Dynamic facet creation.
[mkdru-moved-to-drupal.org.git] / mkdru.module
1 <?php
2 // $Id$
3
4
5
6 // Module metainfo
7 /**
8 * Implements hook_node_info()
9 */
10 function mkdru_node_info() {
11   return array(
12     'mkdru' => array(
13       'name' => t("Pazpar2 metasearch interface"),
14       'module' => 'mkdru',
15       'description' => t("Metasearch interface for Z39.50/SRU and other targets via a Pazpar2/Service Proxy backend"),
16     )
17   );
18 }
19
20 function mkdru_ting_search_tab($keys) {
21   error_log("TING SEARCH TAB invoked");
22    $path = drupal_get_path('module', 'mkdru');
23   // Include client library.
24   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') 
25     . '/pz2.js', 'module', 'footer');
26   drupal_add_js($path . '/jquery.ba-bbq.js', 'module', 'footer');
27   drupal_add_js($path . '/recipe.js', 'module', 'footer');
28   drupal_add_js($path . '/mkdru.theme.js', 'module', 'footer');
29   drupal_add_js($path . '/mkdru.client.js', 'module', 'footer');
30   $html = theme('mkdru_results');
31   drupal_add_js(array('mkdru' => 
32     array(
33       'settings' => json_encode(variable_get('mkdru_ding', NULL)),
34       'pz2_path' => variable_get('pz2_path', '/pazpar2/search.pz2'),
35       'query' => $keys
36     )
37   ), 'setting');
38   return array("content" => $html, "title" => "Meta Search");
39 }
40
41 /**
42 * Implements hook_perm()
43 */
44 function mkdru_perm() {
45   return array('create metasearch interface', 'edit any metasearch interface', 'edit own metasearch interface');
46 }
47
48 /**
49 * Implements hook_access()
50 */
51 function mkdru_access($op, $node, $account) {
52
53   if ($op == 'create') {
54     // Only users with permission to do so may create this node type.
55     return user_access('create metasearch interface', $account);
56   }
57
58   // Users who create a node may edit or delete it later, assuming they have the
59   // necessary permissions.
60   if ($op == 'update' || $op == 'delete') {
61     if (user_access('edit own metasearch interface', $account) && ($account->uid == $node->uid)) {
62       return TRUE;
63     }
64     elseif (user_access('edit any metasearch interface', $account)) {
65       return TRUE;
66     }
67   }
68 }
69
70 /**
71 * Implements hook_menu()
72 */
73 function mkdru_menu() {
74   $items['admin/settings/mkdru'] = array(
75     'title' => 'Pazpar2 Metasearch Settings',
76     'description' => 'Settings for mkdru.',
77     'page callback' => 'drupal_get_form',
78     'page arguments' => array('mkdru_admin_settings'),
79     'access arguments' => array('administer site configuration'),
80     'type' => MENU_NORMAL_ITEM,
81     'file' => 'mkdru.admin.inc',
82   );
83   $items['admin/settings/mkdru-ding'] = array(
84     'title' => 'Pazpar2 Metasearch Ding Integration',
85     'description' => 'Search settings for mkdru instance integrated into Ding.',
86     'page callback' => 'drupal_get_form',
87     'page arguments' => array('mkdru_ding_settings'),
88     'access arguments' => array('administer site configuration'),
89     'type' => MENU_NORMAL_ITEM,
90   );
91   $items['ahah-mkdru-facet'] = array(
92     'page callback' => 'mkdru_add_facet_callback',
93     'access arguments' => array('create metasearch interface'),
94     'type' => MENU_CALLBACK,
95   );
96   return $items;
97 }
98
99 /**
100 * Implements hook_init()
101 */
102 function mkdru_init() {
103   // Applies our module specific CSS to all pages. This works best because
104   // all CSS is aggregated and cached so we reduce the number of HTTP 
105   // requests and the size is negligible.
106   drupal_add_css(drupal_get_path('module', 'mkdru') .'/mkdru.css');
107 }
108
109
110 // Config form common to node and settings
111 // function mkdru_settings_form($form, &$form_state) {
112 function mkdru_settings_form(&$form_state) {
113   if (isset($form_state['values']['settings'])) {
114     $settings = $form_state['values']['settings'];
115   }
116   else if (isset($form_state['build_info']['args']['settings'])) {
117     $settings = $form_state['build_info']['args']['settings'];
118   }
119   else {
120     $settings = variable_get('mkdru_defaults', NULL);
121   }
122
123   $form['#cache'] = TRUE;
124
125   $form['settings'] = array(
126     '#tree' => TRUE,
127   );
128
129   $form['settings']['pz2_path'] = array(
130     '#type' => 'textfield',
131     '#title' => t('Pazpar2/Service Proxy path'),
132     '#description' => t('Path that takes Pazpar2 commands via HTTP'),
133     '#required' => TRUE,
134     '#default_value' => $settings['pz2_path'],
135   );
136   $form['settings']['use_sessions'] = array(
137     '#type' => 'checkbox',
138     '#title' => t('Session handling'),
139     '#description' => t('Disable for use with Service Proxy'),
140     '#default_value' => $settings['use_sessions'],
141   );
142
143   $form['settings']['sp']  = array(
144     '#type' => 'fieldset',
145     '#title' => t('Service Proxy specific settings'),
146     '#collapsible' => TRUE,
147     '#collapsed' => FALSE
148   );
149   $form['settings']['sp']['user'] = array(
150     '#type' => 'textfield',
151     '#title' => t('Service Proxy username'),
152     '#description' => t('Service Proxy username'),
153     '#required' => FALSE,
154     '#default_value' => $settings['sp']['user'],
155   );
156   $form['settings']['sp']['pass'] = array(
157     '#type' => 'password',
158     '#title' => t('Service Proxy password'),
159     '#description' => t('Service Proxy password'),
160     '#required' => FALSE,
161     '#default_value' => $settings['sp']['pass'],
162   );
163
164   $form['settings']['facets']  = array(
165     '#type' => 'fieldset',
166     '#title' => t('Facet settings'),
167     // Set up the wrapper so that AJAX will be able to replace the fieldset.
168     '#prefix' => '<div id="mkdru-facets-form-wrapper">', 
169     '#suffix' => '</div>',
170     '#collapsible' => TRUE,
171     '#collapsed' => FALSE
172   );
173
174   foreach (array_keys($settings['facets']) as $facet) {
175     $form['settings']['facets'][$facet]  = array(
176       '#type' => 'fieldset',
177       '#title' => $facet . ' ' . t('facet'),
178       '#collapsible' => TRUE,
179       '#collapsed' => FALSE
180     );
181     $form['settings']['facets'][$facet]['displayName'] = array(
182       '#type' => 'textfield',
183       '#title' => t('Facet name to display in UI'),
184       '#required' => TRUE,
185       '#default_value' => $settings['facets'][$facet]['displayName'],
186     );
187     $form['settings']['facets'][$facet]['pz2Name'] = array(
188       '#type' => 'textfield',
189       '#title' => t('Name of termlist in Pazpar2'),
190       '#required' => TRUE,
191       '#default_value' => $settings['facets'][$facet]['pz2Name'],
192     );
193     $form['settings']['facets'][$facet]['limiter'] = array(
194       '#type' => 'textfield',
195       '#title' => t('Query limiter string'),
196       '#default_value' => $settings['facets'][$facet]['limiter'],
197       '#size' => 5,
198     );
199     $form['settings']['facets'][$facet]['max'] = array(
200       '#type' => 'textfield',
201       '#title' => t('Number of terms to display'),
202       '#required' => TRUE,
203       '#default_value' => $settings['facets'][$facet]['max'],
204       '#size' => 3,
205       '#maxlength' => 3,
206     );
207     $form['settings']['facets'][$facet]['orderWeight'] = array(
208       '#type' => 'textfield',
209       '#title' => t('Facet weight'),
210       '#default_value' => $settings['facets'][$facet]['orderWeight'],
211       '#size' => 3,
212       '#maxlength' => 3,
213     );
214   }
215   $form['new_facet']  = array(
216     '#type' => 'fieldset',
217     '#title' => t('Add new facet...'),
218     '#tree' => TRUE,
219     '#collapsible' => TRUE,
220     '#collapsed' => FALSE
221   );
222   $form['new_facet']['canonical'] = array(
223     '#type' => 'textfield',
224     '#title' => t('Canonical name of new facet'),
225   );
226   $form['new_facet']['button'] = array(
227     '#type' => 'submit',
228     '#value' => t('Add facet'),
229     '#description' => t('Configure additional facets based on Pazpar2/SP termlists'),
230     '#submit' => array('mkdru_add_facet_submit'),
231     '#ahah' => array(
232       'path' => 'ahah-mkdru-facet',
233       'wrapper' => 'mkdru-facets-form-wrapper',
234     ),
235   );
236   return $form;
237 }
238
239 function mkdru_add_facet_submit($form, &$form_state) {
240 //   $newfacet = $form_state['values']['new_facet']['canonical'];
241   $newfacet = 'PFUI';
242   $form_state['values']['settings']['facets'][$newfacet] = NULL;
243   $form_state['rebuild'] = TRUE;
244   return $form_state['values'];
245 }
246
247 function mkdru_add_facet_callback() {
248   // Necessary to work with hook_form
249   module_load_include('inc', 'node', 'node.pages');
250
251   // Retrieve form from cache
252   $form_state = array('storage' => NULL, 'submitted' => FALSE);
253   $form_build_id = $_POST['form_build_id'];
254   $form = form_get_cache($form_build_id, $form_state);
255
256   // Run drupal_process_form to call submit handler and update $form_state
257   $args = $form['#parameters'];
258   $form_id = array_shift($args);
259   $form_state['post'] = $form['#post'] = $_POST;
260   $form['#programmed'] = $form['#redirect'] = FALSE;
261   drupal_process_form($form_id, $form, $form_state);
262
263   // Regenerate form so we can render the new facet part
264   $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id);
265
266   // Choose subset of form to redraw.
267   $facet_form = $form['settings']['facets'];
268   // Prevent duplicate wrappers.
269   unset($facet_form['#prefix'], $facet_form['#suffix']);
270   $output = theme('status_messages') . drupal_render($facet_form);
271
272   // Final rendering callback
273   drupal_json(array('status' => TRUE, 'data' => $output));
274 }
275
276
277
278 // Ding config
279 function mkdru_ding_settings(&$form_state) {
280   $form_state['build_info']['args']['settings'] = variable_get('mkdru_ding', NULL);
281   $form = drupal_retrieve_form('mkdru_settings_form', &$form_state);
282   $form['settings']['#title'] = t('Default search settings');
283   $form['submit'] = array(
284     '#type' => 'submit',
285     '#value' => 'Save configuration',
286   );
287   return $form;
288 }
289 function mkdru_ding_settings_submit($form, &$form_state) {
290   variable_set('mkdru_ding', $form_state[values][settings]);
291   drupal_set_message(t('The configuration options have been saved.'));
292 }
293
294
295
296 // Node config
297 /**
298 * Implements hook_form()
299 */
300 function mkdru_form(&$node, &$form_state) {
301   if (isset($node->settings)) {
302     // Second decode parameter indicates associative array
303     $form_state['build_info']['args']['settings'] = json_decode($node->settings, TRUE);
304   }
305
306   $form = drupal_retrieve_form('mkdru_settings_form', &$form_state);
307   $type = node_get_types('type', $node);
308   $form['title'] = array(
309     '#type' => 'textfield',
310     '#title' => check_plain($type->title_label),
311     '#required' => FALSE,
312     '#default_value' => $node->title,
313     '#weight' => -5
314   );
315   return $form;
316 }
317
318
319 /**
320 * Implements hook_validate()
321 */
322 function mkdru_validate($node) {
323   // TODO: validation
324 }
325
326 /**
327 * Implements hook_insert().
328 */
329 function mkdru_insert($node) {
330   $record['nid'] =  $node->nid;
331   $record['vid'] =  $node->vid;
332   $record['settings'] =  json_encode($node->settings);
333   drupal_write_record('mkdru', &$record);
334 }
335
336 /**
337 * Implements hook_update().
338 */
339 function mkdru_update($node) {
340   if ($node->revision) {
341     // New revision; treat it as a new record.
342     mkdru_insert($node);
343   }
344   else {
345     $record['nid'] =  $node->nid;
346     $record['vid'] =  $node->vid;
347     $record['settings'] =  json_encode($node->settings);
348     drupal_write_record('mkdru', &$record, 'vid');
349   }
350 }
351
352 /**
353  * Implements hook_nodeapi().
354  *
355  * When a node revision is deleted, we need to remove the corresponding record
356  * from our table. The only way to handle revision deletion is by implementing
357  * hook_nodeapi().
358  */
359 function mkdru_nodeapi(&$node, $op, $teaser, $page) {
360   switch ($op) {
361     case 'delete revision':
362       db_query('DELETE FROM {mkdru} WHERE vid = %d', $node->vid);
363       break;
364   }
365 }
366
367 /**
368  * Implements hook_delete().
369  */
370 function mkdru_delete($node) {
371   // Deleting by nid covers all revisions.
372   db_query('DELETE FROM {mkdru} WHERE nid = %d', $node->nid);
373 }
374
375
376
377 // Node rendering
378 /**
379 * Implements hook_load()
380 */
381 function mkdru_load($node) {
382   return db_fetch_object(db_query('SELECT * FROM {mkdru} WHERE vid = %d', $node->vid));
383 }
384
385 /**
386 * Implements hook_theme().
387 */
388 function mkdru_theme() {
389   return array(
390     'mkdru_form' => array(
391       'template' => 'mkdru-form',
392       'arguments' => array(),
393     ),
394     'mkdru_results' => array(
395       'template' => 'mkdru-results',
396       'arguments' => array(),
397     ),
398     'mkdru_js' => array(
399       'arguments' => array('node' => NULL),
400     ),
401     'mkdru_block_search' => array(
402       'template' => 'mkdru-block-search',
403       'arguments' => array('nid' => NULL, 'path' => NULL),
404     ),
405     'mkdru_block_facet' => array(
406       'template' => 'mkdru-block-facet',
407       'arguments' => array('class' => NULL)
408     )
409   );
410 }
411
412 /**
413 * Theme function to include Javascript search client and deps
414 */
415 function theme_mkdru_js($node) {
416   $path = drupal_get_path('module', 'mkdru');
417   // Pazpar2 client library.
418   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') . '/pz2.js', 'module', 'footer', TRUE, TRUE, FALSE);
419   // jQuery plugin for query string/history manipulation.
420   drupal_add_js($path . '/jquery.ba-bbq.js', 'module', 'footer', TRUE, TRUE, FALSE);
421   drupal_add_js($path . '/mkdru.theme.js', 'module', 'footer', TRUE, TRUE, FALSE);
422   drupal_add_js($path . '/mkdru.client.js', 'module', 'footer', TRUE, TRUE, FALSE);
423   drupal_add_js(array('mkdru' => $node->mkdru), 'setting');
424   drupal_add_js(array('mkdru' => 
425     array(
426       'settings' => $node->settings,
427     )
428   ), 'setting');
429 }
430
431 /** 
432 * Implements hook_view()
433 */
434 function mkdru_view($node, $teaser = FALSE, $page = FALSE) {
435   $node->content['mkdru_js'] = array(
436     '#value' => theme('mkdru_js', $node), 
437     '#weight' => 0,
438   );
439   $node->content['mkdru_form'] = array(
440     '#value' => theme('mkdru_form'), 
441     '#weight' => 1,
442   );
443   $node->content['mkdru_results'] = array(
444     '#value' => theme('mkdru_results'), 
445     '#weight' => 2,
446   );
447   return $node;
448 }
449
450 /** 
451 * Implements hook_block()
452 */
453 function mkdru_block($op='list', $delta='sources', $edit=array()) {
454   switch ($op) {
455     case 'list':
456       // facet blocks
457       // D6 has no setting for note type visibility, set
458       // the default to limit facet display to this type
459       $visPHP = '<?php
460   if (arg(0) == "node" && is_numeric(arg(1))) {
461     $node = node_load(array("nid" => arg(1)));
462     return $node->type == "mkdru";
463   }
464 ?>';
465
466       // NB: block caching is redundant for static content
467       $blocks['mkdru_sources']['info'] = t('mkdru - source facets');
468       $blocks['mkdru_sources']['cache'] = BLOCK_NO_CACHE;
469       $blocks['mkdru_sources']['visibility'] = 2;
470       $blocks['mkdru_sources']['pages'] = $visPHP;
471       $blocks['mkdru_subjects']['info'] = t('mkdru - subject facets');
472       $blocks['mkdru_subjects']['cache'] = BLOCK_NO_CACHE;
473       $blocks['mkdru_subjects']['visibility'] = 2;
474       $blocks['mkdru_subjects']['pages'] = $visPHP;
475       $blocks['mkdru_authors']['info'] = t('mkdru - author facets');
476       $blocks['mkdru_authors']['cache'] = BLOCK_NO_CACHE;
477       $blocks['mkdru_authors']['visibility'] = 2;
478       $blocks['mkdru_authors']['pages'] = $visPHP;
479       // search blocks
480       $result = db_query("SELECT title, nid FROM {node} WHERE type = 'mkdru';");
481       while ($node = db_fetch_object($result)) {
482         $blocks['mkdru_search_' . $node->nid]['info'] = 
483            t('mkdru - search box for "' . $node->title . '"');
484         $blocks['mkdru_sources']['cache'] = BLOCK_NO_CACHE;
485       };
486       return $blocks;
487
488     case 'view':
489       switch ($delta) {
490         case 'mkdru_sources':
491           $block['subject'] = t('Source');
492           $block['content'] = theme('mkdru_block_facet', 'mkdru-facet-source');
493           return $block;
494         case 'mkdru_subjects':
495           $block['subject'] = t('Subject');
496           $block['content'] = theme('mkdru_block_facet', 'mkdru-facet-subject');
497           return $block;
498         case 'mkdru_authors':
499           $block['subject'] = t('Author');
500           $block['content'] = theme('mkdru_block_facet', 'mkdru-facet-author');
501           return $block;
502     }
503     if (substr($delta, 0, 13) == 'mkdru_search_') {
504       $nid = substr($delta, 13);
505       $block['content'] = theme('mkdru_block_search', $nid, '/node/' . $nid);
506       return $block;
507     }
508   }
509 }