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