source: trunk/gsdl/src/recpt/queryaction.cpp@ 936

Last change on this file since 936 was 936, checked in by sjboddie, 24 years ago

tidied up search history stuff a bit - replaced strings with macros

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 40.8 KB
Line 
1/**********************************************************************
2 *
3 * queryaction.cpp --
4 * Copyright (C) 1999 The New Zealand Digital Library Project
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 * $Id: queryaction.cpp 936 2000-02-17 02:35:04Z sjboddie $
25 *
26 *********************************************************************/
27
28/*
29 $Log$
30 Revision 1.35 2000/02/17 02:35:04 sjboddie
31 tidied up search history stuff a bit - replaced strings with macros
32
33 Revision 1.34 2000/02/15 22:53:52 kjm18
34 search history stuff added.
35
36 Revision 1.33 2000/01/24 22:57:59 sjboddie
37 fixed bug in cross-collection searching, tidied up a bit
38
39 Revision 1.32 1999/12/13 02:54:11 davidb
40 Support for cross collection searching (CCS)
41
42 Revision 1.31 1999/12/05 21:22:33 sjboddie
43 tidied up cross-collection searching a bit
44
45 Revision 1.30 1999/11/08 20:26:37 sjboddie
46 added multiplevalue option to cgiarginfo
47
48 Revision 1.29 1999/11/04 20:29:53 sjboddie
49 small change to cross-collection searching
50
51 Revision 1.28 1999/11/03 22:49:50 sjboddie
52 A few changes to cross-collection searching for fao
53
54 Revision 1.27 1999/11/01 21:53:27 sjboddie
55 added cross-collection searching capability - still needs lots of
56 work but the basic functionality is there
57
58 Revision 1.26 1999/10/10 08:14:10 sjboddie
59 - metadata now returns mp rather than array
60 - redesigned browsing support (although it's not finished so
61 won't currently work ;-)
62
63 Revision 1.25 1999/09/24 04:49:39 sjboddie
64 fixed up the query selection boxes to display properly if there's only
65 a single index/sub-collection
66
67 Revision 1.24 1999/09/22 03:44:31 sjboddie
68 EndResults query filter option may now take '-1' for 'all'
69
70 Revision 1.23 1999/09/21 11:34:42 sjboddie
71 added Maxdocs queryfilter option which may be -1 for 'all'
72
73 Revision 1.22 1999/09/07 23:08:51 rjmcnab
74 removed some compiler warnings
75
76 Revision 1.21 1999/09/07 04:56:57 sjboddie
77 added GPL notice
78
79 Revision 1.20 1999/08/25 04:47:55 sjboddie
80 added advanced search option - other minor changes
81
82 Revision 1.19 1999/08/13 04:17:24 sjboddie
83 small change to do with new collection-level metadata
84
85 Revision 1.18 1999/08/10 22:46:33 sjboddie
86 changed format option result to QueryResults and added QueryLinks option
87
88 Revision 1.17 1999/07/30 02:24:42 sjboddie
89 added collectinfo argument to some functions
90
91 Revision 1.16 1999/07/19 00:16:58 sjboddie
92 no longer display documents that don't match all phrases in query string
93
94 Revision 1.15 1999/07/16 08:33:36 rjmcnab
95 Changed the logic for getting the results string slightly
96
97 Revision 1.14 1999/07/16 03:41:29 sjboddie
98 changed isApprox
99
100 Revision 1.13 1999/07/16 00:19:01 sjboddie
101 some changes to the way quoted queries are handled
102
103 Revision 1.12 1999/07/09 02:17:55 rjmcnab
104 Setting macros needed for a second query.
105
106 Revision 1.11 1999/07/07 06:13:10 rjmcnab
107 Added ability to combine two independant queries.
108
109 Revision 1.10 1999/07/07 05:49:35 sjboddie
110 had another crack at the format string code - created a new formattools
111 module. It can now handle {If} and {Or} statements although there's a
112 bug preventing nested if's and or's.
113
114 Revision 1.9 1999/07/01 22:48:46 sjboddie
115 had a go at getting a query result format string working
116
117 Revision 1.8 1999/06/27 22:02:11 sjboddie
118 author is added to queryresults if there is one
119
120 Revision 1.7 1999/06/26 01:10:18 rjmcnab
121 Made h, i, and n arguments saved in the compressed arguments.
122
123 Revision 1.6 1999/06/24 05:12:25 sjboddie
124 lots of small changes
125
126 Revision 1.5 1999/06/16 04:03:48 sjboddie
127 Now sets "cl" arg to "search" when going to a document from a search
128 results page. This allows the close book icon (in hierarchy toc) to
129 take you back to the results page if that's where you came from.
130 If you got to the document page somehow other than from a
131 classification or a search (i.e. if "cl" isn't set) then the close
132 book icon is disabled
133
134 Revision 1.4 1999/06/16 02:08:38 sjboddie
135 got queryaction working
136
137 Revision 1.3 1999/03/25 03:06:45 sjboddie
138
139 altered receptionist slightly so it now passes *collectproto to
140 define_internal_macros and define_external_macros - need it
141 for browseaction
142
143 Revision 1.2 1999/03/03 20:26:50 rjmcnab
144
145 Modified stuff.
146
147 Revision 1.1 1999/02/28 22:45:21 rjmcnab
148
149 Initial revision.
150
151 */
152
153
154#include "queryaction.h"
155#include "querytools.h"
156#include "formattools.h"
157#include "cgiutils.h"
158#include "OIDtools.h"
159//#include "infodbclass.h"
160#include "fileutil.h"
161#include "text_t.h"
162#include "historydb.h"
163
164void colinfo_t::clear () {
165 formatlistptr = NULL;
166 browserptr = NULL;
167}
168
169void QueryResult_t::clear() {
170 doc.clear();
171 collection.clear();
172}
173
174queryaction::queryaction () {
175
176 num_phrases = 0;
177
178 // this action uses cgi variable "a"
179 cgiarginfo arg_ainfo;
180 arg_ainfo.shortname = "a";
181 arg_ainfo.longname = "action";
182 arg_ainfo.multiplechar = true;
183 arg_ainfo.defaultstatus = cgiarginfo::weak;
184 arg_ainfo.argdefault = "q";
185 arg_ainfo.savedarginfo = cgiarginfo::must;
186 argsinfo.addarginfo (NULL, arg_ainfo);
187
188 // "b" - 0 = simple, 1 = advanced
189 arg_ainfo.shortname = "b";
190 arg_ainfo.longname = "query mode";
191 arg_ainfo.multiplechar = false;
192 arg_ainfo.defaultstatus = cgiarginfo::weak;
193 arg_ainfo.argdefault = "0";
194 arg_ainfo.savedarginfo = cgiarginfo::must;
195 argsinfo.addarginfo (NULL, arg_ainfo);
196
197 // "h"
198 arg_ainfo.shortname = "h";
199 arg_ainfo.longname = "main index";
200 arg_ainfo.multiplechar = true;
201 arg_ainfo.defaultstatus = cgiarginfo::weak;
202 arg_ainfo.argdefault = "";
203 arg_ainfo.savedarginfo = cgiarginfo::must;
204 argsinfo.addarginfo (NULL, arg_ainfo);
205
206 // "h2"
207 arg_ainfo.shortname = "h2";
208 arg_ainfo.longname = "main index for second query";
209 arg_ainfo.multiplechar = true;
210 arg_ainfo.defaultstatus = cgiarginfo::weak;
211 arg_ainfo.argdefault = "";
212 arg_ainfo.savedarginfo = cgiarginfo::must;
213 argsinfo.addarginfo (NULL, arg_ainfo);
214
215 // "j"
216 arg_ainfo.shortname = "j";
217 arg_ainfo.longname = "sub collection index";
218 arg_ainfo.multiplechar = true;
219 arg_ainfo.defaultstatus = cgiarginfo::weak;
220 arg_ainfo.argdefault = "";
221 arg_ainfo.savedarginfo = cgiarginfo::must;
222 argsinfo.addarginfo (NULL, arg_ainfo);
223
224 // "j2"
225 arg_ainfo.shortname = "j2";
226 arg_ainfo.longname = "sub collection index for second query";
227 arg_ainfo.multiplechar = true;
228 arg_ainfo.defaultstatus = cgiarginfo::weak;
229 arg_ainfo.argdefault = "";
230 arg_ainfo.savedarginfo = cgiarginfo::must;
231 argsinfo.addarginfo (NULL, arg_ainfo);
232
233 // "n"
234 arg_ainfo.shortname = "n";
235 arg_ainfo.longname = "language index";
236 arg_ainfo.multiplechar = true;
237 arg_ainfo.defaultstatus = cgiarginfo::weak;
238 arg_ainfo.argdefault = "";
239 arg_ainfo.savedarginfo = cgiarginfo::must;
240 argsinfo.addarginfo (NULL, arg_ainfo);
241
242 // "n2"
243 arg_ainfo.shortname = "n2";
244 arg_ainfo.longname = "language index for second query";
245 arg_ainfo.multiplechar = true;
246 arg_ainfo.defaultstatus = cgiarginfo::weak;
247 arg_ainfo.argdefault = "";
248 arg_ainfo.savedarginfo = cgiarginfo::must;
249 argsinfo.addarginfo (NULL, arg_ainfo);
250
251 // "q"
252 arg_ainfo.shortname = "q";
253 arg_ainfo.longname = "query string";
254 arg_ainfo.multiplechar = true;
255 arg_ainfo.defaultstatus = cgiarginfo::weak;
256 arg_ainfo.argdefault = "";
257 arg_ainfo.savedarginfo = cgiarginfo::must;
258 argsinfo.addarginfo (NULL, arg_ainfo);
259
260 // "q2"
261 arg_ainfo.shortname = "q2";
262 arg_ainfo.longname = "query string for second query";
263 arg_ainfo.multiplechar = true;
264 arg_ainfo.defaultstatus = cgiarginfo::weak;
265 arg_ainfo.argdefault = "";
266 arg_ainfo.savedarginfo = cgiarginfo::must;
267 argsinfo.addarginfo (NULL, arg_ainfo);
268
269 // "cq2" ""=don't combine, "and", "or", "not"
270 arg_ainfo.shortname = "cq2";
271 arg_ainfo.longname = "combine queries";
272 arg_ainfo.multiplechar = true;
273 arg_ainfo.defaultstatus = cgiarginfo::weak;
274 arg_ainfo.argdefault = "";
275 arg_ainfo.savedarginfo = cgiarginfo::must;
276 argsinfo.addarginfo (NULL, arg_ainfo);
277
278 // "t" - 1 = ranked 0 = boolean
279 arg_ainfo.shortname = "t";
280 arg_ainfo.longname = "search type";
281 arg_ainfo.multiplechar = false;
282 arg_ainfo.defaultstatus = cgiarginfo::weak;
283 arg_ainfo.argdefault = "1";
284 arg_ainfo.savedarginfo = cgiarginfo::must;
285 argsinfo.addarginfo (NULL, arg_ainfo);
286
287 // "k"
288 arg_ainfo.shortname = "k";
289 arg_ainfo.longname = "casefolding";
290 arg_ainfo.multiplechar = false;
291 arg_ainfo.defaultstatus = cgiarginfo::weak;
292 arg_ainfo.argdefault = "1";
293 arg_ainfo.savedarginfo = cgiarginfo::must;
294 argsinfo.addarginfo (NULL, arg_ainfo);
295
296 // "s"
297 arg_ainfo.shortname = "s";
298 arg_ainfo.longname = "stemming";
299 arg_ainfo.multiplechar = false;
300 arg_ainfo.defaultstatus = cgiarginfo::weak;
301 arg_ainfo.argdefault ="0";
302 arg_ainfo.savedarginfo = cgiarginfo::must;
303 argsinfo.addarginfo (NULL, arg_ainfo);
304
305 // "m"
306 arg_ainfo.shortname = "m";
307 arg_ainfo.longname = "maximum number of documents";
308 arg_ainfo.multiplechar = true;
309 arg_ainfo.defaultstatus = cgiarginfo::weak;
310 arg_ainfo.argdefault = "50";
311 arg_ainfo.savedarginfo = cgiarginfo::must;
312 argsinfo.addarginfo (NULL, arg_ainfo);
313
314 // "o"
315 arg_ainfo.shortname = "o";
316 arg_ainfo.longname = "hits per page";
317 arg_ainfo.multiplechar = true;
318 arg_ainfo.defaultstatus = cgiarginfo::weak;
319 arg_ainfo.argdefault = "20";
320 arg_ainfo.savedarginfo = cgiarginfo::must;
321 argsinfo.addarginfo (NULL, arg_ainfo);
322
323 // "r"
324 arg_ainfo.shortname = "r";
325 arg_ainfo.longname = "start results from";
326 arg_ainfo.multiplechar = true;
327 arg_ainfo.defaultstatus = cgiarginfo::weak;
328 arg_ainfo.argdefault = "1";
329 arg_ainfo.savedarginfo = cgiarginfo::must;
330 argsinfo.addarginfo (NULL, arg_ainfo);
331
332 // "ccs"
333 arg_ainfo.shortname = "ccs";
334 arg_ainfo.longname = "cross collection searching";
335 arg_ainfo.multiplechar = false;
336 arg_ainfo.defaultstatus = cgiarginfo::weak;
337 arg_ainfo.argdefault = "0";
338 arg_ainfo.savedarginfo = cgiarginfo::must;
339 argsinfo.addarginfo (NULL, arg_ainfo);
340
341 // "ccp"
342 arg_ainfo.shortname = "ccp";
343 arg_ainfo.longname = "cross collection page";
344 arg_ainfo.multiplechar = false;
345 arg_ainfo.defaultstatus = cgiarginfo::weak;
346 arg_ainfo.argdefault = "0";
347 arg_ainfo.savedarginfo = cgiarginfo::must;
348 argsinfo.addarginfo (NULL, arg_ainfo);
349
350 // "cc"
351 arg_ainfo.shortname = "cc";
352 arg_ainfo.longname = "collections to search";
353 arg_ainfo.multiplechar = true;
354 arg_ainfo.multiplevalue = true;
355 arg_ainfo.defaultstatus = cgiarginfo::weak;
356 arg_ainfo.argdefault = "";
357 arg_ainfo.savedarginfo = cgiarginfo::must;
358 argsinfo.addarginfo (NULL, arg_ainfo);
359
360 // "hd" history display - search history only displayed when
361 // this var set.
362 arg_ainfo.shortname = "hd";
363 arg_ainfo.longname = "history display";
364 arg_ainfo.multiplechar = false;
365 arg_ainfo.multiplevalue = false;
366 arg_ainfo.defaultstatus = cgiarginfo::weak;
367 arg_ainfo.argdefault = "0";
368 arg_ainfo.savedarginfo = cgiarginfo::must;
369 argsinfo.addarginfo (NULL, arg_ainfo);
370
371
372 // "hs" save - set to 1 in query form, so only save when submit
373 // query
374 // 0 = no save 1 = save
375 arg_ainfo.shortname = "hs";
376 arg_ainfo.longname = "history save";
377 arg_ainfo.multiplechar = false;
378 arg_ainfo.defaultstatus = cgiarginfo::weak;
379 arg_ainfo.argdefault = "0";
380 arg_ainfo.savedarginfo = cgiarginfo::mustnot;
381 argsinfo.addarginfo (NULL, arg_ainfo);
382
383
384 // "hcl" compress the list (show only the last 5)
385 // 0 = expand, 1 = compress
386
387 arg_ainfo.shortname = "hcl";
388 arg_ainfo.longname = "history compress list";
389 arg_ainfo.multiplechar = false;
390 arg_ainfo.defaultstatus = cgiarginfo::weak;
391 arg_ainfo.argdefault = "1";
392 arg_ainfo.savedarginfo = cgiarginfo::must;
393 argsinfo.addarginfo (NULL, arg_ainfo);
394
395
396
397}
398
399void queryaction::configure (const text_t &key, const text_tarray &cfgline) {
400 action::configure (key, cfgline);
401}
402
403bool queryaction::init (ostream &logout) {
404 return action::init (logout);
405}
406
407bool queryaction::check_cgiargs (cgiargsinfoclass &argsinfo, cgiargsclass &args,
408 ostream &logout) {
409
410 // check t argument
411 int arg_t = args.getintarg("t");
412 if (arg_t != 0 && arg_t != 1) {
413 logout << "Warning: \"t\" argument out of range (" << arg_t << ")\n";
414 cgiarginfo *tinfo = argsinfo.getarginfo ("t");
415 if (tinfo != NULL) args["t"] = tinfo->argdefault;
416 }
417
418 // check k argument
419 int arg_k = args.getintarg("k");
420 if (arg_k != 0 && arg_k != 1) {
421 logout << "Warning: \"k\" argument out of range (" << arg_k << ")\n";
422 cgiarginfo *kinfo = argsinfo.getarginfo ("k");
423 if (kinfo != NULL) args["k"] = kinfo->argdefault;
424 }
425
426 // check s argument
427 int arg_s = args.getintarg("s");
428 if (arg_s != 0 && arg_s != 1) {
429 logout << "Warning: \"s\" argument out of range (" << arg_s << ")\n";
430 cgiarginfo *sinfo = argsinfo.getarginfo ("s");
431 if (sinfo != NULL) args["s"] = sinfo->argdefault;
432 }
433
434 // check m argument
435 int arg_m = args.getintarg("m");
436 if (arg_m < -1) {
437 logout << "Warning: \"m\" argument less than -1 (" << arg_m << ")\n";
438 cgiarginfo *minfo = argsinfo.getarginfo ("m");
439 if (minfo != NULL) args["m"] = minfo->argdefault;
440 }
441
442 // check o argument
443 int arg_o = args.getintarg("o");
444 if (arg_o < -1) {
445 logout << "Warning: \"o\" argument less than -1 (" << arg_o << ")\n";
446 cgiarginfo *oinfo = argsinfo.getarginfo ("o");
447 if (oinfo != NULL) args["o"] = oinfo->argdefault;
448 }
449
450 // check r argument
451 int arg_r = args.getintarg("r");
452 if (arg_r < 1) {
453 logout << "Warning: \"r\" argument less than 1 (" << arg_r << ")\n";
454 cgiarginfo *rinfo = argsinfo.getarginfo ("r");
455 if (rinfo != NULL) args["r"] = rinfo->argdefault;
456 }
457 //check hd argument
458 int arg_hd = args.getintarg("hd");
459 if (arg_hd !=0 && arg_hd !=1) {
460 logout << "Warning: \"hd\" argument out of range (" << arg_hd << ")\n";
461 cgiarginfo *hdinfo = argsinfo.getarginfo ("hd");
462 if (hdinfo != NULL) args["hd"] = hdinfo->argdefault;
463 }
464 //check hs argument
465 int arg_hs = args.getintarg("hs");
466 if (arg_hs !=0 && arg_hs !=1) {
467 logout << "Warning: \"hs\" argument out of range (" << arg_hs << ")\n";
468 cgiarginfo *hsinfo = argsinfo.getarginfo ("hs");
469 if (hsinfo != NULL) args["hs"] = hsinfo->argdefault;
470 }
471
472 // chech hcl argument
473 int arg_hcl = args.getintarg("hcl");
474 if (arg_hcl !=0 && arg_hcl !=1) {
475 logout << "Warning: \"hcl\" argument out of range (" << arg_hcl << ")\n";
476 cgiarginfo *hclinfo = argsinfo.getarginfo ("hcl");
477 if (hclinfo != NULL) args["hcl"] = hclinfo->argdefault;
478 }
479
480 return true;
481}
482
483void queryaction::get_cgihead_info (cgiargsclass &/*args*/, recptprotolistclass * /*protos*/,
484 response_t &response, text_t &response_data,
485 ostream &/*logout*/) {
486 response = content;
487 response_data = "text/html";
488}
489
490void queryaction::define_internal_macros (displayclass &disp, cgiargsclass &args,
491 recptprotolistclass * /*protos*/,
492 ostream &/*logout*/) {
493
494 // define_internal_macros sets the following macros:
495
496 // _quotedquery_ the part of the query string that was quoted for post-processing
497
498
499
500 // The following macros are set later (in define_query_macros) as they can't be set until
501 // the query has been done.
502
503 // _freqmsg_ the term frequency string
504
505 // _resultline_ the "x documents matched the query" string
506
507 // _prevfirst_ these are used when setting up the links to previous/next
508 // _prevlast_ pages of results (_thisfirst_ and _thislast_ are used to set
509 // _nextfirst_ the 'results x-x for query: xxxx' string in the title bar)
510 // _nextlast_
511 // _thisfirst_
512 // _thislast_
513
514
515 // get the quoted bits of the query string and set _quotedquery_
516 text_tarray phrases;
517 get_phrases (args["q"], phrases);
518 num_phrases = phrases.size();
519 text_tarray::const_iterator phere = phrases.begin();
520 text_tarray::const_iterator pend = phrases.end();
521 bool first = true;
522 text_t quotedquery;
523 while (phere != pend) {
524 if (!first)
525 if ((phere +1) == pend) quotedquery += " and ";
526 else quotedquery += ", ";
527
528 quotedquery += "\"" + *phere + "\"";
529 first = false;
530 phere ++;
531 }
532 if (args.getintarg("s")) quotedquery += "_textstemon_";
533 disp.setmacro ("quotedquery", "query", quotedquery);
534}
535
536// sets the selection box macros _hselection_, _jselection_, and _nselection_.
537void queryaction::set_option_macro (const text_t &macroname, text_t current_value,
538 const FilterOption_t &option, displayclass &disp) {
539
540 if (option.validValues.empty()) return;
541 else if (option.validValues.size() == 1) {
542 disp.setmacro (macroname + "selection", "Global", "_" + option.defaultValue + "_");
543 return;
544 }
545 if (option.validValues.size() < 2) return;
546
547 text_t macrovalue = "<select name=\"" + macroname + "\">\n";
548
549 if (current_value.empty()) current_value = option.defaultValue;
550
551 text_tarray::const_iterator thisvalue = option.validValues.begin();
552 text_tarray::const_iterator endvalue = option.validValues.end();
553
554 while (thisvalue != endvalue) {
555 macrovalue += "<option value=\"" + *thisvalue + "\"";
556 if (*thisvalue == current_value)
557 macrovalue += " selected";
558 macrovalue += ">_" + *thisvalue + "_\n";
559 thisvalue ++;
560 }
561 macrovalue += "</select>\n";
562 disp.setmacro (macroname + "selection", "Global", macrovalue);
563}
564
565void queryaction::define_external_macros (displayclass &disp, cgiargsclass &args,
566 recptprotolistclass *protos, ostream &logout) {
567
568 // define_external_macros sets the following macros:
569
570 // some or all of these may not be required to be set
571 // _hselection_, _h2selection_ the selection box for the main part of the index
572 // _jselection_, _j2selection_ the selection box for the subcollection part of the index
573 // _nselection_, _n2selection_ the selection box for the language part of the index
574 // _cq2selection the selection box for combining two queries
575
576
577 // can't do anything if collectproto is null (i.e. no collection was specified)
578 recptproto *collectproto = protos->getrecptproto (args["c"], logout);
579 if (collectproto == NULL) return;
580
581 comerror_t err;
582 InfoFilterOptionsResponse_t response;
583 InfoFilterOptionsRequest_t request;
584 request.filterName = "QueryFilter";
585
586 collectproto->get_filteroptions (args["c"], request, response, err, logout);
587 if (err == noError) {
588
589 FilterOption_tmap::const_iterator it;
590 FilterOption_tmap::const_iterator end = response.filterOptions.end();
591
592 // _hselection_ and _h2selection_ (Index)
593 it = response.filterOptions.find ("Index");
594 if (it != end) set_option_macro ("h", args["h"], (*it).second, disp);
595 if (it != end) set_option_macro ("h2", args["h2"], (*it).second, disp);
596
597 // _jselection_ and _j2selection_ (Subcollection)
598 it = response.filterOptions.find ("Subcollection");
599 if (it != end) set_option_macro ("j", args["j"], (*it).second, disp);
600 if (it != end) set_option_macro ("j2", args["j2"], (*it).second, disp);
601
602 // _nselection_ and _n2selection_ (Language)
603 it = response.filterOptions.find ("Language");
604 if (it != end) set_option_macro ("n", args["n"], (*it).second, disp);
605 if (it != end) set_option_macro ("n2", args["n2"], (*it).second, disp);
606
607 // _cq2selection_ (CombineQuery)
608 it = response.filterOptions.find ("CombineQuery");
609 if (it != end) set_option_macro ("cq2", args["cq2"], (*it).second, disp);
610
611 // define_history_macros(disp, args, protos, logout);
612 }
613} // define external macros
614
615void queryaction::define_history_macros (displayclass &disp, cgiargsclass &args,
616 recptprotolistclass *protos, ostream &logout) {
617
618 // defines the following macros
619
620 // _searchhistorylist_
621
622 text_t historylist;
623 int arghd = args.getintarg("hd");
624 if (arghd != 1) {
625 historylist="";
626 }
627 else {
628 historylist = "<!-- Search History List -->\n";
629
630 text_t userid = args["z"];
631 text_tarray entries;
632 if (get_history_info (userid, entries, logout)) {
633 int count = 1;
634 text_tarray::iterator here = entries.begin();
635 text_tarray::iterator end = entries.end();
636 int size=(int)entries.size();
637 if (args["hcl"]==1&&size>5) { // compress the list
638 here = end-5;
639 count=size-4;
640 }
641 historylist += "<table align=center width=500 border=0>\n";
642 historylist += "<tr><td width=300 align=center>";
643 historylist += "<a href=\"_httpclearhistory_\">_textclearhistory_</a></td>\n";
644
645 if (size>5&&args["hcl"]==1) { //compress the list, show the expand button
646
647 historylist += "<td><a href=\"_gwcgi_?e=_compressedoptions_&a=q&hcl=0\">_textexpand_</a></td>\n";
648 }
649 else if (size >5 && args["hcl"]==0) { // expand the list, show contract button
650 historylist += "<td><a href=\"_gwcgi_?e=_compressedoptions_&a=q&hcl=1\">_textcontract_</a></td>\n";
651 }
652 historylist += "</table>\n";
653 historylist += "<table align=center width=500 border=1>\n<tr><th colspan=4 align=center>";
654 historylist += "_textsearchhistory_</th></tr>\n";
655 historylist += "<tr><th width=40>#</th>\n<th width=340>_textquery_</th>\n";
656 historylist += "<th width=60>_textresults_</th><th width=60>_textview_</th></tr>\n";
657
658 while (here !=end ) {
659 text_t c;
660 text_t query;
661 text_t numdocs;
662 text_t cgiargs;
663 text_t userinfo;
664 split_saved_query(*here,c, numdocs, cgiargs);
665 parse_saved_args(cgiargs, "q", query); // get query string out
666 decode_cgi_arg(query); // un cgisafe it
667
668 format_user_info(cgiargs, userinfo, protos, logout);
669
670 historylist += "<tr> <td width=40 align=center>"+c+"</td>\n";
671 historylist += "<td width=340 align=left>"+query+"</td><td width=60 align=center>"+numdocs+"</td>\n";
672 historylist += "<td width=60 align=center><a href=\"_gwcgi_?e=_compressedoptions_&";
673 historylist += *here+"\"><img name=\"display\" src=\"_httpicondisplay_\" width=_widthdisplay_ ";
674 historylist += "height=_heightdisplay_ border=\"0\" alt=\"" + userinfo +"\"></a></td></tr>\n";
675 here++;
676 count++;
677 }
678 historylist+="</table>\n\n";
679
680 } // if
681 else {
682 historylist += "_textnohistory_";
683 }
684 historylist += "<p><! ---- end of history list ----->\n";
685 } // else display list
686 disp.setmacro("searchhistorylist", "query", historylist);
687
688} // define history macros
689
690void queryaction::output_ccp (cgiargsclass &args, recptprotolistclass *protos,
691 displayclass &disp, outconvertclass &outconvert,
692 ostream &textout, ostream &logout) {
693
694 ColInfoResponse_t cinfo;
695 comerror_t err;
696 InfoFilterOptionsResponse_t fresponse;
697 InfoFilterOptionsRequest_t frequest;
698 frequest.filterName = "QueryFilter";
699
700 text_t &index = args["h"];
701 text_t &subcollection = args["j"];
702 text_t &language = args["n"];
703
704 text_tset collections;
705 text_t arg_cc = args["cc"];
706 decode_cgi_arg (arg_cc);
707 splitchar (arg_cc.begin(), arg_cc.end(), ',', collections);
708
709 textout << outconvert << disp << "_query:header_\n"
710 << "<center>_navigationbar_</center><br>\n"
711 << "<form name=QueryForm method=get action=\"_gwcgi_\">\n"
712 << "<input type=hidden name=a value=\"q\">\n"
713 << "<input type=hidden name=e value=\"_compressedoptions_\">\n"
714 << "<input type=hidden name=ccp value=\"1\">\n"
715 << "<center><table width=_pagewidth_><tr valign=top>\n"
716 << "<td>Select collections to search for \"" << args["q"]
717 << "\" <i>(index=" << index << " subcollection=" << subcollection
718 << " language=" << language << ")</i></td>\n"
719 << "<td><input type=\"submit\" value=\"_query:textbeginsearch_\"></td>\n"
720 << "</tr></table></center>\n"
721 << "<center><table width=_pagewidth_>\n"
722 << "<tr><td>\n";
723
724 recptprotolistclass::iterator rprotolist_here = protos->begin();
725 recptprotolistclass::iterator rprotolist_end = protos->end();
726 while (rprotolist_here != rprotolist_end) {
727 if ((*rprotolist_here).p != NULL) {
728
729 text_tarray collist;
730 (*rprotolist_here).p->get_collection_list (collist, err, logout);
731 if (err == noError) {
732 text_tarray::iterator collist_here = collist.begin();
733 text_tarray::iterator collist_end = collist.end();
734 while (collist_here != collist_end) {
735
736 (*rprotolist_here).p->get_collectinfo (*collist_here, cinfo, err, logout);
737 // if (err == noError && cinfo.isPublic && (cinfo.buildDate > 0)) {
738 if (err == noError && (cinfo.buildDate > 0)) {
739
740 (*rprotolist_here).p->get_filteroptions (*collist_here, frequest, fresponse, err, logout);
741 if (err == noError) {
742
743 FilterOption_tmap::const_iterator it;
744 FilterOption_tmap::const_iterator end = fresponse.filterOptions.end();
745 if (!index.empty()) {
746 it = fresponse.filterOptions.find ("Index");
747 if (it == end) {collist_here ++; continue;}
748 text_tarray::const_iterator there = (*it).second.validValues.begin();
749 text_tarray::const_iterator tend = (*it).second.validValues.end();
750 while (there != tend) {
751 if (*there == index) break;
752 there ++;
753 }
754 if (there == tend) {collist_here++; continue;}
755 }
756 if (!subcollection.empty()) {
757 it = fresponse.filterOptions.find ("Subcollection");
758 if (it == end) {collist_here++; continue;}
759 text_tarray::const_iterator there = (*it).second.validValues.begin();
760 text_tarray::const_iterator tend = (*it).second.validValues.end();
761 while (there != tend) {
762 if (*there == subcollection) break;
763 there ++;
764 }
765 if (there == tend) {collist_here++; continue;}
766 }
767 if (!language.empty()) {
768 it = fresponse.filterOptions.find ("Language");
769 if (it == end) {collist_here++; continue;}
770 text_tarray::const_iterator there = (*it).second.validValues.begin();
771 text_tarray::const_iterator tend = (*it).second.validValues.end();
772 while (there != tend) {
773 if (*there == language) break;
774 there ++;
775 }
776 if (there == tend) {collist_here++; continue;}
777 }
778
779 // we've got a matching collection
780 textout << "<input type=checkbox";
781
782 text_tset::const_iterator t = collections.find (*collist_here);
783 if (t != collections.end()) textout << " checked";
784
785 textout << outconvert
786 << " name=cc value=\"" << *collist_here << "\">";
787
788 if (!cinfo.collectionmeta["collectionname"].empty())
789 textout << outconvert << disp << cinfo.collectionmeta["collectionname"];
790 else
791 textout << outconvert << *collist_here;
792
793 textout << "<br>\n";
794
795 }
796 }
797 collist_here ++;
798 }
799 }
800 }
801 rprotolist_here ++;
802 }
803 textout << outconvert << disp
804 << "</td></tr></table></center>\n"
805 << "</form>\n"
806 << "_query:footer_\n";
807
808}
809
810bool queryaction::do_action (cgiargsclass &args, recptprotolistclass *protos,
811 browsermapclass *browsers, displayclass &disp,
812 outconvertclass &outconvert, ostream &textout,
813 ostream &logout) {
814
815 if (args["ccs"] == "1") {
816 if (!args["cc"].empty()) {
817 // query the selected collections
818 text_t::const_iterator b = args["cc"].begin();
819 text_t::const_iterator e = args["cc"].end();
820 if (findchar (b, e, ',') != e) {
821 if (!search_multiple_collections (args, protos, browsers, disp, outconvert,
822 textout, logout)) return false;
823 return true;
824 } else {
825 if (!search_single_collection (args, args["cc"], protos, browsers, disp,
826 outconvert, textout, logout)) return false;
827 return true;
828 }
829 }
830 }
831
832 // simply query the current collection
833 if (!search_single_collection (args, args["c"], protos, browsers, disp,
834 outconvert, textout, logout)) return false;
835 return true;
836}
837
838bool queryaction::search_multiple_collections (cgiargsclass &args, recptprotolistclass *protos,
839 browsermapclass *browsers, displayclass &disp,
840 outconvertclass &outconvert, ostream &textout,
841 ostream &logout) {
842
843 text_tarray collections;
844
845 text_t arg_cc = args["cc"];
846 decode_cgi_arg (arg_cc);
847 splitchar (arg_cc.begin(), arg_cc.end(), ',', collections);
848
849 if (collections.empty()) {
850 logout << "queryaction::search_multiple_collections: No collections "
851 << "set for doing multiple query - will search current collection\n";
852 textout << outconvert << disp << "_query:textwarningnocollections_\n";
853 return search_single_collection (args, args["c"], protos, browsers, disp,
854 outconvert, textout, logout);
855 }
856
857 // queryaction uses "VList" browser to display results,
858 // a queries clasification is "Search"
859 text_t browsertype = "VList";
860 text_t classification = "Search";
861
862 QueryResult_tset results;
863 map<text_t, colinfo_t, lttext_t> colinfomap;
864
865 ColInfoResponse_t cinfo;
866 comerror_t err;
867 FilterRequest_t request;
868 FilterResponse_t response;
869 request.filterResultOptions = FROID | FRmetadata | FRtermFreq | FRranking;
870 text_t formattedstring = args["q"];
871 text_t freqmsg = "_textfreqmsg1_";
872 int numdocs = 0;
873 isapprox isApprox = Exact;
874
875 format_querystring (formattedstring, args.getintarg("b"));
876 set_queryfilter_options (request, formattedstring, args);
877
878 // need to retrieve maxdocs matches for each collection
879 // (will eventually want to tidy this up, do so caching etc.)
880 OptionValue_t option;
881 option.name = "StartResults";
882 option.value = "1";
883 request.filterOptions.push_back (option);
884
885 option.name = "EndResults";
886 option.value = args["m"];
887 request.filterOptions.push_back (option);
888
889 text_tarray::iterator col_here = collections.begin();
890 text_tarray::iterator col_end = collections.end();
891
892 map<text_t, int, lttext_t> termfreqs;
893 while (col_here != col_end) {
894
895 request.fields.erase (request.fields.begin(), request.fields.end());
896 request.getParents = false;
897
898 recptproto *collectproto = protos->getrecptproto (*col_here, logout);
899 if (collectproto == NULL) {
900 logout << outconvert << "queryaction::search_multiple_collections: " << *col_here
901 << " collection has a NULL collectproto, ignoring\n";
902 col_here ++;
903 continue;
904 }
905 collectproto->get_collectinfo (*col_here, cinfo, err, logout);
906
907 browserclass *bptr = browsers->getbrowser (browsertype);
908
909 // get the formatstring if there is one
910 text_t formatstring;
911 if (!get_formatstring (classification, browsertype,
912 cinfo.format, formatstring))
913 formatstring = bptr->get_default_formatstring();
914
915 bptr->load_metadata_defaults (request.fields);
916
917 format_t *formatlistptr = new format_t();
918 parse_formatstring (formatstring, formatlistptr, request.fields, request.getParents);
919
920 colinfo_t thiscolinfo;
921 thiscolinfo.formatlistptr = formatlistptr;
922 thiscolinfo.browserptr = bptr;
923 colinfomap[*col_here] = thiscolinfo;
924
925 // do the query
926 collectproto->filter (*col_here, request, response, err, logout);
927 if (err != noError) {
928 outconvertclass text_t2ascii;
929 logout << text_t2ascii
930 << "queryaction::search_multiple_collections: call to QueryFilter failed "
931 << "for " << *col_here << " collection (" << get_comerror_string (err) << ")\n";
932 return false;
933 }
934
935 if (isApprox == Exact)
936 isApprox = response.isApprox;
937 else if (isApprox == MoreThan)
938 if (response.isApprox == Approximate)
939 isApprox = response.isApprox;
940
941 TermInfo_tarray::const_iterator this_term = response.termInfo.begin();
942 TermInfo_tarray::const_iterator end_term = response.termInfo.end();
943 while (this_term != end_term) {
944 termfreqs[(*this_term).term] += (*this_term).freq;
945 if ((col_here+1) == col_end) {
946 freqmsg += (*this_term).term + ": " + termfreqs[(*this_term).term];
947 if ((this_term+1) != end_term) freqmsg += ", ";
948 }
949 this_term ++;
950 }
951
952 if (response.numDocs > 0) {
953 numdocs += response.numDocs;
954
955 QueryResult_t thisresult;
956 thisresult.collection = *col_here;
957 ResultDocInfo_tarray::iterator doc_here = response.docInfo.begin();
958 ResultDocInfo_tarray::iterator doc_end = response.docInfo.end();
959 while (doc_here != doc_end) {
960 thisresult.doc = *doc_here;
961 results.insert (thisresult);
962 doc_here ++;
963 }
964 }
965 col_here ++;
966 }
967
968 disp.setmacro ("freqmsg", "query", freqmsg);
969
970 text_t resline;
971 if (num_phrases > 0) isApprox = Exact;
972 if (isApprox == Approximate) resline = "_textapprox_";
973 else if (isApprox == MoreThan) resline = "_textmorethan_";
974
975 if (numdocs == 0) resline = "_textnodocs_";
976 else if (numdocs == 1) resline += "_text1doc_";
977 else resline += text_t(numdocs) + " _textlotsdocs_";
978 disp.setmacro("resultline", "query", resline);
979
980 QueryResult_tset::iterator res_here = results.begin();
981 QueryResult_tset::iterator res_end = results.end();
982 text_tset metadata; // empty !!
983 bool getParents = false; // don't care !!
984 recptproto *collectproto = NULL;
985 bool use_table;
986 ResultDocInfo_t thisdoc;
987 format_t *formatlistptr = NULL;
988 browserclass *browserptr = NULL;
989
990 int maxdocs = args.getintarg("m");
991 int firstdoc = args.getintarg("r");
992 int hitsperpage = args.getintarg("o");
993 if (numdocs > maxdocs) numdocs = maxdocs;
994 if (hitsperpage == -1) hitsperpage = numdocs;
995
996 // set up _thisfirst_ and _thislast_ macros
997 disp.setmacro ("thisfirst", "query", firstdoc);
998 int thislast = firstdoc + (hitsperpage - 1);
999 if (thislast > numdocs) thislast = numdocs;
1000 disp.setmacro ("thislast", "query", thislast);
1001
1002 // set up _prevfirst_ and _prevlast_ macros
1003 if (firstdoc > 1) {
1004 disp.setmacro ("prevlast", "query", firstdoc - 1);
1005 int prevfirst = firstdoc - hitsperpage;
1006 if (prevfirst < 1) prevfirst = 1;
1007 disp.setmacro ("prevfirst", "query", prevfirst);
1008 }
1009
1010 // set up _nextfirst_ and _nextlast_ macros
1011 if (thislast < numdocs) {
1012 disp.setmacro ("nextfirst", "query", thislast + 1);
1013 int nextlast = thislast + hitsperpage;
1014 if (nextlast > numdocs) nextlast = numdocs;
1015 disp.setmacro ("nextlast", "query", nextlast);
1016 }
1017
1018 textout << outconvert << disp << "_query:header_\n"
1019 << "_query:content_";
1020
1021 int count = 1;
1022
1023 // output results
1024 while (res_here != res_end) {
1025 if (count < firstdoc) {count ++; res_here ++; continue;}
1026 if (count > thislast) break;
1027 formatlistptr = colinfomap[(*res_here).collection].formatlistptr;
1028 browserptr = colinfomap[(*res_here).collection].browserptr;
1029 thisdoc = (*res_here).doc;
1030 use_table = is_table_content (formatlistptr);
1031 browserptr->output_section_group (thisdoc, args, (*res_here).collection, 0,
1032 formatlistptr, use_table, metadata, getParents,
1033 collectproto, disp, outconvert, textout, logout);
1034 // textout << outconvert << "(ranking: " << (*res_here).doc.ranking << ")\n";
1035 res_here ++;
1036 count ++;
1037 }
1038
1039 textout << outconvert << disp << "_query:footer_";
1040
1041 // clean up the format_t pointers
1042 map<text_t, colinfo_t, lttext_t>::iterator here = colinfomap.begin();
1043 map<text_t, colinfo_t, lttext_t>::iterator end = colinfomap.end();
1044 while (here != end) {
1045 delete ((*here).second.formatlistptr);
1046 here ++;
1047 }
1048 return true;
1049}
1050
1051bool queryaction::search_single_collection (cgiargsclass &args, const text_t &collection,
1052 recptprotolistclass *protos, browsermapclass *browsers,
1053 displayclass &disp, outconvertclass &outconvert,
1054 ostream &textout, ostream &logout) {
1055
1056 recptproto *collectproto = protos->getrecptproto (collection, logout);
1057 if (collectproto == NULL) {
1058 logout << outconvert << "queryaction::search_single_collection: " << collection
1059 << " collection has a NULL collectproto\n";
1060 return false;
1061 }
1062
1063 // queryaction uses "VList" browser to display results,
1064 // a queries clasification is "Search"
1065 text_t browsertype = "VList";
1066 text_t classification = "Search";
1067
1068 ColInfoResponse_t cinfo;
1069 comerror_t err;
1070 collectproto->get_collectinfo (collection, cinfo, err, logout);
1071
1072 browserclass *bptr = browsers->getbrowser (browsertype);
1073
1074 // get the formatstring if there is one
1075 text_t formatstring;
1076 if (!get_formatstring (classification, browsertype,
1077 cinfo.format, formatstring))
1078 formatstring = bptr->get_default_formatstring();
1079
1080 FilterRequest_t request;
1081 FilterResponse_t response;
1082 bptr->set_filter_options (request, args);
1083 bptr->load_metadata_defaults (request.fields);
1084
1085 format_t *formatlistptr = new format_t();
1086 parse_formatstring (formatstring, formatlistptr, request.fields, request.getParents);
1087
1088 // do the query
1089 request.filterResultOptions = FROID | FRmetadata | FRtermFreq;
1090 text_t formattedstring = args["q"];
1091 if (!combine_query(args["z"], formattedstring)) {
1092 args["q"]=formattedstring;
1093 }
1094 format_querystring (formattedstring, args.getintarg("b"));
1095 set_queryfilter_options (request, formattedstring, args);
1096 collectproto->filter (collection, request, response, err, logout);
1097 if (err != noError) {
1098 outconvertclass text_t2ascii;
1099 logout << text_t2ascii
1100 << "queryaction::search_single_collections: call to QueryFilter failed "
1101 << "for " << collection << " collection (" << get_comerror_string (err) << ")\n";
1102 return false;
1103 }
1104
1105
1106 define_query_macros (args, disp, response);
1107
1108 // save the query if appropriate
1109 if (!save_search_history(args, response))
1110 logout << "save failed";
1111
1112 define_history_macros (disp, args, protos, logout);
1113
1114 textout << outconvert << disp << "_query:header_\n"
1115 << "_query:content_";
1116
1117 // output the results
1118 bool use_table = is_table_content (formatlistptr);
1119 bptr->output_section_group (response, args, collection, 0, formatlistptr,
1120 use_table, request.fields, request.getParents,
1121 collectproto, disp, outconvert, textout, logout);
1122
1123
1124 textout << outconvert << disp << "_query:footer_";
1125
1126 delete (formatlistptr);
1127
1128 return true;
1129}
1130
1131// define_query_macros sets the macros that couldn't be set until the
1132// query had been done. Those macros are _freqmsg_, _quotedquery_,
1133// _resultline_, _nextfirst_, _nextlast_, _prevfirst_, _prevlast_,
1134// _thisfirst_, and _thislast_
1135void queryaction::define_query_macros (cgiargsclass &args, displayclass &disp,
1136 const FilterResponse_t &response) {
1137
1138 // set up _freqmsg_ and _quotedquery_ macros
1139 text_t freqmsg = "_textfreqmsg1_";
1140 TermInfo_tarray::const_iterator this_term = response.termInfo.begin();
1141 TermInfo_tarray::const_iterator end_term = response.termInfo.end();
1142 while (this_term != end_term) {
1143 freqmsg += (*this_term).term + ": " + (*this_term).freq;
1144 if ((this_term + 1) != end_term)
1145 freqmsg += ", ";
1146 this_term ++;
1147 }
1148
1149 disp.setmacro ("freqmsg", "query", freqmsg);
1150
1151
1152 // set up _resultline_ macro
1153 text_t resline;
1154 int maxdocs = args.getintarg("m");
1155 int numdocs = response.numDocs;
1156 if (maxdocs == -1) maxdocs = response.numDocs;
1157 isapprox isApprox = response.isApprox;
1158
1159 // if there were phrases (post-processing) we're not going to include
1160 // those documents that didn't match
1161 if (num_phrases > 0) isApprox = Exact;
1162
1163 if (isApprox == Approximate) resline = "_textapprox_";
1164 else if (isApprox == MoreThan) resline = "_textmorethan_";
1165
1166 if (numdocs == 0) resline = "_textnodocs_";
1167 else if (numdocs == 1) resline += "_text1doc_";
1168 else resline += text_t(numdocs) + " _textlotsdocs_";
1169
1170 disp.setmacro("resultline", "query", resline);
1171
1172 int firstdoc = args.getintarg("r");
1173 int hitsperpage = args.getintarg("o");
1174 if (hitsperpage == -1) hitsperpage = numdocs;
1175
1176 // set up _thisfirst_ and _thislast_ macros
1177 disp.setmacro ("thisfirst", "query", firstdoc);
1178 int thislast = firstdoc + (hitsperpage - 1);
1179 if (thislast > numdocs) thislast = numdocs;
1180 disp.setmacro ("thislast", "query", thislast);
1181
1182 // set up _prevfirst_ and _prevlast_ macros
1183 if (firstdoc > 1) {
1184 disp.setmacro ("prevlast", "query", firstdoc - 1);
1185 int prevfirst = firstdoc - hitsperpage;
1186 if (prevfirst < 1) prevfirst = 1;
1187 disp.setmacro ("prevfirst", "query", prevfirst);
1188 }
1189
1190 // set up _nextfirst_ and _nextlast_ macros
1191 if (thislast < numdocs) {
1192 disp.setmacro ("nextfirst", "query", thislast + 1);
1193 int nextlast = thislast + hitsperpage;
1194 if (nextlast > numdocs) nextlast = numdocs;
1195 disp.setmacro ("nextlast", "query", nextlast);
1196 }
1197}
1198
1199bool queryaction::save_search_history (cgiargsclass &args, const FilterResponse_t &response)
1200{
1201 if (args["q"]=="") return true; // null query, dont save
1202 if (args["hs"]=="0") return true; // only save when submit query
1203
1204 // get userid
1205 text_t userid = args["z"];
1206
1207 // the number of docs goes on the front of the query string
1208 int numdocs= response.numDocs;
1209 text_t query = text_t(numdocs);
1210 if (response.isApprox==MoreThan) { // there were more docs found
1211 query.push_back('+');
1212 }
1213 query += "a=q";
1214 query += "&c="+args["c"];
1215 query += "&h="+args["h"];
1216 query += "&t="+args["t"];
1217 query += "&b="+args["b"];
1218 query += "&j="+args["j"];
1219 query += "&n="+args["n"];
1220 query += "&s="+args["s"];
1221 query += "&k="+args["k"];
1222
1223 text_t qstring = args["q"];
1224 text_t formattedquery =cgi_safe(qstring);
1225 query += "&q="+formattedquery;
1226
1227 if (set_history_info(userid, query)) return true;
1228 else return false;
1229
1230
1231}
1232
1233
1234
1235
1236
Note: See TracBrowser for help on using the repository browser.