/
opensearch.php
824 lines (693 loc) · 45 KB
/
opensearch.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
<?php
// Project: Web Reference Database (refbase) <http://www.refbase.net>
// Copyright: Matthias Steffens <mailto:refbase@extracts.de> and the file's
// original author(s).
//
// This code is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY. Please see the GNU General Public
// License for more details.
//
// File: ./opensearch.php
// Repository: $HeadURL$
// Author(s): Matthias Steffens <mailto:refbase@extracts.de>
//
// Created: 04-Feb-06, 21:53
// Modified: $Date: 2012-02-28 16:42:42 -0800 (Tue, 28 Feb 2012) $
// $Author$
// $Revision: 1356 $
// This script serves as a (faceless) routing page which takes an OpenSearch query and
// converts the query into a native refbase query which is then passed to 'show.php'.
// More info is given at <http://opensearch.refbase.net/>.
// Returns an OpenSearch response. Supports the CQL query language, i.e. it allows to
// query all global refbase fields (the given index name must match either one of the
// 'set.index' names listed in the 'sru.php' explain response or match a refbase field
// name directly). If no index name is given 'cql.serverChoice' will be searched by
// default.
// Examples for recognized OpenSearch queries:
//
// - ask the server to return an OpenSearch Description file:
// opensearch.php?operation=explain
//
// - find all records where any of the "main fields" contains 'immunology':
// opensearch.php?query=immunology
// opensearch.php?query=immunology&recordSchema=atom
//
// - find all records where the title field contains either 'ecology' or 'diversity' but
// return only three records starting with record number 4:
// opensearch.php?query=title%20any%20ecology%20diversity&startRecord=4&maximumRecords=3
//
// - ask the server to return JSON-formatted search suggestions for authors whose last names
// begin with either 'Mil' or 'Bel':
// opensearch.php?query=author%20any%20Mil%20Bel&recordSchema=json&operation=suggest
// By default, 'opensearch.php' will output OpenSearch Atom XML ('recordSchema=atom') if not
// specified otherwise in the query. Additionally, 'rss', 'srw_dc', 'srw_mods', 'html' and
// 'json' are currently supported as response formats.
// For more info on OpenSearch, see:
// <http://opensearch.org/>
// TODO: - I18n
// - proper parsing of CQL query string (currently, 'opensearch.php' allows only for a limited set of CQL queries)
// - offer support for the boolean CQL operators 'and/or/not' and parentheses
// (both of the above goals would be accomplished by adopting Rob's CQL-PHP parser, see 'includes/cql.inc.php')
// - if no context set & index name are given in the query, we should search the user's preferred list of "main fields" by default! (cql.serverChoice)
// - currently, 'opensearch.php' does not omit the records list in the response if the OpenSearch query did contain 'maximumRecords=0' (as is the case for an SRU query)
// - finish 'opensearch2xhtml.xsl', and serve it when returning Atom XML
// - finish the form-based query builder (function 'showQueryPage()')
// - what should be done with diagnostics when the client has requested html or json?
// - fix '$citeOrder' issues (see notes in 'rss.php' and below)
// - include OpenSearch elements in RSS & HTML output (see examples at <http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_response_elements>)
// - it would be nice if users could somehow pass authentication details with the OpenSearch Query
// - rewrite HTML using divs + CSS
// - see also inline comments labeled with "TODO"
// NOTES: - Currently, the JSON response format is only supported when returning search suggestions
// ('operation=suggest'), i.e. you cannot (yet) retrieve full record data in JSON format
// - ATM, querying of user-specific fields does only work with a user being logged in
// Incorporate some include files:
include 'initialize/db.inc.php'; // 'db.inc.php' is included to hide username and password
include 'includes/header.inc.php'; // include header
include 'includes/footer.inc.php'; // include footer
include 'includes/include.inc.php'; // include common functions
include 'initialize/ini.inc.php'; // include common variables
include 'includes/atomxml.inc.php'; // include functions that deal with Atom XML
include 'includes/opensearch.inc.php'; // include functions that return an OpenSearch response
include 'includes/srwxml.inc.php'; // include functions that deal with SRW XML
include_once 'includes/webservice.inc.php'; // include functions that are commonly used with the refbase webservices
// --------------------------------------------------------------------
// Extract the ID of the client from which the query originated:
// this identifier is used to identify queries that originated from the refbase command line clients ("cli-refbase-1.1", "cli-refbase_import-1.0"),
// from a bookmarklet (e.g., "jsb-refbase-1.0") or from a browser such as Firefox that uses 'opensearch.php' for search suggestions ("sug-refbase_suggest-1.0")
// (note that 'client' parameter has to be extracted *before* the call to the 'start_session()' function, since it's value is required by this function)
if (isset($_REQUEST['client']))
$client = $_REQUEST['client'];
else
$client = "";
// START A SESSION:
// call the 'start_session()' function (from 'include.inc.php') which will also read out available session variables:
start_session(true);
// --------------------------------------------------------------------
// Initialize preferred display language:
// (note that 'locales.inc.php' has to be included *after* the call to the 'start_session()' function)
include 'includes/locales.inc.php'; // include the locales
// --------------------------------------------------------------------
// Extract mandatory parameters passed to the script:
if (isset($_REQUEST['query'])) // contains the keywords to be searched for ('{searchTerms}')
$cqlQuery = $_REQUEST['query'];
else
$cqlQuery = "";
// Extract optional parameters passed to the script:
if (isset($_REQUEST['operation']) AND preg_match("/^(explain|suggest|advanced|CQL)$/i", $_REQUEST['operation']))
$operation = $_REQUEST['operation'];
else
$operation = "";
if (isset($_REQUEST['recordSchema']) AND !empty($_REQUEST['recordSchema'])) // contains the desired response format; currently supports 'atom', 'rss', 'srw_dc', 'srw_mods', 'html' and 'json'
$recordSchema = $_REQUEST['recordSchema'];
else
$recordSchema = "atom";
if (isset($_REQUEST['maximumRecords'])) // contains the desired number of search results (OpenSearch equivalent: '{count}')
$showRows = $_REQUEST['maximumRecords'];
else
$showRows = $_SESSION['userRecordsPerPage']; // get the default number of records per page preferred by the current user
if (isset($_REQUEST['startRecord'])) // contains the offset of the first search result, starting with one (OpenSearch equivalent: '{startIndex}')
$rowOffset = ($_REQUEST['startRecord']) - 1; // first row number in a MySQL result set is 0 (not 1)
else
$rowOffset = ""; // if no value to the 'startRecord' parameter is given, we'll output records starting with the first record in the result set
if (isset($_REQUEST['stylesheet'])) // contains the desired stylesheet to be returned for transformation of XML data
$exportStylesheet = $_REQUEST['stylesheet']; // if the 'stylesheet' parameter was given in the query without a value, this will suppress the default stylesheet
else
$exportStylesheet = "DEFAULT"; // the special keyword "DEFAULT" causes a default stylesheet to be assigned below based on the requested operation and response format
// The following parameters are defined by the OpenSearch Query Syntax specification but aren't supported yet:
// if (isset($_REQUEST['startPage'])) // indicates groups (= pages) of search results, starting with one ('{startPage}'); e.g., if 'maximumRecords=10', 'startPage=3' will cause records 21-30 to be returned
// $pageOffset = ($_REQUEST['startPage']);
// else
// $pageOffset = "";
// if (isset($_REQUEST['language'])) // indicates that the client desires results in the specified language ('{language}')
// $language = ($_REQUEST['language']);
// else
// $language = "";
// if (isset($_REQUEST['outputEncoding'])) // indicates that the client desires results in the specified character encoding ('{outputEncoding}')
// $outputEncoding = ($_REQUEST['outputEncoding']);
// else
// $outputEncoding = "";
// if (isset($_REQUEST['inputEncoding'])) // indicates that query parameters are encoded via the specified character encoding ('{inputEncoding}')
// $inputEncoding = ($_REQUEST['inputEncoding']);
// else
// $inputEncoding = "";
// Extract the view type requested by the user (either 'Mobile', 'Print', 'Web' or ''):
// ('' will produce the default 'Web' output style)
if (isset($_REQUEST['viewType']))
$viewType = $_REQUEST['viewType'];
else
$viewType = "";
// --------------------------------------------------------------------
// Set required variables based on the requested response format:
if (preg_match("/^srw([ _]?(mods|dc))?([ _]?xml)?$/i", $recordSchema)) // if SRW XML is requested as response format
{
if (preg_match("/^srw[ _]?dc/i", $recordSchema))
{
$exportFormat = "SRW_DC XML";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = "srwdc2html.xsl";
}
else
{
$exportFormat = "SRW_MODS XML";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = "srwmods2html.xsl";
}
$displayType = "Export";
$exportContentType = "application/xml";
$citeOrder = "";
}
elseif (preg_match("/^rss([ _]?xml)?$/i", $recordSchema)) // if RSS XML is requested as response format
{
$exportFormat = "RSS XML";
$displayType = "Export";
$exportContentType = "application/rss+xml";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = "";
$citeOrder = ""; // TODO/NOTE: currently, 'rss.php' always sorts records like as if '$citeOrder="creation-date"' was given, i.e. it sorts records such that newly added/edited records get listed top of the list; this means that Atom links to alternate formats (such as HTML or SRW XML) might return different records!
}
elseif (preg_match("/^html$/i", $recordSchema)) // if HTML is requested as response format
{
$exportFormat = ""; // since search results won't be routed thru the 'generateExport()' function, '$exportFormat' will be without effect (which is why we leave it blank)
if (preg_match("/^Mobile$/i", $viewType)) // for Mobile view, we enforce the compact Citation view
$displayType = "Cite";
else
$displayType = ""; // if '$displayType' is empty, 'show.php' will use the default view that's given in session variable 'userDefaultView'
$exportContentType = "text/html";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = "";
$citeOrder = "";
}
elseif (preg_match("/^json$/i", $recordSchema)) // if JSON is requested as response format
{
$exportFormat = "JSON";
$displayType = "Export";
$exportContentType = "application/json";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = "";
$citeOrder = "";
}
else // by default, OpenSearch Atom XML ('atom') is assumed as response format
{
$exportFormat = "Atom XML";
$displayType = "Export";
$exportContentType = "application/atom+xml";
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = ""; // TODO: finish 'opensearch2xhtml.xsl'
$citeOrder = ""; // TODO/NOTE: '$citeOrder="creation-date"' would sort records such that newly added/edited records get listed top of the list, but then Atom links to alternate formats (such as HTML or SRW XML) would be mismatched!
}
// -------------------------------------------------------------------------------------------------------------------
// Handle the special index 'main_fields':
if (!(preg_match("/^suggest$/i", $operation) AND preg_match("/^(html|json)$/i", $recordSchema)) AND (preg_match("/^main_fields( +(all|any|exact|within) +| *(<>|<=|>=|<|>|=) *)/i", $cqlQuery))) // if the 'main_fields' index is used in conjunction with a non-"suggest" operation
$cqlQuery = preg_replace("/^main_fields(?= +(all|any|exact|within) +| *(<>|<=|>=|<|>|=) *)/i", "cql.serverChoice", $cqlQuery); // replace 'main_fields' index (which, ATM, is only supported for search suggestions) with 'cql.serverChoice'
// Parse CQL query:
$searchArray = parseCQL("1.1", $cqlQuery, $operation); // function 'parseCQL()' is defined in 'webservice.inc.php'
// Build SQL WHERE clause:
$query = ""; // NOTE: although we don't supply a full SQL query here, the variable MUST be named '$query' to have function 'appendToWhereClause()' work correctly
if (!empty($searchArray))
appendToWhereClause($searchArray); // function 'appendToWhereClause()' is defined in 'include.inc.php'
// -------------------------------------------------------------------------------------------------------------------
// Check that mandatory parameters have been passed:
// - if 'opensearch.php' was called with 'operation=explain', we'll return an appropriate OpenSearch description document:
if (preg_match("/^explain$/i", $operation))
{
// Use an appropriate default stylesheet:
if ($exportStylesheet == "DEFAULT")
$exportStylesheet = ""; // TODO: create a stylesheet ('opensearchDescription2html.xsl') that's appropriate for the OpenSearch description
// Set the appropriate mimetype & set the character encoding to the one given
// in '$contentTypeCharset' (which is defined in 'ini.inc.php'):
setHeaderContentType("application/opensearchdescription+xml", $contentTypeCharset); // function 'setHeaderContentType()' is defined in 'include.inc.php'
echo openSearchDescription($exportStylesheet); // function 'openSearchDescription()' is defined in 'opensearch.inc.php'
}
// - if 'opensearch.php' was called with 'operation=suggest' and HTML (or JSON) as the requested response format,
// we'll return search suggestions that match the 'WHERE' clause given in '$query':
elseif (preg_match("/^suggest$/i", $operation) AND preg_match("/^(html|json)$/i", $recordSchema))
{
// Set the appropriate mimetype & set the character encoding to the one given
// in '$contentTypeCharset' (which is defined in 'ini.inc.php'):
setHeaderContentType($exportContentType, $contentTypeCharset);
echo searchSuggestions($cqlQuery, $query);
}
// - If 'opensearch.php' was called without any recognized parameters, we'll present a form where a user can build a query:
elseif (!isset($_REQUEST['query']) AND !isset($_REQUEST['recordSchema']) AND !isset($_REQUEST['maximumRecords']) AND !isset($_REQUEST['startRecord']) AND !isset($_REQUEST['stylesheet']))
showQueryPage($operation, $viewType, $showRows, $rowOffset);
// - If 'opensearch.php' was called without any valid (or with incorrect) parameters, we'll return appropriate 'diagnostics':
elseif (empty($cqlQuery))
returnDiagnostic(7, "query"); // required 'query' parameter is missing
// - Currently, no other schemas than OpenSearch Atom XML, SRW_DC XML, SRW_MODS XML, RSS XML, HTML and JSON are supported:
elseif (!preg_match("/^((atom|rss)([ _]?xml)?|srw([ _]?(mods|dc))?([ _]?xml)?|html|json)$/i",$recordSchema))
returnDiagnostic(66, $recordSchema); // unknown record schema
// -------------------------------------------------------------------------------------------------------------------
else // the script was called at least with the required 'query' parameter
{
// Write the current OpenSearch/CQL query into a session variable:
// (this session variable is used by functions 'atomCollection()' and 'citeRecords()' (in 'cite_html.php') to re-establish the original OpenSearch/CQL query;
// function 'atomCollection()' uses the OpenSearch/CQL query to output 'opensearch.php' URLs instead of 'show.php' URLs)
saveSessionVariable("cqlQuery", $cqlQuery); // function 'saveSessionVariable()' is defined in 'include.inc.php'
// Build the correct query URL:
// (we skip unnecessary parameters here since function 'generateURL()' and 'show.php' will use their default values for them)
$queryParametersArray = array("where" => $query,
"submit" => $displayType,
"viewType" => $viewType,
"exportStylesheet" => $exportStylesheet
);
// NOTE: The 'show.php' script allows anonymous users to query the 'cite_key' field (if a valid 'userID' is included in the query URL).
// However, this requires that the cite key is passed in the 'cite_key' URL parameter. Since 'opensearch.php' uses the 'where'
// parameter to pass its query, anonymous querying of the 'cite_key' field currently does not work for 'opensearch.php'. But
// querying of user-specific fields will work if a user is logged in.
if (isset($_SESSION['loginEmail'])) // we only include the 'userID' parameter if the user is logged in
$queryParametersArray["userID"] = $loginUserID; // for user-specific fields (such as the 'cite_key' field), 'show.php' requires the 'userID' parameter
// call 'show.php' (or 'rss.php' in case of RSS XML) with the correct query URL in order to output record details in the requested format:
$queryURL = generateURL("show.php", $exportFormat, $queryParametersArray, false, $showRows, $rowOffset, "", $citeOrder); // function 'generateURL()' is defined in 'include.inc.php'
header("Location: $queryURL");
}
// -------------------------------------------------------------------------------------------------------------------
// Return a diagnostic error message:
function returnDiagnostic($diagCode, $diagDetails)
{
global $recordSchema;
global $exportContentType;
global $contentTypeCharset; // '$contentTypeCharset' is defined in 'ini.inc.php'
global $exportStylesheet;
// Set the appropriate mimetype & set the character encoding to the one given in '$contentTypeCharset':
setHeaderContentType($exportContentType, $contentTypeCharset); // function 'setHeaderContentType()' is defined in 'include.inc.php'
if (preg_match("/^srw([ _]?(mods|dc))?([ _]?xml)?$/i", $recordSchema))
// Return SRW diagnostics (i.e. SRW error information) wrapped into SRW XML ('searchRetrieveResponse'):
echo srwDiagnostics($diagCode, $diagDetails, $exportStylesheet); // function 'srwDiagnostics()' is defined in 'srwxml.inc.php'
// elseif (preg_match("/^html$/i", $recordSchema))
// TODO!
// elseif (preg_match("/^json$/i", $recordSchema))
// TODO!
else
// Return OpenSearch diagnostics (i.e. OpenSearch error information) wrapped into OpenSearch Atom XML:
echo openSearchDiagnostics($diagCode, $diagDetails, $exportStylesheet); // function 'openSearchDiagnostics()' is defined in 'opensearch.inc.php'
}
// -------------------------------------------------------------------------------------------------------------------
// Return search suggestions that match the 'WHERE' clause given in '$query':
//
// NOTE: Currently, if you specify a multi-item field with 'all' as a relation (as in 'keywords all ...'), only the
// first search term is used to generate search suggestions (though the other search terms will be used to
// restrict the list of search suggestions to only those where the queried field contains ALL search terms).
//
// TODO: - should we support the 'maximumRecords' and 'startRecord' URL parameters for search suggestions?
// - search suggestions for the 'location' field (and possibly other fields) should be omitted if the user isn't logged in!
function searchSuggestions($cqlQuery, $query)
{
global $recordSchema;
global $loginUserID;
global $tableRefs, $tableUserData; // defined in 'db.inc.php'
global $connection;
global $client;
// Extract the first field & search pattern from the 'WHERE' clause:
// (these will be used to retrieve search suggestions)
$origSearchSuggestionsField = preg_replace("/^[ ()]*(\w+).*/i", "\\1", $query);
$searchSuggestionsPattern = preg_replace("/.*? (?:RLIKE|[=<>]+) \"?(.+?)\"?(?=( *\) *?)*( +(AND|OR)\b|$)).*/i", "\\1", $query); // see NOTE above
if (preg_match("/^main_fields$/i", $origSearchSuggestionsField)) // fetch search suggestions for all of the user's "main fields"
$searchSuggestionsFieldsArray = preg_split("/ *, */", $_SESSION['userMainFields']); // get the list of "main fields" preferred by the current user
else
$searchSuggestionsFieldsArray = array($origSearchSuggestionsField); // we only need to fetch search suggestions for one field
$outputDataArray = array(); // make sure that the buffer variable is empty
// Retrieve matching search suggestions for each field given in '$searchSuggestionsFieldsArray':
foreach ($searchSuggestionsFieldsArray as $searchSuggestionsField)
{
if (preg_match("/^main_fields$/i", $origSearchSuggestionsField))
$searchSuggestionsQuery = preg_replace("/\bmain_fields\b/i", $searchSuggestionsField, $query); // replace 'main_fields' (which doesn't exist as SQL field name) with the current field
else
$searchSuggestionsQuery = $query;
// Check whether we need to split field values for this field:
if (preg_match("/^(author|keywords|abstract|address|corporate_author|place|editor|language|summary_language|series_editor|area|expedition|notes|location|call_number|created_by|modified_by|user_keys|user_notes|user_groups|related)$/i", $searchSuggestionsField))
$splitValues = true;
else
$splitValues = false;
// Define split patterns for this field:
if (preg_match("/^(author|corporate_author|editor|series_editor)$/i", $searchSuggestionsField))
$splitPattern = " *[;()/]+ *";
elseif (preg_match("/^abstract$/i", $searchSuggestionsField))
$splitPattern = "\s*[,.()/?!]+\s+|\s+[,.()/?!]\s*|\s+-\s+"; // TODO: can (or should) abstracts be splitted in a better way?
elseif (preg_match("/^(place|notes|location|user_notes|user_groups|related)$/i", $searchSuggestionsField))
$splitPattern = " *[;]+ *";
elseif (preg_match("/^(call_number)$/i", $searchSuggestionsField))
$splitPattern = " *[;@]+ *";
else
$splitPattern = " *[,;()/]+ *";
// Produce the list of search suggestions for this field:
// (function 'selectDistinct()' is defined in 'include.inc.php')
$searchSuggestionsArray = selectDistinct($connection,
$tableRefs,
"serial",
$tableUserData,
"record_id",
"user_id",
$loginUserID,
$searchSuggestionsField,
"",
"",
"",
"",
"serial",
"\".+\" AND $searchSuggestionsQuery", // this is a somewhat hacky workaround that works around current limitations in function 'selectDistinct()'
$splitValues,
$splitPattern,
"ARRAY",
$searchSuggestionsPattern,
false);
if (!empty($searchSuggestionsArray))
{
// Prefix each item with an index name and relation:
//
// NOTE: When the user selects a search suggestion in Firefox's search box, Firefox replaces the
// user-entered data in the browser's search field with the chosen search suggestion. This
// removes any CQL index and relation that was entered by the user (e.g. "keywords any ...")
// and 'cql.serverChoice' will be searched instead. Since this would lead to unexpected (or
// zero) results, we prefix all search suggestions with the index name and the '=' relation.
//
// TODO: This will need to be revised if 'cql.serverChoice' is mapped to the user's preferred list
// of "main fields". Even better would be if browsers would support alternate query URLs for
// each suggestion in the completion list.
if (preg_match("/^json$/i", $recordSchema) AND preg_match("/^sug/i", $client)) // e.g. "sug-refbase_suggest-1.0"
$searchSuggestionsArray = preg_replace('/^/', "$searchSuggestionsField = ", $searchSuggestionsArray);
$outputDataArray = array_merge($outputDataArray, $searchSuggestionsArray); // append this field's search suggestions to the array of found search suggestions
}
}
if (!empty($outputDataArray))
{
if (preg_match("/^main_fields$/i", $origSearchSuggestionsField)) // otherwise, data are already unique and ordered
{
// Remove duplicate values from array:
$outputDataArray = array_unique($outputDataArray);
// Sort in ascending order:
sort($outputDataArray);
}
if (preg_match("/^json$/i", $recordSchema))
$outputData = '"' . implode('", "', $outputDataArray) . '"';
else // unordered HTML list
$outputData = "<li>" . implode("</li><li>", $outputDataArray) . "</li>";
}
else
$outputData = "";
if (preg_match("/^json$/i", $recordSchema)) // return JSON-formatted search suggestions:
return '["' . $cqlQuery . '", [' . $outputData . ']]'; // e.g.: ["fir", ["firefox", "first choice", "mozilla firefox"]]
else // return HTML-formatted search suggestions:
return "<ul>" . $outputData . "</ul>"; // e.g.: <ul><li>firefox</li><li>first choice</li><li>mozilla firefox</li></ul>
}
// -------------------------------------------------------------------------------------------------------------------
// Present a form where a user can build a query:
function showQueryPage($operation, $viewType, $showRows, $rowOffset)
{
global $officialDatabaseName; // defined in 'ini.inc.php'
global $displayType;
global $loc; // defined in 'locales/core.php'
global $client;
// If there's no stored message available:
if (!isset($_SESSION['HeaderString']))
$HeaderString = $loc["SearchDB"].":"; // Provide the default message
else
{
$HeaderString = $_SESSION['HeaderString']; // extract 'HeaderString' session variable (only necessary if register globals is OFF!)
// Note: though we clear the session variable, the current message is still available to this script via '$HeaderString':
deleteSessionVariable("HeaderString"); // function 'deleteSessionVariable()' is defined in 'include.inc.php'
}
// For HTML output, we'll need to reset the value of the '$displayType' variable
// (which, by default, is set to "Export"; see above); otherwise, the 'originalDisplayType'
// parameter in the 'quickSearch' form of the page header would be incorrectly set to "Export"
$displayType = ""; // if '$displayType' is empty, 'show.php' will use the default view that's given in session variable 'userDefaultView'
// Show the login status:
showLogin(); // (function 'showLogin()' is defined in 'include.inc.php')
// DISPLAY header:
// call the 'displayHTMLhead()' and 'showPageHeader()' functions (which are defined in 'header.inc.php'):
displayHTMLhead(encodeHTML($officialDatabaseName) . " -- " . $loc["Search"], "index,follow", "Search the " . encodeHTML($officialDatabaseName), "", true, "", $viewType, array());
if ((!preg_match("/^Mobile$/i", $viewType)) AND (!preg_match("/^inc/i", $client))) // Note: we omit the visible header in mobile view ('viewType=Mobile') and for include mechanisms!
showPageHeader($HeaderString);
// Define variables holding common drop-down elements, i.e. build properly formatted <option> tag elements:
$dropDownConditionals1Array = array("contains" => $loc["contains"],
"does not contain" => $loc["contains not"],
"is equal to" => $loc["equal to"],
"is not equal to" => $loc["equal to not"],
"starts with" => $loc["starts with"],
"ends with" => $loc["ends with"]);
$dropDownItems1 = buildSelectMenuOptions($dropDownConditionals1Array, "//", "\t\t\t", true); // function 'buildSelectMenuOptions()' is defined in 'include.inc.php'
$dropDownConditionals2Array = array("is greater than" => $loc["is greater than"],
"is less than" => $loc["is less than"],
"is within range" => $loc["is within range"],
"is within list" => $loc["is within list"]);
$dropDownItems2 = buildSelectMenuOptions($dropDownConditionals2Array, "//", "\t\t\t", true);
$dropDownFieldNames1Array = array("author" => $loc["DropDownFieldName_Author"],
"address" => $loc["DropDownFieldName_Address"],
"corporate_author" => $loc["DropDownFieldName_CorporateAuthor"],
"thesis" => $loc["DropDownFieldName_Thesis"],
"", // empty array elements function as spacers between groups of drop-down menu items
"title" => $loc["DropDownFieldName_Title"],
"orig_title" => $loc["DropDownFieldName_OrigTitle"],
"",
"year" => $loc["DropDownFieldName_Year"],
"publication" => $loc["DropDownFieldName_Publication"],
"abbrev_journal" => $loc["DropDownFieldName_AbbrevJournal"],
"editor" => $loc["DropDownFieldName_Editor"],
"",
"volume_numeric" => $loc["DropDownFieldName_Volume"], // 'volume_numeric' is used instead of 'volume' in the sort dropdown menus
"issue" => $loc["DropDownFieldName_Issue"],
"pages" => $loc["DropDownFieldName_Pages"],
"",
"series_title" => $loc["DropDownFieldName_SeriesTitle"],
"abbrev_series_title" => $loc["DropDownFieldName_AbbrevSeriesTitle"],
"series_editor" => $loc["DropDownFieldName_SeriesEditor"],
"series_volume_numeric" => $loc["DropDownFieldName_SeriesVolume"], // 'series_volume_numeric' is used instead of 'series_volume' in the sort dropdown menus
"series_issue" => $loc["DropDownFieldName_SeriesIssue"],
"",
"publisher" => $loc["DropDownFieldName_Publisher"],
"place" => $loc["DropDownFieldName_Place"],
"",
"edition" => $loc["DropDownFieldName_Edition"],
"medium" => $loc["DropDownFieldName_Medium"],
"issn" => $loc["DropDownFieldName_Issn"],
"isbn" => $loc["DropDownFieldName_Isbn"],
"",
"language" => $loc["DropDownFieldName_Language"],
"summary_language" => $loc["DropDownFieldName_SummaryLanguage"],
"",
"keywords" => $loc["DropDownFieldName_Keywords"],
"abstract" => $loc["DropDownFieldName_Abstract"],
"",
"area" => $loc["DropDownFieldName_Area"],
"expedition" => $loc["DropDownFieldName_Expedition"],
"conference" => $loc["DropDownFieldName_Conference"],
"",
"doi" => $loc["DropDownFieldName_Doi"],
"url" => $loc["DropDownFieldName_Url"]);
if (isset($_SESSION['loginEmail'])) // we only include the 'file' field if the user is logged in
$dropDownFieldNames1Array["file"] = $loc["DropDownFieldName_File"];
$dropDownFieldNames1Array[] = "";
$dropDownFieldNames1Array["notes"] = $loc["DropDownFieldName_Notes"];
if (isset($_SESSION['loginEmail'])) // we only include the 'location' field if the user is logged in
$dropDownFieldNames1Array["location"] = $loc["DropDownFieldName_Location"];
$dropDownFieldNames2Array = array("call_number" => $loc["DropDownFieldName_CallNumber"],
"",
"serial" => $loc["DropDownFieldName_Serial"],
"type" => $loc["DropDownFieldName_Type"],
"approved" => $loc["DropDownFieldName_Approved"],
"",
"created_date" => $loc["DropDownFieldName_CreatedDate"],
"created_time" => $loc["DropDownFieldName_CreatedTime"]);
if (isset($_SESSION['loginEmail'])) // we only include the 'created_by' field if the user is logged in
$dropDownFieldNames2Array["created_by"] = $loc["DropDownFieldName_CreatedBy"];
$dropDownFieldNames2Array[] = "";
$dropDownFieldNames2Array["modified_date"] = $loc["DropDownFieldName_ModifiedDate"];
$dropDownFieldNames2Array["modified_time"] = $loc["DropDownFieldName_ModifiedTime"];
if (isset($_SESSION['loginEmail'])) // we only include the 'modified_by' field if the user is logged in
$dropDownFieldNames2Array["modified_by"] = $loc["DropDownFieldName_ModifiedBy"];
$dropDownItems3 = buildSelectMenuOptions(array_merge($dropDownFieldNames1Array,$dropDownFieldNames2Array), "//", "\t\t\t", true);
$dropDownConditionals3Array = array("html" => "html",
"atom" => "Atom XML",
"rss" => "RSS XML",
"srw_dc" => "SRW_DC XML",
"srw_mods" => "SRW_MODS XML");
$dropDownItems4 = buildSelectMenuOptions($dropDownConditionals3Array, "//", "\t\t\t", true);
// Map CQL indexes to refbase field names:
$indexNamesArray = mapCQLIndexes(); // function 'mapCQLIndexes()' is defined in 'webservice.inc.php'
// --------------------------------------------------------------------
// TODO: when the simple CQL Query Builder interface is done, a call to 'opensearch.php' (or 'opensearch.php?operation=simple')
// should activate that simple GUI-based interface (currently, it activates the advanced interface that you'd normally only
// get via 'opensearch.php?operation=cql' or 'opensearch.php?operation=advanced')
// if (preg_match("/^(advanced|CQL)$/i", $operation))
showQueryFormAdvanced($dropDownItems1, $dropDownItems2, $dropDownItems3, $dropDownItems4, $showRows, $rowOffset, $indexNamesArray, $viewType); // let's you enter a standard CQL query directly
// else
// showQueryFormSimple($dropDownItems1, $dropDownItems2, $dropDownItems3, $dropDownItems4, $showRows, $rowOffset, $indexNamesArray, $viewType); // let's you build a CQL query via dropdown menues
// --------------------------------------------------------------------
// DISPLAY THE HTML FOOTER:
// call the 'showPageFooter()' and 'displayHTMLfoot()' functions (which are defined in 'footer.inc.php')
if ((!preg_match("/^Mobile$/i", $viewType)) AND (!preg_match("/^inc/i", $client))) // Note: we omit the visible footer in mobile view ('viewType=Mobile') and for include mechanisms!
showPageFooter($HeaderString);
displayHTMLfoot();
}
// -------------------------------------------------------------------------------------------------------------------
// Present a form where a user can build a CQL query via dropdown menues:
//
// TODO: - add a button to add/remove query lines
// - for each form option chosen by the user, a little JavaScript should adopt the underlying CQL query (which finally gets sent to 'opensearch.php' in the 'query' parameter)
// - a 'setup' parameter should allow to pass a full CQL query to 'opensearch.php'; this will be parsed and used to setup the default choice of fields & options
// - offer to save the current choice of fields & options as a CQL query to the 'user_options' table, and reload it upon login using the 'setup' parameter
function showQueryFormSimple($dropDownItems1, $dropDownItems2, $dropDownItems3, $dropDownItems4, $showRows, $rowOffset, $indexNamesArray, $viewType)
{
global $loc; // defined in 'locales/core.php'
// Start <form> and <table> holding the form elements:
?>
<form action="opensearch.php" method="GET" name="openSearch">
<input type="hidden" name="formType" value="openSearch">
<input type="hidden" name="submit" value="<?php echo $loc["ButtonTitle_Search"]; ?>">
<input type="hidden" name="viewType" value="<?php echo $viewType; ?>">
<table id="queryform" align="center" border="0" cellpadding="0" cellspacing="10" width="95%" summary="This table holds a query form">
<tr>
<td width="120" valign="top">
<div class="sect"><?php echo $loc["Query"]; ?>:</div>
</td><?php
// NOTE: the field selectors and search options don't work yet (see the TODO items at the top of this function)
/*
<td width="140">
<select name="fieldSelector"><?php echo $dropDownItems3; ?>
</select>
</td>
<td width="122">
<select name="fieldConditionalSelector"><?php echo $dropDownItems1; ?>
</select>
</td>
*/
?>
<td colspan="2"><input type="text" name="query" value="" size="60"></td>
</tr>
<tr>
<td> </td>
<td>
<input type="submit" name="submit" value="<?php echo $loc["ButtonTitle_Search"]; ?>" title="<?php echo $loc["DescriptionSearchDB"]; ?>">
</td>
</tr>
</table>
</form><?php
}
// -------------------------------------------------------------------------------------------------------------------
// Present a form where a user can enter a standard CQL query directly:
//
// TODO: use divs + CSS styling (instead of a table-based layout) for _all_ output, especially for 'viewType=Mobile'
function showQueryFormAdvanced($dropDownItems1, $dropDownItems2, $dropDownItems3, $dropDownItems4, $showRows, $rowOffset, $indexNamesArray, $viewType)
{
global $officialDatabaseName; // defined in 'ini.inc.php'
global $loc; // defined in 'locales/core.php'
// Start <form> and <table> holding the form elements:
?>
<form action="opensearch.php" method="GET" name="openSearch">
<input type="hidden" name="formType" value="openSearch">
<input type="hidden" name="submit" value="<?php echo $loc["ButtonTitle_Search"]; ?>">
<input type="hidden" name="viewType" value="<?php echo $viewType; ?>">
<table id="queryform" align="center" border="0" cellpadding="0" cellspacing="10" width="95%" summary="This table holds the query form">
<tr>
<td width="120" valign="middle">
<div class="sect"><?php
if (preg_match("/^Mobile$/i", $viewType))
echo $officialDatabaseName;
else
echo $loc["CQLQuery"];
?>:</div>
</td>
<td>
<input type="text" name="query" value="" size="60" title="<?php echo $loc["DescriptionEnterSearchString"]; ?>">
</td>
</tr>
<tr>
<td> </td>
<td>
<input type="submit" name="submit" value="<?php echo $loc["ButtonTitle_Search"]; ?>" title="<?php echo $loc["DescriptionSearchDB"]; ?>">
</td>
</tr>
</table>
<table class="showhide" align="center" border="0" cellpadding="0" cellspacing="10" width="95%">
<tr>
<td class="small" width="120" valign="top">
<a href="javascript:toggleVisibility('searchopt','optToggleimg','optToggletxt','<?php echo rawurlencode($loc["DisplayOptions"]); ?>')"<?php echo addAccessKey("attribute", "search_opt"); ?> title="<?php echo $loc["LinkTitle_ToggleVisibility"] . addAccessKey("title", "search_opt"); ?>">
<img id="optToggleimg" class="toggleimg" src="img/closed.gif" alt="<?php echo $loc["LinkTitle_ToggleVisibility"]; ?>" width="9" height="9" hspace="0" border="0">
<span id="optToggletxt" class="toggletxt"><?php echo $loc["DisplayOptions"]; ?></span>
</a>
</td>
</tr>
</table>
<table id="searchopt" align="center" border="0" cellpadding="0" cellspacing="10" width="95%" summary="This table holds search & display options" style="display: none;">
<tr>
<td width="120" valign="middle">
<div class="sect"><?php echo $loc["DisplayOptions"]; ?>:</div>
</td>
<td width="215" valign="top">
<?php echo $loc["StartAtRecord"]; ?>:
<input type="text" name="startRecord" value="<?php echo ($rowOffset + 1); ?>" size="4" title="<?php echo $loc["DescriptionStartAtRecord"]; ?>">
</td>
<td valign="top">
<?php echo $loc["ShowRecordsPerPage_Prefix"]; ?> <input type="text" id="maximumRecords" name="maximumRecords" value="<?php echo $showRows; ?>" size="4" title="<?php echo $loc["DescriptionShowRecordsPerPage"]; ?>"> <?php echo $loc["ShowRecordsPerPage_Suffix"]; ?>
</td>
</tr>
<tr>
<td> </td>
<td valign="top" colspan="2">
<?php echo $loc["Format"]; ?>:
<select name="recordSchema" title="<?php echo $loc["DescriptionSelectCiteFormat"]; ?>"><?php echo $dropDownItems4; ?>
</select>
</td>
</tr>
</table>
<table class="showhide" align="center" border="0" cellpadding="0" cellspacing="10" width="95%">
<tr>
<td class="small" width="120" valign="top">
<a href="javascript:toggleVisibility('helptxt','helpToggleimg','helpToggletxt','<?php echo rawurlencode($loc["HelpAndExamples"]); ?>')"<?php echo addAccessKey("attribute", "search_help"); ?> title="<?php echo $loc["LinkTitle_ToggleVisibility"] . addAccessKey("title", "search_help"); ?>">
<img id="helpToggleimg" class="toggleimg" src="img/closed.gif" alt="<?php echo $loc["LinkTitle_ToggleVisibility"]; ?>" width="9" height="9" hspace="0" border="0">
<span id="helpToggletxt" class="toggletxt"><?php echo $loc["HelpAndExamples"]; ?></span>
</a>
</td>
</tr>
</table>
<table id="helptxt" align="center" border="0" cellpadding="0" cellspacing="10" width="95%" summary="This table holds some help text and example queries" style="display: none;">
<tr>
<td width="120" valign="top">
<div class="sect"><?php echo $loc["Help"]; ?>:</div>
</td>
<td class="helpbody" valign="top">
<div class="even">
This form lets you search the literature database using a standard CQL query (<a href="http://www.loc.gov/standards/sru/specs/cql.html" target="top">Common Query Language</a>). You can simply enter a query term, in which case the <em><?php echo $indexNamesArray["cql.serverChoice"]; ?></em> field will be searched by default. You can also search any other field, some query examples are given below. An introduction to CQL is given <a href="http://zing.z3950.org/cql/intro.html" target="top">here</a>.
</div>
</td>
</tr>
<tr>
<td width="120" valign="top">
<div class="sect"><?php echo $loc["Examples"]; ?>:</div>
</td>
<td class="examples" valign="top">
<div class="even">
Find all records where the <em><?php echo $indexNamesArray["cql.serverChoice"]; ?></em> field contains the word "ecology":
<pre>ecology</pre>
</div>
<div class="odd">
You can use wildcards anywhere in a search term to match one (<code>?</code>) or more (<code>*</code>) unspecified characters. E.g. this finds all records where the <em><?php echo $indexNamesArray["cql.serverChoice"]; ?></em> field contains a word that starts with "ecolog":
<pre>ecolog*</pre>
</div>
<div class="even">
Find all records where the <em>title</em> field contains <code>any</code> of the given words ("ecology" OR "diversity"):
<pre>title any ecology diversity</pre>
</div>
<div class="odd">
Find all records where the <em>author</em> field contains <code>all</code> of the given words ("dieckmann" AND "thomas" AND "sullivan"):
<pre>author all dieckmann thomas sullivan</pre>
</div>
<div class="even">
You can also search for <code>exact</code> field matches. E.g. this finds all records where the <em>publication</em> field equals EXACTLY "Marine Ecology Progress Series":
<pre>publication exact Marine Ecology Progress Series</pre>
</div>
<div class="odd">
For numeric fields, the obvious ordered relations (<code><</code>, <code><=</code>, <code>=</code>, <code>>=</code>, <code>></code>) may be used. E.g. this finds all records where the <em>year</em> field is greater than or equals "2005":
<pre>year >= 2005</pre>
</div>
<div class="even">
For numeric fields, you can match a range using the <code>within</code> relation followed by the lower and upper end of the range. E.g. this finds all records where the <em>volume</em> field contains a number between "10" and "20":
<pre>volume within 10 20</pre>
</div>
</td>
</tr>
</table>
</form><?php
}
// -------------------------------------------------------------------------------------------------------------------
?>