Merge branch '7.x' of ssh://git.indexdata.com/home/git/pub/mkdru into 7.x
[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   theme('mkdru_js', array('setting' => array('mkdru' => array(
60     'settings' => json_encode(variable_get('mkdru_ding', NULL)),
61     'query' => $keys))));
62   return array();
63 }
64
65 /**
66 * Implements hook_permission()
67 */
68 function mkdru_permission() {
69   return array(
70     'administer metasearch interfaces' => array(
71       'title' => t('Administer Pazpar2 metasearch integration'),
72     ),
73     'create metasearch interface' => array(
74       'title' => t('Create metasearch interface'),
75     ),
76     'edit any metasearch interface' => array(
77       'title' => t('Edit any metasearch interface'),
78     ),
79     'edit own metasearch interface' => array(
80       'title' => t('Edit own metasearch interface'),
81     ),
82   );
83 }
84
85 /**
86 * Implements hook_node_access()
87 */
88 function mkdru_node_access($node, $op, $account) {
89   if ($op == 'create') {
90     // Only users with permission to do so may create this node type.
91     return user_access('create metasearch interface', $account);
92   }
93
94   // Users who create a node may edit or delete it later, assuming they have the
95   // necessary permissions.
96   if ($op == 'update' || $op == 'delete') {
97     if (user_access('edit own metasearch interface', $account) && ($account->uid == $node->uid)) {
98       return TRUE;
99     }
100     elseif (user_access('edit any metasearch interface', $account)) {
101       return TRUE;
102     }
103   }
104 }
105
106 /**
107 * Implements hook_menu()
108 */
109 function mkdru_menu() {
110   $items['admin/config/search/mkdru'] = array(
111     'title' => 'Configure Pazpar2 metasearch integration',
112     'description' => 'Settings for mkdru.',
113     'page callback' => 'drupal_get_form',
114     'page arguments' => array('mkdru_admin_settings'),
115     'access arguments' => array('administer site configuration'),
116     'type' => MENU_NORMAL_ITEM,
117     'file' => 'mkdru.admin.inc',
118   );
119   $items['admin/config/search/mkdru_ding'] = array(
120     'title' => 'Pazpar2 Metasearch Ding Integration',
121     'description' => 'Search settings for mkdru instance integrated into Ding.',
122     'page callback' => 'drupal_get_form',
123     'page arguments' => array('mkdru_ding_settings'),
124     'access arguments' => array('administer site configuration'),
125     'type' => MENU_NORMAL_ITEM,
126   );
127   return $items;
128 }
129
130 /**
131 * Implementation of hook_init()
132 */
133 function mkdru_init() {
134   // Applies our module specific CSS to all pages. This works best because
135   // all CSS is aggregated and cached so we reduce the number of HTTP 
136   // requests and the size is negligible.
137   drupal_add_css(drupal_get_path('module', 'mkdru') .'/mkdru.css');
138 }
139
140
141
142 // Config form common to node and settings
143 function mkdru_settings_form($form, &$form_state) {
144   if (isset($form_state['values']['settings'])) {
145     $settings = $form_state['values']['settings'];
146   }
147   else if (isset($form_state['build_info']['args']['settings'])) {
148     $settings = $form_state['build_info']['args']['settings'];
149   }
150   else {
151     $settings = variable_get('mkdru_defaults');
152   }
153
154   $form['settings'] = array(
155     '#tree' => TRUE,
156   );
157
158   $form['settings']['pz2_path'] = array(
159     '#type' => 'textfield',
160     '#title' => t('Pazpar2/Service Proxy path'),
161     '#description' => t('Path that takes Pazpar2 commands via HTTP'),
162     '#required' => TRUE,
163     '#default_value' => $settings['pz2_path'],
164   );
165   $form['settings']['use_sessions'] = array(
166     '#type' => 'checkbox',
167     '#title' => t('Session handling'),
168     '#description' => t('Disable for use with Service Proxy'),
169     '#default_value' => $settings['use_sessions'],
170   );
171
172   $form['settings']['sp']  = array(
173     '#type' => 'fieldset',
174     '#title' => t('Service Proxy specific settings'),
175     '#collapsible' => TRUE,
176     '#collapsed' => TRUE
177   );
178   $form['settings']['sp']['user'] = array(
179     '#type' => 'textfield',
180     '#title' => t('Service Proxy username'),
181     '#description' => t('Service Proxy username'),
182     '#required' => FALSE,
183     '#default_value' => $settings['sp']['user'],
184   );
185   $form['settings']['sp']['pass'] = array(
186     '#type' => 'textfield',
187     '#title' => t('Service Proxy password'),
188     '#description' => t('Service Proxy password'),
189     '#required' => FALSE,
190     '#default_value' => $settings['sp']['pass'],
191   );
192
193   $form['settings']['facets']  = array(
194     '#type' => 'fieldset',
195     '#title' => t('Facet settings'),
196     // Set up the wrapper so that AJAX will be able to replace the fieldset.
197     '#prefix' => '<div id="mkdru-facets-form-wrapper">', 
198     '#suffix' => '</div>',
199     '#collapsible' => TRUE,
200     '#collapsed' => FALSE
201   );
202
203   foreach (array_keys($settings['facets']) as $facet) {
204     $form['settings']['facets'][$facet]  = array(
205       '#type' => 'fieldset',
206       '#title' => $facet . ' ' . t('facet'),
207       '#collapsible' => TRUE,
208       '#collapsed' => FALSE
209     );
210     $form['settings']['facets'][$facet]['displayName'] = array(
211       '#type' => 'textfield',
212       '#title' => t('Facet name to display in UI'),
213       '#required' => TRUE,
214       '#default_value' => $settings['facets'][$facet]['displayName'],
215     );
216     $form['settings']['facets'][$facet]['pz2Name'] = array(
217       '#type' => 'textfield',
218       '#title' => t('Name of termlist in Pazpar2'),
219       '#required' => TRUE,
220       '#default_value' => $settings['facets'][$facet]['pz2Name'],
221     );
222     $form['settings']['facets'][$facet]['limiter'] = array(
223       '#type' => 'textfield',
224       '#title' => t('Query limiter string'),
225       '#default_value' => $settings['facets'][$facet]['limiter'],
226       '#size' => 5,
227     );
228     $form['settings']['facets'][$facet]['multiLimit'] = array(
229       '#type' => 'checkbox',
230       '#title' => t('Allow multiple limits?'),
231       '#default_value' => $settings['facets'][$facet]['multiLimit'],
232     );
233     $form['settings']['facets'][$facet]['max'] = array(
234       '#type' => 'textfield',
235       '#title' => t('Number of terms to display'),
236       '#required' => TRUE,
237       '#default_value' => $settings['facets'][$facet]['max'],
238       '#size' => 3,
239       '#maxlength' => 3,
240     );
241     $form['settings']['facets'][$facet]['orderWeight'] = array(
242       '#type' => 'textfield',
243       '#title' => t('Facet weight'),
244       '#default_value' => $settings['facets'][$facet]['orderWeight'],
245       '#size' => 3,
246       '#maxlength' => 3,
247     );
248     $form['settings']['facets'][$facet]['remove'] = array(
249       '#type' => 'submit',
250       '#value' => t('Remove ') . $facet . t(' facet'),
251       '#mkdru facet' => $facet,
252       '#submit' => array('mkdru_remove_facet'),
253       '#ajax' => array(
254         'callback' => 'mkdru_ajax_facet_callback',
255         'wrapper' => 'mkdru-facets-form-wrapper',
256       ),
257     );
258   }
259
260   $form['new_facet']  = array(
261     '#type' => 'fieldset',
262     '#title' => t('Add new facet...'),
263     '#tree' => TRUE,
264     '#collapsible' => TRUE,
265     '#collapsed' => FALSE
266   );
267   $form['new_facet']['canonical'] = array(
268     '#type' => 'textfield',
269     '#title' => t('Canonical name of new facet'),
270   );
271   $form['new_facet']['button'] = array(
272     '#type' => 'submit',
273     '#value' => t('Add facet'),
274     '#description' => t('Configure additional facets based on Pazpar2/SP termlists'),
275     '#weight' => 1,
276     '#submit' => array('mkdru_add_facet_form'),
277     '#ajax' => array(
278       'callback' => 'mkdru_ajax_facet_callback', 
279       'wrapper' => 'mkdru-facets-form-wrapper',
280     ),
281   );
282   return $form;
283 }
284
285 function mkdru_add_facet_form($form, &$form_state) {
286   // TODO: validation
287   $newfacet = $form_state['values']['new_facet']['canonical'];
288   $form_state['values']['settings']['facets'][$newfacet] = NULL;
289   $form_state['rebuild'] = TRUE;
290 }
291
292 function mkdru_remove_facet($form, &$form_state) {
293   $delfacet = $form_state['clicked_button']['#mkdru facet'];
294   if ($delfacet)
295     unset($form_state['values']['settings']['facets'][$delfacet]);
296   // Block table is not rebuilt like in D6 so we need to remove blocks explicitly
297   // This is a bit preemptive but the block still reappears in block_list if you
298   // decide not to save the facet deletion.
299   db_delete('block')->condition(db_and()
300     ->condition('module', 'mkdru')
301     ->condition('delta', 'mkdru_facet_' . $delfacet . '_' . $form_state['values']['nid'])
302     )->execute();
303   $form_state['rebuild'] = TRUE;
304 }
305
306 function mkdru_ajax_facet_callback($form, &$form_state) {
307   return $form['settings']['facets'];
308 }
309
310
311
312 // Ding config
313 function mkdru_ding_settings($form, &$form_state) {
314   $form_state['build_info']['args']['settings'] = variable_get('mkdru_ding', NULL);
315   $form = drupal_retrieve_form('mkdru_settings_form', $form_state);
316   $form['settings']['#title'] = t('Search settings for DING integration');
317   $form['submit'] = array(
318     '#type' => 'submit',
319     '#value' => 'Save configuration',
320   );
321   return $form;
322 }
323
324 function mkdru_ding_settings_submit($form, &$form_state) {
325   variable_set('mkdru_ding', $form_state['values']['settings']);
326   drupal_set_message(t('The configuration options have been saved.'));
327 }
328
329
330 // Node config
331 /**
332 * Implements hook_form()
333 */
334 function mkdru_form(&$node, &$form_state) {
335   if (isset($node->mkdru->settings)) {
336     // Second decode parameter indicates associative array
337     $form_state['build_info']['args']['settings'] = json_decode($node->mkdru->settings, TRUE);
338   }
339   $form = drupal_retrieve_form('mkdru_settings_form', $form_state);
340   $type = node_type_get_type($node);
341   $form['title'] = array(
342     '#type' => 'textfield',
343     '#title' => check_plain($type->title_label),
344     '#required' => FALSE,
345     '#default_value' => $node->title,
346     '#weight' => -5
347   );
348   return $form;
349 }
350
351 /**
352 * Implements hook_validate()
353 */
354 function mkdru_validate($node) {
355 // TODO: validation
356 //   if (!is_numeric($node->source_max)) {
357 //     form_set_error('source_max', t('Please enter a number.'));
358 //   }
359 }
360
361 /**
362 * Implements hook_insert().
363 */
364 function mkdru_insert($node) {
365   $fields = array('nid' => $node->nid, 'vid' => $node->vid,
366             'settings' => json_encode($node->settings));
367   db_insert('mkdru')->fields($fields)->execute();
368 }
369
370 /**
371 * Implements hook_update().
372 */
373 function mkdru_update($node) {
374   if ($node->revision) {
375     // New revision; treat it as a new record.
376     mkdru_insert($node);
377   }
378   else {
379     db_update('mkdru')
380       ->condition('vid', $node->vid)
381       ->fields(array('settings' => json_encode($node->settings)))
382       ->execute();
383     block_flush_caches();
384   }
385 }
386
387 /**
388  * Implements hook_node_revision_delete()
389  */
390 function mkdru_node_revision_delete($node) {
391   db_delete('mkdru')
392     ->condition('vid', $node->vid)
393     ->execute();
394 }
395
396 /**
397  * Implements hook_delete()
398  */
399 function mkdru_delete($node) {
400   // Deleting by nid covers all revisions.
401   db_delete('mkdru')
402     ->condition('nid', $node->nid)
403     ->execute();
404   // Block table is not rebuilt like in D6 so we need to remove blocks explicitly
405   db_delete('block')->condition(db_and()
406     ->condition('module', 'mkdru')
407     ->condition('delta', '%\_' . $node->nid, 'like')
408     )->execute();
409 }
410
411
412
413 // Node rendering
414 /**
415 * Implements hook_load()
416 */
417 function mkdru_load($nodes) {
418   $result = db_query('SELECT * FROM {mkdru} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
419   foreach ($result as $record) {
420     $nodes[$record->nid]->mkdru = $record;
421   }
422 }
423
424 /**
425 * Implements hook_theme().
426 */
427 function mkdru_theme() {
428   return array(
429     'mkdru_form' => array(
430       'template' => 'mkdru-form',
431       'variables' => array(),
432     ),
433     'mkdru_results' => array(
434       'template' => 'mkdru-results',
435       'variables' => array(),
436     ),
437     'mkdru_js' => array(
438       'variables' => array('setting' => NULL),
439     ),
440     'mkdru_block_search' => array(
441       'template' => 'mkdru-block-search',
442       'variables' => array('nid' => NULL, 'path' => NULL),
443     ),
444     'mkdru_block_facet' => array(
445       'template' => 'mkdru-block-facet',
446       'variables' => array('class' => NULL)
447     )
448   );
449 }
450
451 /**
452 * Theme function to include Javascript search client and deps
453 */
454 function theme_mkdru_js(&$variables) {
455   $path = drupal_get_path('module', 'mkdru');
456   // Pazpar2 client library
457   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') . '/pz2.js', array(
458     'type' => 'external', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
459   // jQuery plugin for query string/history manipulation.
460   drupal_add_library('system', 'jquery.bbq');
461   drupal_add_js($path . '/mkdru.theme.js', array(
462     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
463   drupal_add_js($path . '/mkdru.client.js', array(
464     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
465   drupal_add_js($variables['setting'], 'setting');
466 }
467
468 /** 
469 * Implements hook_view()
470 */
471 function mkdru_view($node, $view_mode) {
472   if ($view_mode == 'full') {
473     $node->content['mkdru_js'] = array(
474       '#markup' => theme('mkdru_js', array('setting' 
475         => array('mkdru' => array('settings' => $node->mkdru->settings)))),
476       '#weight' => 0,
477     );
478     $node->content['mkdru_form'] = array(
479       '#markup' => theme('mkdru_form'), 
480       '#weight' => 1,
481     );
482     $node->content['mkdru_results'] = array(
483       '#markup' => theme('mkdru_results'), 
484       '#weight' => 2,
485     );
486   }
487   return $node;
488 }
489
490
491
492 // Blocks
493 /** 
494 * Implements hook_block_info()
495 */
496 function mkdru_block_info() {
497   $blocks = array();
498   $result = db_query("SELECT title, {mkdru}.nid as nid, settings FROM {node},{mkdru} WHERE {mkdru}.nid = {node}.nid;");
499   foreach($result as $node) {
500     // search blocks
501     $blocks['mkdru_search_' . $node->nid]['info'] = 
502         t('mkdru - search box for "' . $node->title . '"');
503     $blocks['mkdru_search_' . $node->nid]['cache'] = DRUPAL_NO_CACHE;
504     // facet blocks
505     $settings = json_decode($node->settings, TRUE);
506     foreach ($settings['facets'] as $facet_name => $facet) {
507       $key = 'mkdru_facet_' . $facet_name . '_' . $node->nid;
508       $blocks[$key]['info'] = 'mkdru - ' . $facet_name
509         . t(' facet for "') . $node->title . '"';
510       $blocks[$key]['visibility'] = 1;
511       $blocks[$key]['pages'] = 'node/' . $node->nid;
512       $blocks[$key]['cache'] = DRUPAL_CACHE_GLOBAL;
513     }
514   };
515   return $blocks;
516 }
517
518 /** 
519 * Implements hook_block_view()
520 */
521 function mkdru_block_view($delta) {
522   if (substr($delta, 0, 13) == 'mkdru_search_') {
523     $nid = substr($delta, 13);
524     $block['content'] = theme('mkdru_block_search',
525       array('nid' => $nid, 'path' => '/node/' . $nid));
526     return $block;
527   }
528   else if (preg_match('/^mkdru_facet_(.*)_(\d+)$/', $delta, $matches) > 0) {
529     $facet = $matches[1];
530     $nid = $matches[2];
531     $result = db_query("SELECT settings FROM {mkdru} WHERE nid = :nid;", array(':nid' => $nid));
532     $settingsjson = $result->fetchObject()->settings;
533     $settings = json_decode($settingsjson, TRUE);
534     if (isset($settings['facets'][$facet]['displayName'])) {
535       $block['subject'] = $settings['facets'][$facet]['displayName'];
536     }
537     $block['content'] = theme('mkdru_block_facet', array('class' => 'mkdru-facet-' . $facet));
538     return $block;
539   }
540 }