Again, typo that should've been fixed.
[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['values']['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']['pz2_path'] = array(
181     '#type' => 'textfield',
182     '#title' => t('Pazpar2/Service Proxy path'),
183     '#description' => t('Path that takes Pazpar2 commands via HTTP'),
184     '#required' => TRUE,
185     '#default_value' => $settings['pz2_path'],
186   );
187   $form['settings']['use_sessions'] = array(
188     '#type' => 'checkbox',
189     '#title' => t('Session handling'),
190     '#description' => t('Disable for use with Service Proxy'),
191     '#default_value' => $settings['use_sessions'],
192   );
193
194   $form['settings']['sp']  = array(
195     '#type' => 'fieldset',
196     '#title' => t('Service Proxy specific settings'),
197     '#collapsible' => TRUE,
198     '#collapsed' => TRUE
199   );
200   $form['settings']['sp']['user'] = array(
201     '#type' => 'password',
202     '#title' => t('Service Proxy username'),
203     '#description' => t('Service Proxy username'),
204     '#required' => FALSE,
205     '#default_value' => $settings['sp']['user'],
206   );
207   $form['settings']['sp']['pass'] = array(
208     '#type' => 'textfield',
209     '#title' => t('Service Proxy password'),
210     '#description' => t('Service Proxy password'),
211     '#required' => FALSE,
212     '#default_value' => $settings['sp']['pass'],
213   );
214
215   $form['settings']['facets']  = array(
216     '#type' => 'fieldset',
217     '#title' => t('Facet settings'),
218     // Set up the wrapper so that AJAX will be able to replace the fieldset.
219     '#prefix' => '<div id="mkdru-facets-form-wrapper">', 
220     '#suffix' => '</div>',
221     '#collapsible' => TRUE,
222     '#collapsed' => FALSE
223   );
224
225   foreach (array_keys($settings['facets']) as $facet) {
226     $form['settings']['facets'][$facet]  = array(
227       '#type' => 'fieldset',
228       '#title' => $facet . ' ' . t('facet'),
229       '#collapsible' => TRUE,
230       '#collapsed' => FALSE
231     );
232     $form['settings']['facets'][$facet]['displayName'] = array(
233       '#type' => 'textfield',
234       '#title' => t('Facet name to display in UI'),
235       '#required' => TRUE,
236       '#default_value' => $settings['facets'][$facet]['displayName'],
237     );
238     $form['settings']['facets'][$facet]['pz2Name'] = array(
239       '#type' => 'textfield',
240       '#title' => t('Name of termlist in Pazpar2'),
241       '#required' => TRUE,
242       '#default_value' => $settings['facets'][$facet]['pz2Name'],
243     );
244     $form['settings']['facets'][$facet]['limiter'] = array(
245       '#type' => 'textfield',
246       '#title' => t('Query limiter string'),
247       '#default_value' => $settings['facets'][$facet]['limiter'],
248       '#size' => 5,
249     );
250     $form['settings']['facets'][$facet]['max'] = array(
251       '#type' => 'textfield',
252       '#title' => t('Number of terms to display'),
253       '#required' => TRUE,
254       '#default_value' => $settings['facets'][$facet]['max'],
255       '#size' => 3,
256       '#maxlength' => 3,
257     );
258     $form['settings']['facets'][$facet]['orderWeight'] = array(
259       '#type' => 'textfield',
260       '#title' => t('Facet weight'),
261       '#default_value' => $settings['facets'][$facet]['orderWeight'],
262       '#size' => 3,
263       '#maxlength' => 3,
264     );
265   }
266
267   $form['new_facet']  = array(
268     '#type' => 'fieldset',
269     '#title' => t('Add new facet...'),
270     '#tree' => TRUE,
271     '#collapsible' => TRUE,
272     '#collapsed' => FALSE
273   );
274   $form['new_facet']['canonical'] = array(
275     '#type' => 'textfield',
276     '#title' => t('Canonical name of new facet'),
277   );
278   $form['new_facet']['button'] = array(
279     '#type' => 'submit',
280     '#value' => t('Add facet'),
281     '#description' => t('Configure additional facets based on Pazpar2/SP termlists'),
282     '#weight' => 1,
283     '#submit' => array('mkdru_add_facet_form'),
284     '#ajax' => array(
285       'callback' => 'mkdru_add_facet_callback', 
286       'wrapper' => 'mkdru-facets-form-wrapper',
287     ),
288   );
289   return $form;
290 }
291
292
293 // Node config
294 /**
295 * Implements hook_form()
296 */
297 function mkdru_form(&$node, &$form_state) {
298   if (isset($node->mkdru->settings)) {
299     // Second decode parameter indicates associative array
300     $form_state['build_info']['args']['settings'] = json_decode($node->mkdru->settings, TRUE);
301   }
302   $form = drupal_retrieve_form('mkdru_settings_form', $form_state);
303   $type = node_type_get_type($node);
304   $form['title'] = array(
305     '#type' => 'textfield',
306     '#title' => check_plain($type->title_label),
307     '#required' => FALSE,
308     '#default_value' => $node->title,
309     '#weight' => -5
310   );
311   return $form;
312 }
313
314 function mkdru_add_facet_form($form, &$form_state) {
315   // TODO: validation
316   $newfacet = $form_state['values']['new_facet']['canonical'];
317   $form_state['values']['settings']['facets'][$newfacet] = NULL;
318   $form_state['rebuild'] = TRUE;
319 }
320
321 function mkdru_add_facet_callback($form, $form_state) {
322   return $form['settings']['facets'];
323 }
324
325 /**
326 * Implements hook_validate()
327 */
328 function mkdru_validate($node) {
329 // TODO: validation
330 //   if (!is_numeric($node->source_max)) {
331 //     form_set_error('source_max', t('Please enter a number.'));
332 //   }
333 }
334
335 /**
336 * Implements hook_insert().
337 */
338 function mkdru_insert($node) {
339   $fields = array('nid' => $node->nid, 'vid' => $node->vid,
340             'settings' => json_encode($node->settings));
341   db_insert('mkdru')->fields($fields)->execute();
342 }
343
344 /**
345 * Implements hook_update().
346 */
347 function mkdru_update($node) {
348   if ($node->revision) {
349     // New revision; treat it as a new record.
350     mkdru_insert($node);
351   }
352   else {
353     db_update('mkdru')
354       ->condition('vid', $node->vid)
355       ->fields(array('settings' => json_encode($node->settings)))
356       ->execute();
357   }
358 }
359
360 /**
361  * Implements hook_node_revision_delete()
362  */
363 function mkdru_node_revision_delete($node) {
364   db_delete('mkdru')
365     ->condition('vid', $node->vid)
366     ->execute();
367 }
368
369 /**
370  * Implements hook_delete()
371  */
372 function mkdru_delete($node) {
373   // Deleting by nid covers all revisions.
374   db_delete('mkdru')
375     ->condition('nid', $node->nid)
376     ->execute();
377 }
378
379
380
381 // Node rendering
382 /**
383 * Implements hook_load()
384 */
385 function mkdru_load($nodes) {
386   $result = db_query('SELECT * FROM {mkdru} WHERE nid IN (:nids)', array(':nids' => array_keys($nodes)));
387   foreach ($result as $record) {
388     $nodes[$record->nid]->mkdru = $record;
389   }
390 }
391
392 /**
393 * Implements hook_theme().
394 */
395 function mkdru_theme() {
396   return array(
397     'mkdru_form' => array(
398       'template' => 'mkdru-form',
399       'variables' => array(),
400     ),
401     'mkdru_results' => array(
402       'template' => 'mkdru-results',
403       'variables' => array(),
404     ),
405     'mkdru_js' => array(
406       'variables' => array('node' => NULL),
407     ),
408     'mkdru_block_search' => array(
409       'template' => 'mkdru-block-search',
410       'variables' => array('nid' => NULL, 'path' => NULL),
411     ),
412     'mkdru_block_facet' => array(
413       'template' => 'mkdru-block-facet',
414       'variables' => array('class' => NULL)
415     )
416   );
417 }
418
419 /**
420 * Theme function to include Javascript search client and deps
421 */
422 function theme_mkdru_js(&$variables) {
423   $path = drupal_get_path('module', 'mkdru');
424   // Pazpar2 client library
425   drupal_add_js(variable_get('pz2_js_path', 'pazpar2/js') . '/pz2.js', array(
426     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
427   // jQuery plugin for query string/history manipulation.
428   drupal_add_library('system', 'jquery.bbq');
429   drupal_add_js($path . '/mkdru.theme.js', array(
430     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
431   drupal_add_js($path . '/mkdru.client.js', array(
432     'type' => 'file', 'scope' => 'footer', 'defer' => TRUE, 'preprocess' => FALSE));
433   drupal_add_js(array('mkdru' => $variables['node']->mkdru), 'setting');
434 }
435
436 /** 
437 * Implements hook_view()
438 */
439 function mkdru_view($node, $view_mode) {
440   if ($view_mode == 'full') {
441     $node->content['mkdru_js'] = array(
442       '#markup' => theme('mkdru_js', array('node' => $node)), 
443       '#weight' => 0,
444     );
445     $node->content['mkdru_form'] = array(
446       '#markup' => theme('mkdru_form'), 
447       '#weight' => 1,
448     );
449     $node->content['mkdru_results'] = array(
450       '#markup' => theme('mkdru_results'), 
451       '#weight' => 2,
452     );
453   }
454   return $node;
455 }
456
457
458
459 // Blocks
460 /** 
461 * Implements hook_block_info()
462 */
463 function mkdru_block_info() {
464   // facet blocks
465   $blocks = array();
466   $facets = variable_get('mkdru_facets', array());
467   foreach ($facets as $facet) {
468     // NB: block caching is redundant for static content
469     $blocks['mkdru_facet_' . $facet]['info'] = "mkdru - $facet " . t('facet');
470     $blocks['mkdru_facet_' . $facet]['cache'] = DRUPAL_NO_CACHE;
471   }
472
473   // search blocks
474   $result = db_query("SELECT title, nid FROM {node} WHERE type = 'mkdru';");
475   foreach($result as $node) {
476     $blocks['mkdru_search_' . $node->nid]['info'] = 
477         t('mkdru - search box for "' . $node->title . '"');
478     $blocks['mkdru_sources']['cache'] = DRUPAL_NO_CACHE;
479   };
480
481   return $blocks;
482 }
483
484 /** 
485 * Implements hook_block_view()
486 */
487 function mkdru_block_view($delta) {
488   if (substr($delta, 0, 12) == 'mkdru_facet_') {
489     $facet = substr($delta, 12);
490     $block['subject'] = t(ucwords($facet));
491     $block['content'] = theme('mkdru_block_facet',
492                                array('class' => 'mkdru-facet-' . $facet));
493     return $block;
494   }
495   elseif (substr($delta, 0, 13) == 'mkdru_search_') {
496     $nid = substr($delta, 13);
497     $block['content'] = theme('mkdru_block_search',
498         array('nid' => $nid, 'path' => '/node/' . $nid));
499     return $block;
500   }
501 }