/[drupal]/contributions/modules/z3950/z3950.module
ViewVC logotype

Contents of /contributions/modules/z3950/z3950.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.5 - (show annotations) (download) (as text)
Sat Sep 1 19:57:47 2007 UTC (2 years, 2 months ago) by douggreen
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
Changes since 1.4: +2 -2 lines
File MIME type: text/x-php
change api op from details to detail
1 <?php
2 /* $Id: z3950.module,v 1.4 2007/09/01 19:49:13 douggreen Exp $ */
3
4 /**
5 * @file
6 * Lets users search metadata in z39.50 compliant servers.
7 */
8
9 /**
10 * YAZ configuration defaults
11 * See http://www.loc.gov/marc/bibliographic/ecbdhome.html
12 * See http://www.loc.gov/z3950/lcserver.html
13 */
14 define('YAZ_DEFAULT_SYNTAX', '1.2.840.10003.5.10');
15 define('YAZ_DEFAULT_SERVER', 'z3950.loc.gov:7090/Voyager'); // jenson.stanford.edu:2210/unicorn
16
17 /**
18 * Implementation of hook_menu()
19 */
20 function z3950_menu($may_cache = true) {
21 $items = array();
22 $items['z3950'] = array(
23 'access' => 'search in z3950 servers',
24 'page callback' => 'z3950_get'
25 );
26 $items['admin/settings/z3950'] = array(
27 'title' => 'z3950',
28 'description' => 'Set z3950 configuration options',
29 'page callback' => 'drupal_get_form',
30 'page arguments' => 'z3950_admin_settings',
31 'access arguments' => array('administer site configuration'),
32 );
33 return $items;
34 }
35
36 /**
37 * Implementation of hook_perm().
38 */
39 function z3950_perm() {
40 return array('search in z3950 servers');
41 }
42
43 /**
44 * Implementation of hook_settings().
45 */
46 function z3950_admin_settings() {
47 if (!_z3950_install_check()) {
48 return;
49 }
50
51 $form = array();
52 $form['z3950_zurls'] = array(
53 '#type' => 'textarea',
54 '#title' => t('z39.50 zURLS'),
55 '#default_value' => variable_get('z3950_zurls', YAZ_DEFAULT_SERVER),
56 '#description' => t('Valid zURLs to perform searches of metadata. Add one zURL per row. zURL is a string that takes the form host[:port][/database]. If port is omitted, port 210 is used. If database is omitted Default is used.'),
57 );
58 $form['z3950_syntax'] = array(
59 '#type' => 'select',
60 '#title' => t('Record Syntax'),
61 '#default_value' => variable_get('z3950_syntax', YAZ_DEFAULT_SYNTAX),
62 '#options' => _z3950_syntax_options(),
63 '#description' => t('Specifies the preferred record syntax for retrieval (<a href="@url">see also</a>)', array('@url' => 'http://www.loc.gov/z3950/agency/defns/oids.html#5')),
64 );
65 return system_settings_form($form);
66 }
67
68 /**
69 * Check to make sure that the yaz functions are installed in php,
70 * and if not, display an error
71 */
72 function _z3950_install_check() {
73 if (!function_exists('yaz_connect')) {
74 drupal_set_message(t('You must compile Php with <a href="@yazurl">YAZ</a> and enable the YAZ extension in <a href="@phpurl">php.ini</a> file.', array('@yazurl' => 'http://www.php.net/ref.yaz', '@phpurl' => 'http://www.php.net/configuration#configuration.file')), 'error');
75 return FALSE;
76 }
77 return TRUE;
78 }
79
80 /**
81 * Return list of the z39.50 syntax options
82 * See http://www.loc.gov/z3950/agency/defns/oids.html
83 */
84 function _z3950_syntax_options() {
85 $options = array('' => t('-- Select One --'));
86 foreach (_z3950_load_syntax_files() as $info) {
87 $options += $info['options'];
88 }
89 return $options;
90 }
91
92 function _z3950_get_syntax($id, $pos) {
93 $syntax = yaz_record($id, $pos, 'syntax');
94 $modules = _z3950_load_syntax_files();
95 if (isset($modules[$syntax])) {
96 return $modules[$syntax];
97 }
98 else {
99 $msg = t('z39.50 search invalid %syntax.', array('%syntax' => $syntax));
100 watchdog('search', $msg, WATCHDOG_WARNING);
101 drupal_set_message($msg);
102 }
103 }
104
105 function _z3950_load_syntax_files() {
106 static $syntax_info;
107 if (!isset($syntax_info)) {
108 $syntax_info = array();
109 $path = drupal_get_path('module', 'z3950') .'/syntax';
110 $files = drupal_system_listing('z3950_.*\.inc$', $path, 'name', 0);
111 foreach ($files as $file) {
112 require_once('./'. $file->filename);
113 $function = $file->name;
114 if (function_exists($function)) {
115 $info = call_user_func($function, 'info');
116 $info['function'] = $function;
117 $syntax_info[$info['value']] = $info;
118 }
119 }
120 }
121 return $syntax_info;
122 }
123
124 /**
125 * Implementation of hook_search().
126 */
127 function z3950_search($op = 'search', $keys = null) {
128 if (function_exists('yaz_connect') && user_access('search in z3950 servers')) {
129 switch ($op) {
130 case 'name':
131 return t('Metadata');
132 case 'search':
133 return _z3950_search($keys);
134 }
135 }
136 }
137
138 /**
139 * internal implementation of hook_search op=$search
140 */
141 function _z3950_search($keys) {
142 // get list of available servers
143 if ($servers = _z3950_get_servers()) {
144 // parse the search keys into it's form elements,
145 if (preg_match('/(.*)server:(.*)/', $keys, $match)) {
146 // replace the keys (which was keys and server) with just the keys
147 $keys = $match[1];
148
149 // remove unused servers from the list of available servers
150 $valid_servers = drupal_map_assoc(explode(',', $match[2]));
151 foreach ($servers as $server_index => $server) {
152 if (!isset($valid_servers[$server_index])) {
153 unset($servers[$server_index]);
154 }
155 }
156 }
157
158 // define the CCL fields the is used by yaz_ccl_conf
159 $fields = array(
160 'ti' => '1=4', // title
161 'au' => '1=1', // author
162 );
163
164 // setup the YAZ search on each server
165 $ids = array();
166 foreach ($servers as $server_index => $server) {
167 // connect to the yaz server
168 $id = $ids[$server_index] = yaz_connect($server);
169
170 // tell the yaz server the preferred syntax
171 yaz_syntax($id, variable_get('z3950_syntax', YAZ_DEFAULT_SYNTAX));
172
173 // parse the query and setup the search
174 yaz_ccl_conf($id, $fields);
175 if (yaz_ccl_parse($id, $keys, $cclresult)) {
176 if (!yaz_search($id, 'rpn', $cclresult['rpn'])) {
177 unset($ids[$server_index]);
178 }
179 }
180 else {
181 // display the error message, with server name (if more than one server)
182 $error = $cclresult['errorstring'];
183 if (count($servers) > 1) {
184 $error .= '('. $servers[$server_index] .')';
185 }
186 drupal_set_message($error, 'error');
187
188 // on failure, remove this server from the waiting list
189 unset($ids[$server_index]);
190 }
191 }
192
193 // if there are any available servers that didn't fail, wait for, then process results
194 if (count($ids)) {
195 // wait for all YAZ servers to respond, then process results
196 $options = array('timeout' => 30);
197 if (yaz_wait($options)) {
198 // count the number of results
199 $server_hits = array();
200 $server_parts = array();
201 $server_pos = array();
202 $total_hits = 0;
203 foreach ($ids as $server_index => $id) {
204 // check for error and get number of 'hits' (results) from this server
205 $error = yaz_error($id);
206 if (empty($error)) {
207 $server_hits[$server_index] = yaz_hits($id);
208
209 // if this server has any results
210 if (!empty($server_hits[$server_index])) {
211 // keep track of total number of hits, for use in the pager
212 $total_hits += $server_hits[$server_index];
213
214 // set the starting position
215 $server_pos[$server_index] = 1;
216
217 // parse this server:port/database into it's three parts
218 preg_match('/^([^\/|^:]+):*([0-9]*)\/*(.*)/i', $servers[$server_index], $zurl_parts);
219 $server_part = $zurl_parts[1];
220 if ($server_part == 'localhost' or $server_part == '127.0.0.1') {
221 $server_part = $_SERVER['SERVER_NAME'];
222 }
223 $server_parts[$server_index] = $server_part;
224 }
225 }
226 else {
227 // display the error message, with server name (if more than one server)
228 if (count($servers) > 1) {
229 $error .= ' ('. $servers[$server_index] .')';
230 }
231 drupal_set_message($error, 'error');
232 }
233 }
234
235 if ($total_hits > 0) {
236 // initialize pager variables
237 $total_pos = pager_init(10, 0, $total_hits);
238 $total_per_server = $total_pos / count($ids);
239
240 // initialize the current position in each server
241 // @TODO: there's a faster way to do this, this is just the easy way!
242 // @TODO: test this around the edge cases, is it losing any results?
243 while ($total_pos > 1) {
244 foreach ($ids as $server_index => $id) {
245 if ($server_pos[$server_index] < $server_hits[$server_index]) {
246 $server_pos[$server_index] ++;
247 $total_pos --;
248 }
249 }
250 }
251
252 // process results from search server, evenly mixing results from each server
253 $limit = min($total_hits, 10);
254 $find = array();
255 while (count($find) <= $limit) {
256 foreach ($ids as $server_index => $id) {
257 // loop through the results from this server
258 if ($server_pos[$server_index] < $server_hits[$server_index]) {
259 // store the position in a scalar variable to make code easier to read
260 $pos = $server_pos[$server_index];
261
262 // store the database name for use in the source string below
263 $rec_database = yaz_record($id, $pos, 'database');
264
265 // process the result based on the z39.50 syntax
266 if ($syntax = _z3950_get_syntax($id, $pos)) {
267 // get the syntax summary for this record
268 $summary = call_user_func($syntax['function'], 'summary', $id, $pos);
269
270 // add the link to the summary
271 $summary['link'] = url('z3950/'. $server_index .'/'. $pos .'/'. $keys);
272
273 // add the syntax and source to the summary
274 if (!isset($summary['extra'])) {
275 $summary['extra'] = array();
276 }
277 $summary['extra']['syntax'] = t('syntax: %syntax', array('%syntax' => $syntax['value']));
278 $summary['extra']['source'] = t('source: <a href="@url">@url</a> (%database)', array('@url' => 'http://'. $server_parts[$server_index], '%database' => $rec_database));
279
280 // save this summary, and don't process it again
281 $find[] = $summary;
282 $server_pos[$server_index] ++;
283 }
284 }
285 }
286 }
287 return $find;
288 }
289 }
290 }
291 }
292 }
293
294
295 /**
296 * Implementation of hook_form_alter().
297 */
298 function z3950_form_alter(&$form, $form_state, $form_id) {
299 // alter the z39.50 search form
300 if ($form_id == 'search_form' && arg(1) == 'z3950') {
301 // get list of available servers
302 $options = _z3950_get_servers();
303 $default_values = array_keys($options);
304
305 // if search keys have already been used, parse them into form elements
306 $keys = $form['basic']['inline']['keys']['#default_value'];
307 if (preg_match('/(.*)server:(.*)/', $keys, $match)) {
308 // replace the keys (which was keys and server) with just the keys
309 $form['basic']['inline']['keys']['#default_value'] = $match[1];
310
311 // remove unused servers from the list of available servers
312 $valid_servers = drupal_map_assoc(explode(',', $match[2]));
313 foreach ($default_values as $server_index => $server) {
314 if (!isset($valid_servers[$server_index])) {
315 unset($default_values[$server_index]);
316 }
317 }
318 }
319
320 // add checkboxes for available servers to the basic form
321 if (count($options) > 1) {
322 $form['basic']['server'] = array(
323 '#type' => 'checkboxes',
324 '#options' => $options,
325 '#default_value' => $default_values,
326 );
327 }
328
329 // add help text to the basic form
330 $form['basic']['#description'] = t('enter the z39.50 search query (<A HREF="@url">see also</A>) and select the servers to search', array('@url' => 'http://www.indexdata.dk/yaz/doc/tools.tkl#CCL'));
331
332 // add advanced form
333 // @TODO
334
335 // add validate hook to process these new form elements
336 $form['#validate'][] = 'z3950_search_validate';
337 }
338 }
339
340 /**
341 * Form API callback for the search form. Registered in node_form_alter().
342 *
343 * hook_form_validate() is used solely to set the 'processed_keys' form
344 * value for the basic search form.
345 */
346 function z3950_search_validate($form, &$form_state) {
347 if (isset($form_state['values']['server'])) {
348 $keys = $form_state['values']['processed_keys'];
349 $keys .= ' server:'. implode(',', $form_state['values']['server']);
350 form_set_value($form['basic']['inline']['processed_keys'], $keys, $form_state);
351 }
352 }
353
354 /**
355 * Return the Page results for one search item
356 * This page is linked from each record in the search results
357 */
358 function z3950_get() {
359 // The URL from the search results is made from the server-index/position/keys
360 $server_index = arg(1);
361 $pos = arg(2);
362 $keys = arg(3);
363
364 $servers = _z3950_get_servers();
365 if ($servers && is_numeric($server_index) && is_numeric($pos)) {
366 // connect to the yaz server
367 $id = yaz_connect($servers[$server_index]);
368
369 // tell the yaz server the preferred syntax
370 yaz_syntax($id, variable_get('z3950_syntax', YAZ_DEFAULT_SYNTAX));
371
372 // setup the search
373 if (yaz_search($id, 'rpn', $keys)) {
374 // wait for YAZ server to respond, then display page results
375 $options = array('timeout' => 30);
376 if (yaz_wait($options)) {
377 // check for errors, then display page results
378 $error = yaz_error($id);
379 if (empty($error)) {
380 // display the page results based on the z39.50 syntax
381 if ($syntax = _z3950_get_syntax($id, $pos)) {
382 if ($record = call_user_func($syntax['function'], 'detail', $id, $pos)) {
383 return theme('yaz_record', $record);
384 }
385 }
386 }
387 else {
388 drupal_set_message(t('z39.50 search error: %error in server %server', array('%error' => $error, '%server' => $servers[$server_index])));
389 }
390 }
391 }
392 }
393
394 // if we get here, there was some error, so go back to the search page
395 drupal_goto('search/z3950/'. $keys);
396 }
397
398 function _z3950_get_servers() {
399 if ($servers = variable_get('z3950_zurls', YAZ_DEFAULT_SERVER)) {
400 // split the servers into an array, and trim leading and trailing white space
401 return preg_replace('/^\s+|\s+$/', '', split("/\r\n|\n|\r/", $servers));
402 }
403 drupal_set_message(t('No servers defined in the <a href="@url">settings</a>', array('@url' => url('admin/settings/z3950'))), 'error');
404 }
405
406 /**
407 * Implementation of hook_theme().
408 */
409 function z3950_theme() {
410 return array('yaz_record' => array());
411 }
412
413 /**
414 * Theme function to display one record in the search results
415 */
416 function theme_yaz_record($result) {
417 $output .= '<div><pre>';
418 $output .= empty($result) ? t('No records found.') : $result;
419 $output .= '</pre></div>';
420 return $output;
421 }
422
423 /**
424 * Initialize the global pager variables without using the pager_query
425 * This should be in pager.inc, but isn't
426 */
427 function pager_init($limit = 10, $element = 0, $count) {
428 global $pager_page_array, $pager_total, $pager_total_items;
429 $page = isset($_GET['page']) ? $_GET['page'] : '';
430
431 // Convert comma-separated $page to an array, used by other functions.
432 $pager_page_array = explode(',', $page);
433
434 // calculate the total of pages as ceil(items / limit).
435 $pager_total_items[$element] = $count;
436 $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
437 $pager_page_array[$element] = max(0, min((int)$pager_page_array[$element], ((int)$pager_total[$element]) - 1));
438
439 // return the current position
440 return $pager_page_array[$element] * $limit;
441 }

  ViewVC Help
Powered by ViewVC 1.1.2