/[drupal]/contributions/modules/query_export/query_export.module
ViewVC logotype

Diff of /contributions/modules/query_export/query_export.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph | View Patch Patch

revision 1.1, Thu Jun 8 06:34:55 2006 UTC revision 1.2, Sat Aug 26 00:06:30 2006 UTC
# Line 12  Line 12 
12   *   *
13   *   *
14   * @author Rob Thorne (rob AT torenware.com)   * @author Rob Thorne (rob AT torenware.com)
15   * @copyright Bioneers 2006   * @copyright © 2006 by Collective Heritage Institute
16   * @license GPL v. 2.0   * @license GPL v. 2.0
17   * @version 4.7.0 - development   * @version 4.7.0 - development
18   * @see   * @see
# Line 21  Line 21 
21   */   */
22    
23    
24    //includes here.  //includes here.
25  $mod_path = drupal_get_path('module', 'query_export');  $mod_path = drupal_get_path('module', 'query_export');
26  require_once $mod_path . "/metadata.inc";  require_once $mod_path . "/metadata.inc";
27    require_once $mod_path . "/views.inc";
28    require_once $mod_path . "/civicrm.inc";
29    require_once $mod_path . "/formatters.inc";
30  if (module_exist('civinode')) {  if (module_exist('civinode')) {
31    $civinode_path = drupal_get_path('module', 'civinode');    $civinode_path = drupal_get_path('module', 'civinode');
32    require_once $civinode_path . "/civinode_utils.inc";    require_once $civinode_path . "/civinode_utils.inc";
# Line 34  define('QUERY_EXPORT_ERROR_BASE', 8300); Line 37  define('QUERY_EXPORT_ERROR_BASE', 8300);
37  define('QUERY_EXPORT_ERROR_NO_AUTH',          QUERY_EXPORT_ERROR_BASE + 1);  define('QUERY_EXPORT_ERROR_NO_AUTH',          QUERY_EXPORT_ERROR_BASE + 1);
38  define('QUERY_EXPORT_ERROR_NO_SUCH_QUERY',    QUERY_EXPORT_ERROR_BASE + 2);  define('QUERY_EXPORT_ERROR_NO_SUCH_QUERY',    QUERY_EXPORT_ERROR_BASE + 2);
39  define('QUERY_EXPORT_ERROR_SERVER_IS_HORKED', QUERY_EXPORT_ERROR_BASE + 3);  define('QUERY_EXPORT_ERROR_SERVER_IS_HORKED', QUERY_EXPORT_ERROR_BASE + 3);
40    define('QUERY_EXPORT_ERROR_UNAUTHORIZED',     QUERY_EXPORT_ERROR_BASE + 4);
41    
42    
43    //Some Jserv defaults
44    define('QUERY_EXPORT_DEFAULT_JSERV', 'http://localhost:8081/jasperService');
45    
46  /**  /**
47   * Implementation of hook_help   * Implementation of hook_help
48   *   *
# Line 43  define('QUERY_EXPORT_ERROR_SERVER_IS_HOR Line 50  define('QUERY_EXPORT_ERROR_SERVER_IS_HOR
50    
51  function query_export_help($section) {  function query_export_help($section) {
52    switch ($section) {    switch ($section) {
53      case 'admin/help#query_export':    case 'admin/help#query_export':
54        $output = '<p>'. t('The query_export module is used to create query definitions you want to expose to XML-RPC, for report generation and other purposes. ') .'</p>';      $output = '<p>'. t('The query_export module is used to create query definitions you want to expose to XML-RPC, for report generation and other purposes. ') .'</p>';
55        $output .= '<p>'. t('The query_export administration interface allows for complex configuration.') .'</p>';      $output .= '<p>'. t('The query_export administration interface allows for complex configuration.') .'</p>';
56        $output .= t('<p>You can</p>      $output .= t('<p>You can</p>
57  <ul>  <ul>
58  <li><em>Items Listed Here...</em></li></ul>  <li><em>Items Listed Here...</em></li></ul>
59  ', array('%node-add-query_export' => url('node/add/query_export'), '%admin-settings-content-types-query_export' => url('admin/settings/content-types/query_export')));  ', array('%node-add-query_export' => url('node/add/query_export'), '%admin-settings-content-types-query_export' => url('admin/settings/content-types/query_export')));
60    
61        return $output;      return $output;
62      case 'admin/modules#description':    case 'admin/modules#description':
63        return t('Allows users to define node queries for export via XML-RPC.');      return t('Allows users to define node queries for export via XML-RPC.');
64      case 'node/add#query_export':    case 'node/add#query_export':
65        return t('Query Exports are definitions of queries you want to use in reporting applications like JasperReports.');      return t('Query Exports are definitions of queries you want to use in reporting applications like JasperReports.');
66    }    }
67  }  }
68    
# Line 63  function query_export_help($section) { Line 70  function query_export_help($section) {
70   * Implementation of hook_node_info().   * Implementation of hook_node_info().
71   */   */
72  function query_export_node_info() {  function query_export_node_info() {
73    return array('query_export' => array('name' => t('query_export'), 'base' => 'query_export'));    return array('query_export' => array('name' => t('Jasper Report Definition'), 'base' => 'query_export'));
74  }  }
75    
76  /**  /**
77   * Implementation of hook_perm().   * Implementation of hook_perm().
78     *
79     * Custom tables are a quick hack that will likely go away in
80     * official versions (June 2006 RMT).
81   */   */
82  function query_export_perm() {  function query_export_perm() {
83    return array('create query exports', 'administer query exports', 'access query exports via XML-RPC');    return array('create query exports', 'administer query exports', 'access query exports via XML-RPC', 'access custom tables');
84  }  }
85    
86  /**  /**
# Line 80  function query_export_access($op, $node) Line 90  function query_export_access($op, $node)
90    //TO DO: this piece requires thought.    //TO DO: this piece requires thought.
91    global $user;    global $user;
92    
93    if ($op == 'create') {    if ($op == 'create' or $op == 'update' or $op == 'delete') {
94      return user_access('create query exports');      return user_access('create query exports');
95    }    }
96    /**    /**
97    if ($op == 'update' || $op == 'delete') {       if ($op == 'update' || $op == 'delete') {
98      if (user_access('edit own stories') && ($user->uid == $node->uid)) {       if (user_access('edit own stories') && ($user->uid == $node->uid)) {
99        return TRUE;       return TRUE;
100      }       }
101    }       }
102    **/    **/
103  }  }
104    
# Line 102  function query_export_menu($may_cache) { Line 112  function query_export_menu($may_cache) {
112    if ($may_cache) {    if ($may_cache) {
113      //Admin screens, query creation screens      //Admin screens, query creation screens
114      $items[] = array('path' => 'query_export/test_rpc',      $items[] = array('path' => 'query_export/test_rpc',
115           'title' => t('Test RPC Routines'),                       'title' => t('Test RPC Routines'),
116           'callback' => 'query_export_test_rpc',                       'callback' => 'query_export_test_rpc',
117           'type' => MENU_CALLBACK,                       'type' => MENU_CALLBACK,
118           'access' => 1                       'access' => user_access('create query exports')
119           );                       );
120        $items[] = array('path' => 'query_export/field_editor',
121                         'title' => t('Set Up Field Handlers'),
122                         'callback' => 'query_export_edit_field_info_form',
123                         'type' => MENU_CALLBACK,
124                         'access' => user_access('create query exports')
125                         );
126    
127        $items[] = array('path' => 'query_export/param_editor',
128                         'title' => t('Set Up Default Parameters'),
129                         'callback' => 'query_export_edit_default_params_form',
130                         'type' => MENU_CALLBACK,
131                         'access' => user_access('create query exports')
132                         );
133    
134        $items[] = array('path' => 'query_export/render',
135                         'title' => t('Produce A Report In Jasper Reports'),
136                         'callback' => 'query_export_render_form',
137                         'type' => MENU_CALLBACK,
138                         'access' => user_access('access query exports via XML-RPC')
139                         );
140    }    }
141    
142    return $items;    return $items;
# Line 114  function query_export_menu($may_cache) { Line 144  function query_export_menu($may_cache) {
144    
145    
146  /**  /**
147     * Menu handler for rendering reports
148     *
149     */
150    function query_export_render_form(){
151      $node_arg = arg(2) and is_numeric(arg(2)) ? arg(2) : FALSE;
152      $output = '';
153      if (!$node_arg) {
154        $output .= "This page should have a listing of possible rep defs";
155      }
156      else {
157        $node = node_load($node_arg);
158        if (!$node or $node->type != 'query_export')
159          drupal_not_found();
160        //$output .= "This should be a form to render Node $node_arg, if it exists";
161    
162        //If we are processing, set up the call to the proxy.
163        if (isset($_POST['edit'])) {
164          $edit = $_POST['edit'];
165          $params = array();
166          if (isset($edit['output_type'])) {
167            if ($edit['output_type'] == 'csv') {
168              return query_export_render_csv_file($node,
169                                                  $edit['query_string']
170                                                  );
171            }
172            else
173              $params['format'] = urlencode($edit['output_type']);
174          }
175          if (isset($edit['query_string']))
176            $params['qstring'] = urlencode($edit['query_string']);
177          error_log('About to launch our proxy...');
178          return query_export_render_report($node_arg, $params);
179    
180        }
181        $form = array();
182        $form['dummy_label'] =
183          array('#type' => 'item',
184                '#title' => t('Definition Name'),
185                '#value' => $node->title);
186        $form['dummy_dsource'] =
187          array('#type' => 'item',
188                '#title' => t('Data Source'),
189                '#value' => query_export_query_ui_name($node->query_name));
190        $form['dummy_dstype'] =
191          array('#type' => 'item',
192                '#title' => t('Source Type'),
193                '#value' => $node->query_type);
194        if (module_exist('civinode') and $node->query_type == 'civicrm') {
195          global $user;
196          $pid = $node->query_profile ?
197            $node->query_profile :
198            civinode_get_default_profile_id($user->uid);
199          $prof_title = civinode_get_profile_title($pid);
200          $form['dummy_prof_id'] =
201            array('#type' => 'item',
202                  '#title' => t('Contact Fields From Profile'),
203                  '#value' => $prof_title);
204    
205        }
206    
207        $form['output_type'] =
208          array('#type' => 'select',
209                '#title' => t('Type Of Output'),
210                '#description' => t('Jasper Reports can generate multiple kinds of output.  Choose the desired format for this report'),
211                '#default_value' => 'csv',
212                '#options' =>
213                array('pdf' => t("PDF File"),
214                      'xls' => t('MS Excel Spreadsheet'),
215                      'html' => t('HTML Web Page'),
216                      'rtf' => t('Rich Text (RTF)'),
217                      'txt' => t('Plain Text'),
218                      'csv' => t('CSV File')
219                      )
220                );
221        $form['query_string'] =
222          array('#type' => 'textfield',
223                '#title' => t('Query String'),
224                '#description' => t('For some report types, you can pass an optional query string that will further restrict what rows you will get in your report  You should ask your system admin how to use this, since this is an "Advanced Feature".  YMMV :-)'),
225                '#size' => 30,
226                '#maxlength' => 60
227                );
228    
229        $form['submit'] =
230          array('#type' => 'submit',
231                '#value' => t('Run Report')
232                );
233        $output .= drupal_get_form('request_jasper_form', $form);
234      }
235    
236      return $output;
237    }
238    
239    
240    /**
241     * Bridging functions for the Java servlet
242     *
243     */
244    
245    
246    /**
247     * Ask the servlet to compile and reload a report definition
248     *
249     * @param int $nid  node id of the report to load or reload.
250     * @return TRUE on success
251     */
252    
253    function query_export_request_compile($nid){
254      $base = variable_get('query_export_jserv_url', QUERY_EXPORT_DEFAULT_JSERV);
255      if ($base) {
256        //$len = strlen($base);
257        //build the query string
258        $qs = "?compile=$nid";
259        $curl = curl_init();
260        $url = $base . $qs;
261        curl_setopt($curl, CURLOPT_URL, $url);
262        curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
263        $rslt = curl_exec($curl);
264        //For now, just log this
265        error_log("Polled $url and got: " . $rslt);
266        //Need to test result, but for now...
267        $match = array();
268        if (preg_match('/^Status\:\s+(\S+)/', $rslt, $match)) {
269          error_log("Got code: " . $match[1]);
270          return TRUE;
271        }
272    
273      }
274      return FALSE;
275    }
276    
277    
278    /**
279     * Process a report rendering via the servlet
280     *
281     */
282    
283    function query_export_render_report($nid, $params = array()){
284      //build request for proxy.
285      $base = variable_get('query_export_jserv_url', QUERY_EXPORT_DEFAULT_JSERV);
286      //Check for params or set defaults
287      //?report=nid&cookie=xxxx&qstring=xxxxxx&format=pdf
288      //TO DO: this should really be using URL encoding functions
289      $cookie = query_export_generate_cookie($nid);
290      if (isset($params['format']))
291        $format = $params['format'];
292      else
293        $format = 'pdf';
294      $qs = "?report=$nid&format=$format&cookie=$cookie";
295      if (isset($params['qstring']))
296        $qs .= "&qstring=" . $params['qstring'];
297      if ($base) {
298        //$len = strlen($base);
299        //build the query string
300        $node = node_load($nid);
301        $file_name = query_export_query_ui_name($node->query_name);
302        //Get an extension and MIME type
303        switch ($format) {
304        case 'pdf':
305          $extension = "pdf";
306          $mime = 'application/pdf';
307          break;
308        case 'xls':
309          $extension = "xls";
310          $mime = 'application/vnd.ms-excel';
311          break;
312        case 'html':
313          $extension = "html";
314          $mime = 'text/html';
315          break;
316        case 'txt':
317          $extension = "txt";
318          $mime = 'text/plain';
319          break;
320        case 'doc':
321          $extension = "doc";
322          $mime = 'application/msword';
323          break;
324        case 'rtf':
325          $extension = "rtf";
326          $mime = 'application/rtf';
327          break;
328        default:
329          $extension = "bin";
330          $mime = 'application/octet-stream';
331          break;
332        }
333        $curl = curl_init();
334        $url = $base . $qs;
335        curl_setopt($curl, CURLOPT_URL, $url);
336        curl_setopt($curl, CURLOPT_RETURNTRANSFER, FALSE);
337        curl_setopt($curl, CURLOPT_BINARYTRANSFER, TRUE);
338        curl_setopt($curl, CURLOPT_HEADER, FALSE); //write our own
339        //curl_setopt($curl, CURLOPT_FAILONERROR, 1);
340        //We need to proxy over our headers
341        //curl_setopt($curl, CURLOPT_HEADERFUNCTION,
342        //            'query_export_proxy_header_callback');
343        curl_setopt($curl, CURLOPT_WRITEFUNCTION,
344                    'query_export_proxy_data_callback');
345        $date = date('r');
346        error_log("Start transfer at $date");
347        header( "Content-type: $mime" );
348        $file_name .= "." . $extension;
349        $file_name = strtr($file_name, array(' ' => "_"));
350        header( "Content-disposition: attachment; filename=$file_name" );
351    
352        curl_exec($curl);
353        $errno = curl_errno($curl);
354        if ($errno)
355          error_log(curl_error($curl));
356        error_log("Called curl_exec with $errno for " . $url);
357        curl_close($curl);
358        exit(0);
359      }
360    }
361    
362    
363    /**
364     * Proxy http headers
365     *
366     * Curl callback to proxy over the HTTP headers
367     */
368    function query_export_proxy_header_callback($curl_obj, $header){
369      error_log($header);
370      if (preg_match('/^HTTP\:/', $header) or
371          preg_match('/^Content/', $header)) {
372    
373        header($header);
374        return strlen($header);
375      }
376      return 0;
377    }
378    
379    
380    function query_export_proxy_data_callback($curl_obj, $data){
381      $size = strlen($data);
382      print $data;
383    #error_log("Wrote $size bytes");
384      return $size;
385    }
386    
387    
388    
389    
390    
391    /**
392     * Implementation of hook_settings
393     *
394     */
395    function query_export_settings(){
396      $form['query_export_jserv_url'] =
397        array('#type' => 'textfield',
398              '#default_value' => variable_get('query_export_jserv_url',
399                                               QUERY_EXPORT_DEFAULT_JSERV),
400              '#title' => t('URL of your Drupal/Jasper bridge'),
401              '#description' => t('If you want to run Jasper Reports queries, you need ' .
402                                  'to supply a URL of your Drupal bridge servlet.'),
403              '#size' => 35,
404              '#maxlength' => 60);
405      return $form;
406    
407    }
408    
409    
410    /**
411     *
412     * Implementation of hook_view
413     *
414     *
415     */
416    function query_export_view(&$node, $teaser = FALSE, $page = FALSE){
417      //Very simple rendering so we can see settings
418      $items = array();
419      $items[] = t('<b>Data Source Name:</b> ') . query_export_query_ui_name($node->query_name);
420      $items[] = t('<b>Type Of Data:</b> ') . $node->query_type;
421      if ($node->query_type == 'civicrm' and $node->query_profile) {
422        if (module_exist('civinode')) {
423          $profile = civinode_get_profile_title($node->query_profile);
424        }
425        else
426          $profile = t('<em>NA</em>');
427        $items[] = t('<b>Use Fields From CRM Profile:</b> ') . $profile;
428    
429      }
430      $output = theme('item_list', $items, t('Report Parameters'));
431      if (module_exist('views') and $node->nid) {
432        $output .= t('<br /><p>You can define special handlers to modify the way some of your fields are formatted,
433        or in the case of custom queries (queries where you write the SQL yourself) where you want to make report
434        generators like iReport aware that you are exporting a particular field.  You can add new definitions or
435        edit existing fields here:</p>');
436        $num_field_defs = query_export_load_field_definitions($node->nid);
437        if ($num_field_defs) {
438          $view = views_get_view('query_export_field_defs');
439          $output .= views_build_view('embed', $view, array($node->nid), TRUE, 20);
440          $output .= "<br />";
441        }
442        $output .= l(t('Add A Special Handler'), 'query_export/field_editor/' . $node->nid);
443        $output .= "<br /><br />\n";
444    
445        $output .= t('<p>You can also define default parameters that can be passed to the report generator
446    to define ranges for queries or to add a parameter to restrict the report to one group or contact. You may
447    add these here:</p><br />');
448        //TO DO: render a view here.
449        $all_params = query_export_fetch_default_param($node->nid);
450        if (count($all_params)) {
451          $view = views_get_view('query_export_param_embed');
452          $output .= views_build_view('embed', $view, array($node->nid), TRUE, 20);
453          $output .= "<br />";
454        }
455    
456        $output .= l(t('Define A Default Parameter'), 'query_export/param_editor/' . $node->nid);
457        $output .= "<br /><br />\n";
458    
459      }
460    
461      $node->body = $output;
462      $node->teaser = $output;
463    }
464    
465    
466    /**
467     * Implementation of hook_link.
468     *
469     * Link goes to the "generate report" page for that node.
470     * @author
471     * @param
472     * @return
473     * @exeption
474     * @see
475     */
476    
477    function query_export_link($type, $node = NULL, $teaser = false){
478      $links = array();
479    
480      if ($type == 'node' and $node->type == 'query_export' and $node->nid) {
481        if (user_access('access query exports via XML-RPC')) {
482          $links[] = l(t('generate a report'), "query_export/render/" . $node->nid,
483                       array('title' => t('Generate a JasperReports report.')));
484        }
485      }
486    
487      return $links;
488    
489    }
490    
491    
492    
493    /**
494     * Implementation of hook_prepare.
495     *
496     * We use this to set up the uploads
497     */
498    function query_export_prepare(&$node){
499      // Clears our files in session when you enter the edit view the first time.
500      // This is so files don't linger on the form if you happen to leave the node
501      // and come back into it.
502      if(count($_POST) == 0) {
503        unset($_SESSION['jasper_jrxml_file']);
504      }
505    
506      //We need to remember if we already have an upload from
507      //before
508      $have_upload = $node->report_text ? TRUE : FALSE;
509    
510      // check for an upload
511      if ($file = file_check_upload('jasper_jrxml_upload')) {
512        // save the upload into drupal's temp directory
513        $temppath = file_directory_temp() . '/jasper/';
514        file_check_directory($temppath, TRUE);
515        $node->jasper_jrxml_file = file_save_upload($file, $temppath .'/'. $file->filename,
516                                                    FILE_EXISTS_REPLACE);
517        $node->jasper_jrxml_file->newfile = TRUE;
518        $_SESSION['jasper_jrxml_file'] = $node->jasper_jrxml_file;
519        $have_upload = TRUE;
520      }
521      else if (!empty($_SESSION['jasper_jrxml_file'])) {
522        $node->jasper_jrxml_file = $_SESSION['jasper_jrxml_file'];
523        $have_upload = TRUE;
524      }
525      else {
526        //May not need this, since we will not keep the file
527        if ($node->jasper_jrxml_file)
528          $_SESSION['jasper_jrxml_file'] = $node->jasper_jrxml_file;
529      }
530    
531      //And if we have an upload, save away this info so that we
532      //can see this later in processing
533      //form_set_value(array('qe_have_upload' =>
534      //              array('#value' => $have_upload)),)
535    
536    }
537    
538    
539    /**
540     * Function to aid managing uploads
541     *
542     * @param int $nid
543     */
544    
545    function query_export_report_rec_has_text($nid){
546      $sql = "SELECT length(report_text) FROM {query_export_reports} WHERE nid = %d";
547      return db_result(db_query($sql,$nid));
548    }
549    
550    /**
551   * Implementation of hook_validate().   * Implementation of hook_validate().
552   */   */
553  function query_export_validate($node) {  function query_export_validate(&$node) {
554    //node_validate_title($node);  
555      //First, as far as query names go, There Can Be Only One.
556      if ($node->query_type == 'views') {
557        $node->crm_query = '';
558        if (!$node->views_query) {
559          form_set_error('views_query', t("Please choose a View to extract data from"));
560        }
561    
562        $node->query_name = $node->views_query;
563      }
564      else if ($node->query_type == 'civicrm') {
565        $node->views_query = '';
566        if (!$node->crm_query) {
567          form_set_error('crm_query', t("Please choose a CiviCRM group to extract data from"));
568        }
569    
570        $node->query_name = $node->crm_query;
571      }
572    
573      //Check if we need an upload
574      $had_report = FALSE;
575      if ($node->nid) {
576        $had_report = query_export_report_rec_has_text($node->nid);
577      }
578      if (!$had_report && !isset($_SESSION['jasper_jrxml_file'])) {
579        form_set_error('jasper_jrxml_upload', t('Please choose a Jasper Reports definition file (JRXML) to upload.'));
580      }
581    
582      //Prepare stuff gets thrown away?  Fix it here as well
583      if (isset($_SESSION['jasper_jrxml_file'])) {
584        $node->jasper_jrxml_file = $_SESSION['jasper_jrxml_file'];
585      }
586    }
587    
588    
589    
590    function query_export_submit(&$node) {
591      //Set derived fields if not already set
592      if (!isset($node->query_name)) {
593        if ($node->query_type == 'civicrm')
594          $node->query_name = $node->crm_query;
595        elseif ($node->query_type == 'views')
596          $node->query_name = "views::" . $node->views_query;
597        else
598          $node->query_name = "custom::custom_{$node->nid}";
599      }
600      // check if the file was stored in the session during audio_prepare().
601      if (isset($_SESSION['jasper_jrxml_file'])) {
602        $node->jasper_jrxml_file = $_SESSION['jasper_jrxml_file'];
603        //Scoop up the file and delete it
604        $report_source = file_get_contents($node->jasper_jrxml_file->filepath);
605        if (!$report_source) {
606          drupal_set_message(t('Warning -- upload failed!'));
607          error_log("Upload error: " . $node->jasper_jrxml_file->filepath);
608        }
609        else {
610          $node->report_text = $report_source;
611          $node->jasper = NULL;
612        }
613    
614        // won't be needing this any longer
615        file_delete($node->jasper_jrxml_file);
616        unset($_SESSION['jasper_jrxml_file']);
617      }
618    
619    }
620    
621    
622    
623    
624    /**
625     * Implementation of hook_insert
626     */
627    function query_export_insert(&$node) {
628      $sql = "INSERT INTO {query_export_reports}
629              (nid, last_update, query_name, query_type,query_profile,
630               query_sql, report_text)
631              VALUES(%d,UNIX_TIMESTAMP(),'%s','%s',%d,'%s','%s')";
632    
633      db_query($sql, $node->nid,
634               $node->query_name, $node->query_type,
635               $node->query_profile,
636               $node->query_sql,
637               $node->report_text);
638      //Get the proxy to update the record
639      query_export_request_compile($node->nid);
640  }  }
641    
642    
643  /**  /**
644     * Implementation of hook_update
645     *
646     */
647    function query_export_update($node){
648      if ($node->report_text) {
649        //If we are not setting this now, we want
650        //to 0 out the jasper object, but we don't
651        //want to remove an existing report.
652        $set_report = "report_text = '%s',";
653      }
654      else
655        $set_report = '';
656    
657      $sql = "UPDATE {query_export_reports}
658              SET
659                query_name = '%s', query_type = '%s',
660                query_profile = %d,
661                query_sql = '%s',
662                $set_report
663                last_update = UNIX_TIMESTAMP(),
664                jasper = NULL
665              WHERE nid = %d";
666      if ($set_report)
667        db_query($sql,
668                 $node->query_name, $node->query_type,
669                 $node->query_profile, $node->query_sql,
670                 $node->report_text, $node->nid);
671      else
672        db_query($sql,
673                 $node->query_name, $node->query_type,
674                 $node->query_profile, $node->query_sql, $node->nid);
675    
676      //Get the proxy to update the record
677      query_export_request_compile($node->nid);
678    
679    }
680    
681    
682    /**
683     * Implementation of hook_load
684     *
685     */
686    function query_export_load($node){
687      $sql = 'SELECT * FROM {query_export_reports} WHERE nid = %d';
688      $obj = db_fetch_object(db_query($sql, $node->nid));
689      unset($obj->jasper); //we do not use this internally
690      if ($obj->query_type == 'civicrm')
691        $obj->crm_query = $obj->query_name;
692      else if ($obj->query_type == 'views')
693        $obj->views_query = $obj->query_name;
694    
695      return $obj;
696    }
697    
698    
699    /**
700     * Implementation of hook_delete
701     *
702     */
703    function query_export_delete($node){
704      $sql = "DELETE FROM {query_export_reports} WHERE nid = %d ";
705      db_query($sql, $node->nid);
706    }
707    
708    
709    
710    /**
711   * Implementation of hook_form().   * Implementation of hook_form().
712   */   */
713  function query_export_form(&$node) {  function query_export_form(&$node) {
714    $form['title'] = array('#type' => 'textfield', '#title' => t('Title'), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5);    $form['#attributes'] = array("enctype" => "multipart/form-data");
715    //TO DO: Lots of stuff here.    $form['title'] = array('#type' => 'textfield', '#title' => t('Short Query Description'), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -5);
716    
717      //Handle preview problems...
718      if (!isset($node->query_type)) {
719        if (isset($_POST['edit'])) {
720          $edit = $_POST['edit'];
721          if (isset($edit['query_type'])) {
722            // make some resettings
723            if ($edit['query_type'] != 'views')
724              $_POST['edit']['views_query'] = '';
725            else if ($edit['query_type'] != 'civicrm')
726              $_POST['edit']['crm_query'] = '';
727          }
728        }
729      }
730    
731      $form['query_type'] =
732        array('#type' => 'radios',
733              '#title' => t('Type Of Data Object'),
734              '#description' => t('What kind of data do you want to use for your report?'),
735              '#options' =>
736              array(
737                    'civicrm' => t('CiviCRM Group Of Contacts'),
738                    'views' => t('Drupals Views Data'),
739                    'custom' => t('Custom Query (SQL)'),
740                    ),
741              '#required' => TRUE,
742              '#default_value' => $node->query_type
743              );
744      //Nice option for use of Stupid Java Script tricks: hide one or the other:
745      $v_options =
746        array('' => "-- select a view --",
747              query_export_list_views_queries());
748      $v_default = $node->views_query ? $node->views_query : '';
749    
750      $c_options =    array('' => "-- select a CiviCRM group --",
751                            query_export_list_civicrm_queries());
752      $c_default =  $node->crm_query ? $node->crm_query : '';
753    
754      $form['views_query'] =
755        array('#type' => 'select',
756              '#title' => t('Exportable Drupal Views'),
757              '#options' => $v_options,
758              '#default_value' => $v_default,
759              '#description' => t('Choose a views table to pass to the reporting engine'));
760    
761      $form['crm_query'] =
762        array('#type' => 'select',
763              '#title' => t('Exportable CiviCRM Groups'),
764              '#options' => $c_options,
765              '#default_value' => $c_default,
766              '#description' => t('Choose a group of CiviCRM contacts to pass to the reporting engine'));
767    
768    
769      if (module_exist('civinode')) {
770        $profile_opts = civinode_profile_selector(t('CRM Profile'),
771                                                  t('For formatting CiviCRM contacts, choose a profile to define what fields to pass to the reporting engine'),
772                                                  $node->query_profile);
773        $form['query_profile'] = $profile_opts;
774      }
775    
776    
777      //Custom stuff
778      $form['query_sql'] =
779        array('#type' => 'textarea',
780              '#title' => t('Custom SQL'),
781              '#description' => t("For custom queries only, you can design your own query on any Drupal table.  This is an advanced feature, and if your database disappears in a puff of smoke, don't say we didn't warn you :-)"),
782              '#cols' => 60,
783              '#rows' => 10,
784              '#default_value' => $node->query_sql
785              );
786      //File object for upload
787      $form['jasper_jrxml_upload'] =
788        array(
789              '#type' => 'file',
790              '#title' => t('Attach JRXML File'),
791              '#description' =>
792              t('Select a JasperReports report definition file (.jrxml) to upload to the server'));
793    
794    return $form;    return $form;
795  }  }
796    
797    
798    
799    
800    /**
801     * Implementation of hook_form_alter:
802     * Add a copy button to the main node form
803     *
804     */
805    function query_export_form_alter($form_id, &$form){
806      if ($form_id == 'query_export_node_form') {
807        $copy_value = t('Copy');
808        //check for '#submit' key, 'submit' item(s) #submit['node_form_submit']
809        //#node key on submit,  key nid['#value']
810    
811        //check for a delete item, and if it exists, add a copy as well
812        if (isset($form['delete'])) {
813          //construct our copy button
814          if (!isset($form['copy'])) {
815            $form['copy'] =
816              array('#type' => 'submit',
817                    '#value' => $copy_value);
818    
819            $form['copy']['#weight'] = $form['delete']['#weight'] + 2;
820    
821          }
822        }
823    
824      }
825    }
826    
827    
828    /**
829     * Take advantage of EditAsNew module if available.  For now,
830     * I'm using the renamed template.module version of this.
831     *
832     */
833    function query_export_template_insert(&$node, $template_node){
834      error_log("Template insert was called");
835    
836    }
837    
838    
839    
840    
841  function query_export_xmlrpc() {  function query_export_xmlrpc() {
842    $methods = array();    $methods = array();
843    
# Line 141  function query_export_xmlrpc() { Line 847  function query_export_xmlrpc() {
847    //with everything else.    //with everything else.
848    $methods[] =    $methods[] =
849      array(      array(
850      'drupal.qexport.login',            'drupal.qexport.login',
851      'query_export_qexport_login_handler',            'query_export_qexport_login_handler',
852      array('struct', 'string', 'string'),            array('struct', 'string', 'string'),
853      t('Login/Authentication For Query Export. Takes user/pw, returns qe result object.'));            t('Login/Authentication For Query Export. Takes user/pw, returns qe result object.'));
854    
855    //Get queries of a certain type. pass cookie as first arg,    //Get queries of a certain type. pass cookie as first arg,
856    //params as second.  params is a struct, with keys:    //params as second.  params is a struct, with keys:
# Line 152  function query_export_xmlrpc() { Line 858  function query_export_xmlrpc() {
858    //allow taxonomy somehow.    //allow taxonomy somehow.
859    $methods[] =    $methods[] =
860      array(      array(
861      'drupal.qexport.query.list',            'drupal.qexport.query.list',
862      'query_export_qexport_query_list_handler',            'query_export_qexport_query_list_handler',
863      array('array', 'string', 'struct'),            array('array', 'string', 'struct'),
864      t('Fetches a list of queries that meet some set of criteria'));            t('Fetches a list of queries that meet some set of criteria'));
865    
866    //This method returns meta data about a query. For now, enough    //This method returns meta data about a query. For now, enough
867    //info to describe the query to the reporting engine, whatever    //info to describe the query to the reporting engine, whatever
868    //that is.    //that is.
869    $methods[] =    $methods[] =
870      array(      array(
871      'drupal.qexport.query.metadata',            'drupal.qexport.query.metadata',
872      'query_export_qexport_query_metadata_handler',            'query_export_qexport_query_metadata_handler',
873      array('array', 'string', 'string'),            array('array', 'string', 'string'),
874      t('Fetches metadata about a query'));            t('Fetches metadata about a query'));
875    
876    
877    
# Line 175  function query_export_xmlrpc() { Line 881  function query_export_xmlrpc() {
881    //queries.    //queries.
882    $methods[] =    $methods[] =
883      array(      array(
884      'drupal.qexport.query.run',            'drupal.qexport.query.run',
885      'query_export_qexport_query_run_handler',            'query_export_qexport_query_run_handler',
886      array('array', 'string', 'string', 'struct'),            array('array', 'string', 'string', 'struct'),
887      t('Fetches an array of records for a query'));            t('Fetches an array of records for a query'));
888    
889    
890    
# Line 204  function query_export_qexport_login_hand Line 910  function query_export_qexport_login_hand
910    else {    else {
911      return array('user' => $user_name, 'cookie' => $auth_obj->cookie,      return array('user' => $user_name, 'cookie' => $auth_obj->cookie,
912                   'expires' => $auth_obj->expires                   'expires' => $auth_obj->expires
913             );                   );
914    }    }
915   //test stub    //test stub
916   //return array('user' => $user_name, 'cookie' => 'monster');    //return array('user' => $user_name, 'cookie' => 'monster');
917  }  }
918    
919    
# Line 217  function query_export_qexport_query_list Line 923  function query_export_qexport_query_list
923    error_log("user recognized as ", $access->user);    error_log("user recognized as ", $access->user);
924    if(!user_access('access query exports via XML-RPC')){    if(!user_access('access query exports via XML-RPC')){
925      error_log("User not authorized");      error_log("User not authorized");
926      return array(); //isn't there a nicer way to bail??      return xmlrpc_server_error(QUERY_EXPORT_ERROR_UNAUTHORIZED, t('Not authorized for this user'));
927    }    }
928    //Test stub    //Test stub
929    //return array('fortunes', 'quick_hits', 'best_episodes');    //return array('fortunes', 'quick_hits', 'best_episodes');
# Line 231  function query_export_qexport_query_meta Line 937  function query_export_qexport_query_meta
937    global $user;    global $user;
938    $access = query_export_auth_from_cookie($cookie, $user);    $access = query_export_auth_from_cookie($cookie, $user);
939    if(!user_access('access query exports via XML-RPC'))    if(!user_access('access query exports via XML-RPC'))
940      return array(); //isn't there a nicer way to bail??      return xmlrpc_server_error(QUERY_EXPORT_ERROR_UNAUTHORIZED, t('Not authorized for this user'));
941    return _qex_test_get_metadata($qname);    return _qex_test_get_metadata($qname);
942    //Test stub    //Test stub
943    /**    /**
944    return array(       return array(
945           array('name' => 'name',       array('name' => 'name',
946           'type' => 'string',       'type' => 'string',
947           'size' => 20,       'size' => 20,
948           'required' => 1),       'required' => 1),
949           array('name' => 'rating',       array('name' => 'rating',
950           'type' => 'int',       'type' => 'int',
951           'required' => 1)       'required' => 1)
952           );       );
953    **/    **/
954    
955  }  }
# Line 252  function query_export_qexport_query_meta Line 958  function query_export_qexport_query_meta
958  function query_export_qexport_query_run_handler($cookie, $qname, $qparams){  function query_export_qexport_query_run_handler($cookie, $qname, $qparams){
959    $results = array();    $results = array();
960    /**    /**
961    //Test stub     //Test stub
962    $results[] = array('name' => "Uncle Junior", 'rating' => 9);     $results[] = array('name' => "Uncle Junior", 'rating' => 9);
963    $results[] = array('name' => "Big Tony", 'rating' => 15);     $results[] = array('name' => "Big Tony", 'rating' => 15);
964    $results[] = array('name' => "Little Tony", 'rating' => 0);     $results[] = array('name' => "Little Tony", 'rating' => 0);
965    **/    **/
966    
967    global $user;    global $user;
968      error_log("Entering Run XMLRPC handler");
969    $access = query_export_auth_from_cookie($cookie, $user);    $access = query_export_auth_from_cookie($cookie, $user);
970    if(!user_access('access query exports via XML-RPC'))    if(!user_access('access query exports via XML-RPC')) {
971      return array(); //isn't there a nicer way to bail??      error_log("Not authorized for run!!");
972        return xmlrpc_server_error(QUERY_EXPORT_ERROR_UNAUTHORIZED, t('Not authorized for this user'));
973      }
974      error_log("OK for run; about to request records");
975    return _qex_test_get_records_for_query($qname, $qparams);    return _qex_test_get_records_for_query($qname, $qparams);
976  }  }
977    
978  /**  /**
979  * Authentication functions   * Authentication functions
980  *   *
981  */   */
982    
983    
984  /**  /**
985  * Authenticate against drupal and return a query_export   * Authenticate against drupal and return a query_export
986  * auth record. Return NULL if we did not authenticate.   * auth record. Return NULL if we did not authenticate.
987  *   *
988  */   */
989    
990  function query_export_authenticate($user_name, $password){  function query_export_authenticate($user_name, $password){
991    global $user;    global $user;
# Line 294  function query_export_authenticate($user Line 1003  function query_export_authenticate($user
1003    //And create the record (slight possibility of collision. Do I need to    //And create the record (slight possibility of collision. Do I need to
1004    //add some kind of random seed as well?    //add some kind of random seed as well?
1005    $sql = "INSERT INTO {query_export_auth} (uid, expires, user_name, cookie) " .    $sql = "INSERT INTO {query_export_auth} (uid, expires, user_name, cookie) " .
1006           "VALUES(%d, %d, '%s', '%s')";      "VALUES(%d, %d, '%s', '%s')";
1007    db_query($sql, $user_obj->uid, $expires, $user_name, $cookie);    db_query($sql, $user_obj->uid, $expires, $user_name, $cookie);
1008    //And get back our object    //And get back our object
1009    $obj = db_fetch_object(db_query("SELECT * FROM {{query_export_auth}} WHERE cookie = '%s'", $cookie));    $obj = db_fetch_object(db_query("SELECT * FROM {query_export_auth} WHERE cookie = '%s'", $cookie));
1010    return $obj;    return $obj;
1011  }  }
1012    
1013    
1014    /**
1015     * Create a cookie record so our user can pass a temporary
1016     * authorization to our Java proxy
1017     *
1018     */
1019    
1020    function query_export_generate_cookie($seed = 'salt'){
1021      global $user;
1022    
1023      //Calculate our expiry time
1024      //Default at 1 minute
1025      $expires = 60 + time();
1026      //Make a cookie
1027      $dough = sprintf("%s%d%s", $user->name, $expires, $salt);
1028      $cookie = md5($dough);
1029      //And create the record (slight possibility of collision. Do I need to
1030      //add some kind of random seed as well?
1031      $sql = "INSERT INTO {query_export_auth} (uid, expires, user_name, cookie) " .
1032        "VALUES(%d, %d, '%s', '%s')";
1033      db_query($sql, $user->uid, $expires, $user->name, $cookie);
1034      $time = date('r');
1035      error_log("$date: cookie $cookie for user " . $user->name);
1036      return $cookie;
1037    }
1038    
1039    
1040    
1041  /**  /**
1042  * Authenticate by looking up the cookie. Must find the cookie, and   * Authenticate by looking up the cookie. Must find the cookie, and
1043  * the cookie should still be valid.   * the cookie should still be valid.
1044  *   *
1045  */   */
1046    
1047  function query_export_auth_from_cookie($cookie, &$user_obj){  function query_export_auth_from_cookie($cookie, &$user_obj){
1048    global $user;    global $user;
# Line 324  function query_export_auth_from_cookie($ Line 1061  function query_export_auth_from_cookie($
1061      _query_export_construct_user($obj->uid);      _query_export_construct_user($obj->uid);
1062    
1063      if ($user->uid) {      if ($user->uid) {
1064          $time = date('r');
1065          error_log("$time: authed as user " . $user->uid . "with cookie $cookie");
1066        return $obj;        return $obj;
1067      }      }
1068    }    }
1069      $time = date('r');
1070      error_log("$time: auth failed with cookie $cookie");
1071    //Or we are nobody at all    //Or we are nobody at all
1072    return NULL;    return NULL;
1073  }  }
# Line 394  function query_export_test_rpc(){ Line 1135  function query_export_test_rpc(){
1135  }  }
1136    
1137    
1138  function _qex_test_avail_queries(){  function query_export_list_civicrm_queries(){
   $queries = array();  
   //For now, let's export one query for each flexinode type we know about  
   if (module_exist('flexinode')) {  
     $types = flexinode_content_types();  
     foreach($types as $ctype_id => $type_data){  
       $key = 'flexinode::flexinode-' . $ctype_id;  
       $queries[$key] = $type_data->name; //  
     }  
   }  
1139    //And for civicrm, list our groups    //And for civicrm, list our groups
1140      $queries = array();
1141    if (module_exist('civicrm') and module_exist('civinode')) {    if (module_exist('civicrm') and module_exist('civinode')) {
1142      $groups = civinode_get_groups(); //get 'em all for now      $groups = civinode_get_groups(); //get 'em all for now
1143      if($groups){      if($groups){
# Line 414  function _qex_test_avail_queries(){ Line 1147  function _qex_test_avail_queries(){
1147        }        }
1148      }      }
1149    }    }
1150      return $queries;
1151    }
1152    
1153    function _qex_test_avail_queries(){
1154      $queries = array();
1155      //For now, let's export one query for each flexinode type we know about
1156      /**
1157    
1158      //No flexinode unless someone begs :-)
1159      //and pays too, for that matter
1160    
1161      if (module_exist('flexinode')) {
1162      $types = flexinode_content_types();
1163      foreach($types as $ctype_id => $type_data){
1164      $key = 'flexinode::flexinode-' . $ctype_id;
1165      $queries[$key] = $type_data->name; //
1166      }
1167      }
1168      **/
1169    
1170      //And for civicrm, list our groups
1171      if (module_exist('civicrm') and module_exist('civinode')) {
1172        $queries = query_export_list_civicrm_queries();
1173      }
1174    
1175      //If we have created an custom queries, fetch them now
1176      $custom_queries = _qex_fetch_custom_queries();
1177      foreach ($custom_queries as $qname => $qtitle) {
1178        $queries[$qname] = $qtitle;
1179      }
1180    if (module_exist('views') and module_exist('civinode')) {    if (module_exist('views') and module_exist('civinode')) {
     $groups = civinode_get_groups(); //get 'em all for now  
1181      //TO DO: I probably need to wrap this stuff into      //TO DO: I probably need to wrap this stuff into
1182      //some kind of loader function.      //some kind of loader function.
1183      if (!function_exists('query_export_list_views_queries')) {      if (!function_exists('query_export_list_views_queries')) {
# Line 437  function _qex_test_avail_queries(){ Line 1198  function _qex_test_avail_queries(){
1198  }  }
1199    
1200    
1201    /**
1202     * Return our custom queries
1203     *
1204     */
1205    function _qex_fetch_custom_queries(){
1206      $sql = "select q.query_name, n.title as query_title from {node} n LEFT JOIN {query_export_reports} q ON (n.nid = q.nid) WHERE q.query_type = 'custom'";
1207      //exclude queries we do not have the right to see
1208      $mod_sql = db_rewrite_sql($sql, 'n', 'nid');
1209      $rslt = db_query($mod_sql);
1210      $queries = array();
1211      while ($row = db_fetch_array($rslt)) {
1212        $queries[$row['query_name']] = $row['query_title'];
1213      }
1214      return $queries;
1215    }
1216    
1217    
1218  function _qex_test_get_metadata($qname){  function _qex_test_get_metadata($qname){
1219    list($namespace, $name) = explode('::', $qname);    list($namespace, $name) = explode('::', $qname);
1220    //Test data: all legal names have this    //Test data: all legal names have this
# Line 445  function _qex_test_get_metadata($qname){ Line 1223  function _qex_test_get_metadata($qname){
1223    $type_info = query_export_get_type($namespace, $name);    $type_info = query_export_get_type($namespace, $name);
1224    if(!$type_info){    if(!$type_info){
1225      //consider logging error or telling xmlrpc      //consider logging error or telling xmlrpc
1226        error_log("No type info... bailing");
1227      return xmlrpc_server_error(QUERY_EXPORT_ERROR_NO_SUCH_QUERY, t('Query is unknown to server'));      return xmlrpc_server_error(QUERY_EXPORT_ERROR_NO_SUCH_QUERY, t('Query is unknown to server'));
1228    }    }
1229    //Do not pass internal data to XMLRPC    //Do not pass internal data to XMLRPC
# Line 457  function _qex_test_get_metadata($qname){ Line 1236  function _qex_test_get_metadata($qname){
1236  function _qex_test_get_records_for_query($qname, $params){  function _qex_test_get_records_for_query($qname, $params){
1237    list($namespace, $name) = explode('::', $qname);    list($namespace, $name) = explode('::', $qname);
1238    
1239      //Temporary hack: custom types
1240      //if ($namespace == 'custom' and !user_access('access custom tables'))
1241      //  xmlrpc_server_error(QUERY_EXPORT_ERROR_UNAUTHORIZED, t('Not authorized for this user'));
1242    //process parameters    //process parameters
1243    $args = isset($params['args']) ? $params['args'] : FALSE;    $args = isset($params['args']) ? $params['args'] : FALSE;
1244    $start = isset($params['start']) ? $params['start'] : 0;    $start = isset($params['start']) ? $params['start'] : 0;
1245    $num_recs = isset($params['num_recs']) ? $params['num_recs'] : 200;    //num_recs should be 0 if no limit, the number otherwise. Not all sources
1246      //implement this (currently, only CiviCRM does)
1247      $num_recs = isset($params['num_recs']) ? $params['num_recs'] : 0;
1248      $profile = isset($params['profile_id']) ? $params['profile_id'] : 0;
1249      $query_nid = isset($params['query_nid']) ? $params['query_nid'] : 0;
1250    
1251      return query_export_get_record_array($namespace, $name, $query_nid,
1252                                           $args, $profile, $start, $num_recs);
1253    }
1254    
1255    
1256    /**
1257     * Some view support for choosing queries
1258     *
1259     */
1260    
1261    
1262    
1263    /**
1264     * Implementation of hook_views_tables (View API)
1265     *
1266     */
1267    
1268    function query_export_views_tables(){
1269      $tables['query_export_defs'] =
1270        array('name' => 'query_export_reports',
1271              'provider' => 'query_export',
1272              'join' =>
1273              array('left' =>
1274                    array('table' => 'node',
1275                          'field' => 'nid'),
1276                    'right' =>
1277                    array('field' => 'nid')
1278                    ),
1279              'fields' =>
1280              array('query_name' =>
1281                    array('name' => t('Query Exporter: Query Name'),
1282                          'handler' => 'query_export_qname_view_handler',
1283                          'sortable' => TRUE),
1284                    'query_type' =>
1285                    array('name' => t('Query Exporter: Query Type')),
1286                    'query_profile' =>
1287                    array('name' => t('Query Exporter: CRM Profile Definition'),
1288                          'handler' => 'query_export_profile_view_handler'
1289                          ),
1290                    'last_update' =>
1291                    array('name' => t('Query Exporter: Last Updated'),
1292                          'handler' => 'query_export_unix_time_view_handler'),
1293                    )
1294              );
1295    
1296      //Expose data handlers
1297      $tables['query_export_formatters'] =
1298        array('name' => 'query_export_data_handlers',
1299              'provider' => 'query_export',
1300              'join' =>
1301              array('left' =>
1302                    array('table' => 'node',
1303                          'field' => 'nid'),
1304                    'right' =>
1305                    array('field' => 'nid')
1306                    ),
1307              'fields' =>
1308              array(
1309                    'field_name' =>
1310                    array('name' => t('Query Exporter: Field Name'),
1311                          'addlfields' => array('nid'),
1312                          'handler' =>
1313                          array('query_export_formatter_field_link_handler' => t('Edit Or Delete Link'),
1314                                'query_export_passthru_handler' => t('Pass through')),
1315                          'sortable' => TRUE),
1316                    'handler' =>
1317                    array('name' => t('Query Exporter: Data Formatter'),
1318                          'handler' => 'query_export_formatter_name_handler'),
1319                    'extra' =>
1320                    array('name' => t('Query Exporter: Extra Formatter Info')),
1321                    )
1322              );
1323    
1324      //Expose default parameters
1325      $tables['query_export_parameters'] =
1326        array('name' => 'query_export_param_defaults',
1327              'provider' => 'query_export',
1328              'join' =>
1329              array('left' =>
1330                    array('table' => 'node',
1331                          'field' => 'nid'),
1332                    'right' =>
1333                    array('field' => 'nid')
1334                    ),
1335              'fields' =>
1336              array(
1337                    'param_name' =>
1338                    array('name' => t('Query Exporter: Default Parameter'),
1339                          'addlfields' => array('nid'),
1340                          'handler' =>
1341                          array('query_export_param_link_handler' => t('Edit Or Delete Link'),
1342                                'query_export_passthru_handler' => t('Pass through')),
1343                          'sortable' => TRUE),
1344                    'default_value' =>
1345                    array('name' => t('Query Exporter: Default Param Value')),
1346                    'description' =>
1347                    array('name' => t('Query Exporter: Parameter Description')),
1348                    )
1349              );
1350    
1351    
1352      return $tables;
1353    }
1354    
1355    
1356    function query_export_profile_view_handler($fieldinfo, $fielddata, $value, $data){
1357      if (module_exist('civinode') and module_exist('civicrm')) {
1358        if ($value)
1359          $title = civinode_get_profile_title($value);
1360        else
1361          $title = t('NA');
1362        if ($title)
1363          return $title;
1364        else
1365          return $value;
1366      }
1367      else
1368        return t('NA');
1369    }
1370    
1371    
1372    function query_export_qname_view_handler($fieldinfo, $fielddata, $value, $data){
1373      return query_export_query_ui_name($value);
1374    }
1375    
1376    function query_export_unix_time_view_handler($fieldinfo, $fielddata, $value, $data){
1377      if (!$value or !is_numeric($value))
1378        return t('NA');
1379      return date('r', $value);
1380    }
1381    
1382    
1383    function query_export_formatter_name_handler($fieldinfo, $fielddata, $value, $data){
1384      query_export_prime_handler_cache(); //safe to call multiple times
1385      $title = query_export_get_data_handlers($value);
1386      if ($title)
1387        return $title;
1388      else
1389        return $value;
1390    }
1391    
1392