f1dcfc603e8f9fc08d431ec632d1c3c1eec04db0
[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       'base'        => 'mkdru',
15       'description' => t("Metasearch interface for Z39.50/SRU and other targets via a Pazpar2/Service Proxy backend"),
16     )
17   );
18 }
19
20 /**
21  * Implements hook_search_info()
22  */
23 function mkdru_search_info() {
24   return array(
25     'title'               => 'Meta search',
26     'path'                => 'meta',
27     'conditions_callback' => 'mkdru_search_conditions_callback',
28   );
29 }
30
31 /**
32  * Implements hook_search_page()
33  */
34 function mkdru_search_page($results) {
35   $output['prefix']['#markup'] = theme('mkdru_results');
36   $output['suffix']['#markup'] = '';
37   return $output;
38 }
39
40 /**
41  * Implements hook_ding_facetbrowser()
42  */
43 function mkdru_ding_facetbrowser() {
44         $results             = new stdClass();
45   $results->facets     = array();
46   $results->show_empty = TRUE; # Show an empty facetbrowser block, even if search didn't return any results
47   return $results;
48 }
49
50 /**
51  * Search callback function that is invoked by search_view()
52  */
53 function mkdru_search_conditions_callback($keys) {}
54
55 /**
56  * Implement hook_search_execute()
57  */
58 function mkdru_search_execute($keys = NULL, $conditions = NULL) {
59   $path = drupal_get_path('module', 'mkdru');
60   // Include client library.
61   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') . '/pz2.js',
62     array('type' => 'file', 'scope' => 'footer'));
63   drupal_add_library('overlay', 'jquery-bbq');
64   drupal_add_js($path . '/recipe.js',
65     array('type' => 'file', 'scope' => 'footer'));
66   drupal_add_js($path . '/mkdru.theme.js',
67     array('type' => 'file', 'scope' => 'footer'));
68   drupal_add_js($path . '/mkdru.client.js',
69     array('type' => 'file', 'scope' => 'footer'));
70   drupal_add_js(array('mkdru' =>
71     array(
72       'use_sessions' => variable_get('use_sessions', '1'),
73       'pz2_path'     => variable_get('pz2_path', '/pazpar2/search.pz2'),
74       'sp_user'      => variable_get('sp_user', ''),
75       'sp_pass'      => variable_get('sp_pass', ''),
76       'query'        => $keys,
77     )
78   ), 'setting');
79
80   return array();
81 }
82
83 /**
84 * Implements hook_permission()
85 */
86 function mkdru_permission() {
87   return array(
88     'administer metasearch interfaces' => array(
89       'title' => t('Administer Pazpar2 metasearch integration'),
90     ),
91     'create metasearch interface' => array(
92       'title' => t('Create metasearch interface'),
93     ),
94     'edit any metasearch interface' => array(
95       'title' => t('Edit any metasearch interface'),
96     ),
97     'edit own metasearch interface' => array(
98       'title' => t('Edit own metasearch interface'),
99     ),
100   );
101 }
102
103 /**
104 * Implements hook_node_access()
105 */
106 function mkdru_node_access($node, $op, $account) {
107   if ($op == 'create') {
108     // Only users with permission to do so may create this node type.
109     return user_access('create metasearch interface', $account);
110   }
111
112   // Users who create a node may edit or delete it later, assuming they have the
113   // necessary permissions.
114   if ($op == 'update' || $op == 'delete') {
115     if (user_access('edit own metasearch interface', $account) && ($account->uid == $node->uid)) {
116       return TRUE;
117     }
118     elseif (user_access('edit any metasearch interface', $account)) {
119       return TRUE;
120     }
121   }
122 }
123
124 /**
125 * Implements hook_menu()
126 */
127 function mkdru_menu() {
128   $items['admin/config/search/mkdru'] = array(
129     'title' => 'Configure Pazpar2 metasearch integration',
130     'description' => 'Settings for mkdru.',
131     'page callback' => 'drupal_get_form',
132     'page arguments' => array('mkdru_admin_settings'),
133     'access arguments' => array('administer site configuration'),
134     'type' => MENU_NORMAL_ITEM,
135     'file' => 'mkdru.admin.inc',
136   );
137   $items['admin/config/search/mkdru/defaults'] = array(
138     'title' => 'Default settings for Pazpar2 metasearch integration',
139     'description' => 'Settings for mkdru.',
140     'page callback' => 'drupal_get_form',
141     'page arguments' => array('mkdru_admin_settings'),
142     'access arguments' => array('administer site configuration'),
143     'type' => MENU_LOCAL_TASK,
144     'file' => 'mkdru.admin.inc',
145   );
146   return $items;
147 }
148
149 /**
150 * Implementation of hook_init()
151 */
152 function mkdru_init() {
153   // Applies our module specific CSS to all pages. This works best because
154   // all CSS is aggregated and cached so we reduce the number of HTTP 
155   // requests and the size is negligible.
156   drupal_add_css(drupal_get_path('module', 'mkdru') .'/mkdru.css');
157 }
158
159
160
161 // Config form common to node and settings
162 function mkdru_settings_form($form, &$form_state) {
163 //   dpm($form);
164   dpm($form_state);
165 //   dpm($settings);
166   if (isset($form_state['values']['settings'])) {
167     $settings = $form_state['build_info']['args']['settings'];
168   }
169   else if (isset($form_state['build_info']['args']['settings'])) {
170     $settings = $form_state['build_info']['args']['settings'];
171   }
172   else {
173     $settings = variable_get('mkdru_defaults');
174   }
175
176   $form['settings'] = array(
177     '#tree' => TRUE,
178   );
179
180   $form['settings']['title'] = array(
181     '#type' => 'textfield',
182     '#title' => t('Search title'),
183     '#required' => FALSE,
184     '#default_value' => $settings['title'],
185     '#weight' => -5
186   );
187
188   $form['settings']['pz2_path'] = array(
189     '#type' => 'textfield',
190     '#title' => t('Pazpar2/Service Proxy path'),
191     '#description' => t('Path that takes Pazpar2 commands via HTTP'),
192     '#required' => TRUE,
193     '#default_value' => $settings['pz2_path'],
194   );
195   $form['settings']['use_sessions'] = array(
196     '#type' => 'checkbox',
197     '#title' => t('Session handling'),
198     '#description' => t('Disable for use with Service Proxy'),
199     '#default_value' => $settings['use_sessions'],
200   );
201
202   $form['settings']['sp']  = array(
203     '#type' => 'fieldset',
204     '#title' => t('Service Proxy specific settings'),
205     '#collapsible' => TRUE,
206     '#collapsed' => TRUE
207   );
208   $form['settings']['sp']['user'] = array(
209     '#type' => 'password',
210     '#title' => t('Service Proxy username'),
211     '#description' => t('Service Proxy username'),
212     '#required' => FALSE,
213     '#default_value' => $settings['sp']['user'],
214   );
215   $form['settings']['sp']['pass'] = array(
216     '#type' => 'textfield',
217     '#title' => t('Service Proxy password'),
218     '#description' => t('Service Proxy password'),
219     '#required' => FALSE,
220     '#default_value' => $settings['sp']['pass'],
221   );
222
223   $form['settings']['facets']  = array(
224     '#type' => 'fieldset',
225     '#title' => t('Facet settings'),
226     // Set up the wrapper so that AJAX will be able to replace the fieldset.
227     '#prefix' => '<div id="mkdru-facets-form-wrapper">', 
228     '#suffix' => '</div>',
229     '#collapsible' => TRUE,
230     '#collapsed' => FALSE
231   );
232   if (empty($form_state['facet_names'])) {
233     $form_state['facet_names'] = array('source', 'author', 'subject');
234   }
235   foreach ($form_state['facet_names'] as $facet) {
236     $form['settings']['facets'][$facet]  = array(
237       '#type' => 'fieldset',
238       '#title' => $facet . ' ' . t('facet'),
239       '#collapsible' => TRUE,
240       '#collapsed' => FALSE
241     );
242     $form['settings']['facets'][$facet]['displayName'] = array(
243       '#type' => 'textfield',
244       '#title' => t('Facet name to display in UI'),
245       '#required' => TRUE,
246       '#default_value' => $settings['facets'][$facet]['displayName'],
247     );
248     $form['settings']['facets'][$facet]['pz2Name'] = array(
249       '#type' => 'textfield',
250       '#title' => t('Name of termlist in Pazpar2'),
251       '#required' => TRUE,
252       '#default_value' => $settings['facets'][$facet]['pz2Name'],
253     );
254     $form['settings']['facets'][$facet]['limiter'] = array(
255       '#type' => 'textfield',
256       '#title' => t('Query limiter string'),
257       '#default_value' => $settings['facets'][$facet]['limiter'],
258       '#size' => 5,
259     );
260     $form['settings']['facets'][$facet]['max'] = array(
261       '#type' => 'textfield',
262       '#title' => t('Number of terms to display'),
263       '#required' => TRUE,
264       '#default_value' => $settings['facets'][$facet]['max'],
265       '#size' => 3,
266       '#maxlength' => 3,
267     );
268     $form['settings']['facets'][$facet]['orderWeight'] = array(
269       '#type' => 'textfield',
270       '#title' => t('Facet weight'),
271       '#default_value' => $settings['facets'][$facet]['orderWeight'],
272       '#size' => 3,
273       '#maxlength' => 3,
274     );
275   }
276   $form['new_facet']  = array(
277     '#type' => 'fieldset',
278     '#title' => t('Add new facet...'),
279     '#tree' => TRUE,
280     '#collapsible' => TRUE,
281     '#collapsed' => FALSE
282   );
283   $form['new_facet']['canonical'] = array(
284     '#type' => 'textfield',
285     '#title' => t('Canonical name of new facet'),
286   );
287   $form['new_facet']['button'] = array(
288     '#type' => 'submit',
289     '#value' => t('Add facet'),
290     '#description' => t('Configure additional facets based on Pazpar2/SP termlists'),
291     '#weight' => 1,
292     '#submit' => array('mkdru_add_facet_form'),
293     '#ajax' => array(
294       'callback' => 'mkdru_add_facet_callback', 
295       'wrapper' => 'mkdru-facets-form-wrapper',
296     ),
297   );
298   return $form;
299 }
300
301
302 // Node config
303 /**
304 * Implements hook_form()
305 */
306 function mkdru_form(&$node, &$form_state) {
307 // Can't use the node type's title since
308 // merging AJAX forms is currently beyond me.
309 //   $type = node_type_get_type($node);
310 //   Title would be: check_plain($type->title_label);
311   if (isset($node->mkdru->settings)) {
312     // Second decode parameter indicates associative array
313     $form_state['build_info']['args']['settings'] = json_decode($node->mkdru->settings, TRUE);
314   }
315   return drupal_retrieve_form('mkdru_settings_form', $form_state);
316 }
317
318 function mkdru_add_facet_form($form, &$form_state) {
319   $form_state['facet_names'][] = $form_state['values']['new_facet']['canonical'];
320   $form_state['rebuild'] = TRUE;
321 }
322
323 function mkdru_add_facet_callback($form, $form_state) {
324   return $form['settings']['facets'];
325 }
326
327 /**
328 * Implements hook_validate()
329 */
330 function mkdru_validate($node) {
331 // TODO: validation
332 //   if (!is_numeric($node->source_max)) {
333 //     form_set_error('source_max', t('Please enter a number.'));
334 //   }
335 }
336
337 /**
338 * Implements hook_insert().
339 */
340 function mkdru_insert($node) {
341   $fields = array('nid' => $node->nid, 'vid' => $node->vid,
342             'settings' => json_encode($node->settings));
343   db_insert('mkdru')->fields($fields)->execute();
344 }
345
346 /**
347 * Implements hook_update().
348 */
349 function mkdru_update($node) {
350   if ($node->revision) {
351     // New revision; treat it as a new record.
352     mkdru_insert($node);
353   }
354   else {
355     db_update('mkdru')
356       ->condition('vid', $node->vid)
357       ->fields(array('settings' => json_encode($node->settings)))
358       ->execute();
359   }
360 }
361
362 /**
363  * Implements hook_node_revision_delete()
364  */
365 function mkdru_node_revision_delete($node) {
366   db_delete('mkdru')
367     ->condition('vid', $node->vid)
368     ->execute();
369 }
370
371 /**
372  * Implements hook_delete()
373  */
374 function mkdru_delete($node) {
375   // Deleting by nid covers all revisions.
376   db_delete('mkdru')
377     ->condition('nid', $node->nid)
378     ->execute();
379 }
380
381
382
383 // Node rendering
384 /**
385 * Implements hook_load()
386 */
387 function mkdru_load($nodes) {
388   $result = db_query('SELECT * FROM {mkdru} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
389   foreach ($result as $record) {
390     $nodes[$record->nid]->mkdru = $record;
391   }
392 }
393
394 /**
395 * Implements hook_theme().
396 */
397 function mkdru_theme() {
398   return array(
399     'mkdru_form' => array(
400       'template' => 'mkdru-form',
401       'variables' => array(),
402     ),
403     'mkdru_results' => array(
404       'template' => 'mkdru-results',
405       'variables' => array(),
406     ),
407     'mkdru_js' => array(
408       'variables' => array('node' => NULL),
409     ),
410     'mkdru_block_search' => array(
411       'template' => 'mkdru-block-search',
412       'variables' => array('nid' => NULL, 'path' => NULL),
413     ),
414     'mkdru_block_facet' => array(
415       'template' => 'mkdru-block-facet',
416       'variables' => array('class' => NULL)
417     )
418   );
419 }
420
421 /**
422 * Theme function to include Javascript search client and deps
423 */
424 function theme_mkdru_js(&$variables) {
425   $path = drupal_get_path('module', 'mkdru');
426   // Pazpar2 client library
427   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') . '/pz2.js', array(
428     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
429   // jQuery plugin for query string/history manipulation.
430   drupal_add_library('system', 'jquery.bbq');
431   drupal_add_js($path . '/mkdru.theme.js', array(
432     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
433   drupal_add_js($path . '/mkdru.client.js', array(
434     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
435   drupal_add_js(array('mkdru' => $variables['node']->mkdru), 'setting');
436 }
437
438 /** 
439 * Implements hook_view()
440 */
441 function mkdru_view($node, $view_mode) {
442   if ($view_mode == 'full') {
443     $node->content['mkdru_js'] = array(
444       '#markup' => theme('mkdru_js', array('node' => $node)), 
445       '#weight' => 0,
446     );
447     $node->content['mkdru_form'] = array(
448       '#markup' => theme('mkdru_form'), 
449       '#weight' => 1,
450     );
451     $node->content['mkdru_results'] = array(
452       '#markup' => theme('mkdru_results'), 
453       '#weight' => 2,
454     );
455   }
456   return $node;
457 }
458
459
460
461 // Blocks
462 /** 
463 * Implements hook_block_info()
464 */
465 function mkdru_block_info() {
466   // facet blocks
467   $facets = variable_get('mkdru_facets');
468   foreach ($facets as $facet) {
469     // NB: block caching is redundant for static content
470     $blocks['mkdru_facet_' . $facet]['info'] = "mkdru - $facet " . t('facet');
471     $blocks['mkdru_facet_' . $facet]['cache'] = DRUPAL_NO_CACHE;
472   }
473
474   // search blocks
475   $result = db_query("SELECT title, nid FROM {node} WHERE type = 'mkdru';");
476   foreach($result as $node) {
477     $blocks['mkdru_search_' . $node->nid]['info'] = 
478         t('mkdru - search box for "' . $node->title . '"');
479     $blocks['mkdru_sources']['cache'] = DRUPAL_NO_CACHE;
480   };
481
482   return $blocks;
483 }
484
485 /** 
486 * Implements hook_block_view()
487 */
488 function mkdru_block_view($delta) {
489   if (substr($delta, 0, 12) == 'mkdru_facet_') {
490     $facet = substr($delta, 12);
491     $block['subject'] = t(ucwords($facet));
492     $block['content'] = theme('mkdru_block_facet',
493                                array('class' => 'mkdru-facet-' . $facet));
494     return $block;
495   }
496   elseif (substr($delta, 0, 13) == 'mkdru_search_') {
497     $nid = substr($delta, 13);
498     $block['content'] = theme('mkdru_block_search',
499         array('nid' => $nid, 'path' => '/node/' . $nid));
500     return $block;
501   }
502 }