CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

SPTSearchEngine.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: SPTSearchEngine.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2011-2016 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9
10 class SPTSearchEngine extends SearchEngine
11 {
15 public function __construct()
16 {
17 # pass database handle and config values to real search engine object
18 parent::__construct("Resources", "ResourceId", "SchemaId");
19
20 # for each schema
21 $Schemas = MetadataSchema::GetAllSchemas();
22 foreach ($Schemas as $SchemaId => $Schema)
23 {
24 # for each field defined in schema
25 $this->Schemas[$SchemaId] = new MetadataSchema($SchemaId);
26 $Fields = $this->Schemas[$SchemaId]->GetFields();
27 foreach ($Fields as $FieldId => $Field)
28 {
29 # save metadata field type
30 $this->FieldTypes[$FieldId] = $Field->Type();
31
32 # determine field type for searching
33 switch ($Field->Type())
34 {
35 case MetadataSchema::MDFTYPE_TEXT:
36 case MetadataSchema::MDFTYPE_PARAGRAPH:
37 case MetadataSchema::MDFTYPE_USER:
38 case MetadataSchema::MDFTYPE_TREE:
39 case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
40 case MetadataSchema::MDFTYPE_OPTION:
41 case MetadataSchema::MDFTYPE_IMAGE:
42 case MetadataSchema::MDFTYPE_FILE:
43 case MetadataSchema::MDFTYPE_URL:
44 case MetadataSchema::MDFTYPE_REFERENCE:
45 $FieldType = self::FIELDTYPE_TEXT;
46 break;
47
48 case MetadataSchema::MDFTYPE_NUMBER:
49 case MetadataSchema::MDFTYPE_FLAG:
50 $FieldType = self::FIELDTYPE_NUMERIC;
51 break;
52
53 case MetadataSchema::MDFTYPE_DATE:
54 $FieldType = self::FIELDTYPE_DATERANGE;
55 break;
56
57 case MetadataSchema::MDFTYPE_TIMESTAMP:
58 $FieldType = self::FIELDTYPE_DATE;
59 break;
60
61 case MetadataSchema::MDFTYPE_POINT:
62 $FieldType = NULL;
63 break;
64
65 default:
66 throw Exception("ERROR: unknown field type "
67 .$Field->Type());
68 break;
69 }
70
71 if ($FieldType !== NULL)
72 {
73 # add field to search engine
74 $this->AddField($FieldId, $FieldType, $Field->SchemaId(),
75 $Field->SearchWeight(),
76 $Field->IncludeInKeywordSearch());
77 }
78 }
79 }
80 }
81
89 public function GetFieldContent($ItemId, $FieldId)
90 {
91 # get resource object
92 $Resource = new Resource($ItemId);
93
94 # if this is a reference field
95 if ($this->FieldTypes[$FieldId] == MetadataSchema::MDFTYPE_REFERENCE)
96 {
97 # retrieve IDs of referenced items
98 $ReferredItemIds = $Resource->Get($FieldId);
99
100 # for each referred item
101 $ReturnValue = array();
102 foreach ($ReferredItemIds as $RefId)
103 {
104 # retrieve title value for item and add to returned values
105 $RefResource = new Resource($RefId);
106 $ReturnValue[] = $RefResource->GetMapped("Title");
107 }
108
109 # return referred item titles to caller
110 return $ReturnValue;
111 }
112 else
113 {
114 # retrieve text (including variants) from resource object and return to caller
115 return $Resource->Get($FieldId, FALSE, TRUE);
116 }
117 }
118
125 public function SearchFieldForPhrases($FieldId, $Phrase)
126 {
127 # normalize and escape search phrase for use in SQL query
128 $SearchPhrase = strtolower(addslashes($Phrase));
129
130 # query DB for matching list based on field type
131 $Field = new MetadataField($FieldId);
132 switch ($Field->Type())
133 {
134 case MetadataSchema::MDFTYPE_TEXT:
135 case MetadataSchema::MDFTYPE_PARAGRAPH:
136 case MetadataSchema::MDFTYPE_FILE:
137 case MetadataSchema::MDFTYPE_URL:
138 $QueryString = "SELECT DISTINCT ResourceId FROM Resources "
139 ."WHERE POSITION('".$SearchPhrase."'"
140 ." IN LOWER(`".$Field->DBFieldName()."`)) ";
141 break;
142
143 case MetadataSchema::MDFTYPE_IMAGE:
144 $QueryString = "SELECT DISTINCT ResourceId FROM Resources "
145 ."WHERE POSITION('".$SearchPhrase."'"
146 ." IN LOWER(`".$Field->DBFieldName()."AltText`)) ";
147 break;
148
149 case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
150 $NameTableSize = $this->DB->Query("SELECT COUNT(*) AS NameCount"
151 ." FROM ControlledNames", "NameCount");
152 $QueryString = "SELECT DISTINCT ResourceNameInts.ResourceId "
153 ."FROM ResourceNameInts, ControlledNames "
154 ."WHERE POSITION('".$SearchPhrase."' IN LOWER(ControlledName)) "
155 ."AND ControlledNames.ControlledNameId"
156 ." = ResourceNameInts.ControlledNameId "
157 ."AND ControlledNames.FieldId = ".intval($FieldId);
158 $SecondQueryString = "SELECT DISTINCT ResourceNameInts.ResourceId "
159 ."FROM ResourceNameInts, ControlledNames, VariantNames "
160 ."WHERE POSITION('".$SearchPhrase."' IN LOWER(VariantName)) "
161 ."AND VariantNames.ControlledNameId"
162 ." = ResourceNameInts.ControlledNameId "
163 ."AND ControlledNames.ControlledNameId"
164 ." = ResourceNameInts.ControlledNameId "
165 ."AND ControlledNames.FieldId = ".intval($FieldId);
166 break;
167
168 case MetadataSchema::MDFTYPE_OPTION:
169 $QueryString = "SELECT DISTINCT ResourceNameInts.ResourceId "
170 ."FROM ResourceNameInts, ControlledNames "
171 ."WHERE POSITION('".$SearchPhrase."' IN LOWER(ControlledName)) "
172 ."AND ControlledNames.ControlledNameId"
173 ." = ResourceNameInts.ControlledNameId "
174 ."AND ControlledNames.FieldId = ".intval($FieldId);
175 break;
176
177 case MetadataSchema::MDFTYPE_TREE:
178 $QueryString = "SELECT DISTINCT ResourceClassInts.ResourceId "
179 ."FROM ResourceClassInts, Classifications "
180 ."WHERE POSITION('".$SearchPhrase
181 ."' IN LOWER(ClassificationName)) "
182 ."AND Classifications.ClassificationId"
183 ." = ResourceClassInts.ClassificationId "
184 ."AND Classifications.FieldId = ".intval($FieldId);
185 break;
186
187 case MetadataSchema::MDFTYPE_USER:
188 $UserId = $this->DB->Query("SELECT UserId FROM APUsers "
189 ."WHERE POSITION('".$SearchPhrase
190 ."' IN LOWER(UserName)) "
191 ."OR POSITION('".$SearchPhrase
192 ."' IN LOWER(RealName))", "UserId");
193 if ($UserId != NULL)
194 {
195 $QueryString = "SELECT DISTINCT ResourceId FROM ResourceUserInts "
196 ."WHERE UserId = ".$UserId
197 ." AND FieldId = ".intval($FieldId);
198 }
199 break;
200
201 case MetadataSchema::MDFTYPE_NUMBER:
202 if ($SearchPhrase > 0)
203 {
204 $QueryString = "SELECT DISTINCT ResourceId FROM Resources "
205 ."WHERE `".$Field->DBFieldName()
206 ."` = ".(int)$SearchPhrase;
207 }
208 break;
209
210 case MetadataSchema::MDFTYPE_FLAG:
211 case MetadataSchema::MDFTYPE_DATE:
212 case MetadataSchema::MDFTYPE_TIMESTAMP:
213 case MetadataSchema::MDFTYPE_REFERENCE:
214 # (these types not yet handled by search engine for phrases)
215 break;
216 }
217
218 # build match list based on results returned from DB
219 if (isset($QueryString))
220 {
221 $this->DMsg(7, "Performing phrase search query (<i>".$QueryString."</i>)");
222 if ($this->DebugLevel > 9) { $StartTime = microtime(TRUE); }
223 $this->DB->Query($QueryString);
224 if ($this->DebugLevel > 9)
225 {
226 $EndTime = microtime(TRUE);
227 if (($StartTime - $EndTime) > 0.1)
228 {
229 printf("SE: Query took %.2f seconds<br>\n",
230 ($EndTime - $StartTime));
231 }
232 }
233 $MatchList = $this->DB->FetchColumn("ResourceId");
234 if (isset($SecondQueryString))
235 {
236 $this->DMsg(7, "Performing second phrase search query"
237 ." (<i>".$SecondQueryString."</i>)");
238 if ($this->DebugLevel > 9) { $StartTime = microtime(TRUE); }
239 $this->DB->Query($SecondQueryString);
240 if ($this->DebugLevel > 9)
241 {
242 $EndTime = microtime(TRUE);
243 if (($StartTime - $EndTime) > 0.1)
244 {
245 printf("SE: query took %.2f seconds<br>\n",
246 ($EndTime - $StartTime));
247 }
248 }
249 $MatchList = $MatchList + $this->DB->FetchColumn("ResourceId");
250 }
251 }
252 else
253 {
254 $MatchList = array();
255 }
256
257 # return list of matching resources to caller
258 return $MatchList;
259 }
260
269 public function SearchFieldsForComparisonMatches(
270 $FieldIds, $Operators, $Values, $Logic)
271 {
272 # use SQL keyword appropriate to current search logic for combining operations
273 $CombineWord = ($Logic == "AND") ? " AND " : " OR ";
274
275 # for each comparison
276 foreach ($FieldIds as $Index => $FieldId)
277 {
278 # skip field if it is not valid
279 if (!MetadataSchema::FieldExistsInAnySchema($FieldId) )
280 {
281 continue;
282 }
283
284 $Field = new MetadataField($FieldId);
285 $Operator = $Operators[$Index];
286 $Value = $Values[$Index];
287
288 $ProcessingType = ($Operator{0} == "@")
289 ? "Modification Comparison" : $Field->Type();
290 switch ($ProcessingType)
291 {
292 case MetadataSchema::MDFTYPE_TEXT:
293 case MetadataSchema::MDFTYPE_PARAGRAPH:
294 case MetadataSchema::MDFTYPE_NUMBER:
295 case MetadataSchema::MDFTYPE_FLAG:
296 case MetadataSchema::MDFTYPE_URL:
297 $QueryConditions["Resources"][] = $this->GetTextComparisonSql(
298 $Field->DBFieldName(), $Operator, $Value);
299 break;
300
301 case MetadataSchema::MDFTYPE_USER:
302 $User = new CWUser($Value);
303 $QueryConditions["ResourceUserInts".$FieldId][] =
304 "(UserId = ".$User->Id()." AND FieldId = "
305 .intval($FieldId).")";
306 break;
307
308 case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
309 $QueryIndex = "ResourceNameInts".$FieldId;
310 if (!isset($Queries[$QueryIndex]["A"]))
311 {
312 $Queries[$QueryIndex]["A"] =
313 "SELECT DISTINCT ResourceId"
314 ." FROM ResourceNameInts, ControlledNames "
315 ." WHERE ControlledNames.FieldId = "
316 .intval($FieldId)
317 ." AND ( ";
318 $CloseQuery[$QueryIndex]["A"] = TRUE;
319 $ComparisonCount[$QueryIndex]["A"] = 1;
320 $ComparisonCountField[$QueryIndex]["A"] = "ControlledName";
321 }
322 else
323 {
324 $Queries[$QueryIndex]["A"] .= " OR ";
325 $ComparisonCount[$QueryIndex]["A"]++;
326 }
327 $Queries[$QueryIndex]["A"] .=
328 "(ResourceNameInts.ControlledNameId"
329 ." = ControlledNames.ControlledNameId"
330 ." AND ".$this->GetTextComparisonSql(
331 "ControlledName", $Operator, $Value)
332 .")";
333 if (!isset($Queries[$QueryIndex]["B"]))
334 {
335 $Queries[$QueryIndex]["B"] =
336 "SELECT DISTINCT ResourceId"
337 . " FROM ResourceNameInts, ControlledNames,"
338 ." VariantNames "
339 ." WHERE ControlledNames.FieldId = "
340 .intval($FieldId)
341 ." AND ( ";
342 $CloseQuery[$QueryIndex]["B"] = TRUE;
343 $ComparisonCount[$QueryIndex]["B"] = 1;
344 $ComparisonCountField[$QueryIndex]["B"] = "ControlledName";
345 }
346 else
347 {
348 $Queries[$QueryIndex]["B"] .= " OR ";
349 $ComparisonCount[$QueryIndex]["B"]++;
350 }
351 $Queries[$QueryIndex]["B"] .=
352 "(ResourceNameInts.ControlledNameId"
353 ." = ControlledNames.ControlledNameId"
354 ." AND ResourceNameInts.ControlledNameId"
355 ." = VariantNames.ControlledNameId"
356 ." AND ".$this->GetTextComparisonSql(
357 "VariantName", $Operator, $Value)
358 .")";
359 break;
360
361 case MetadataSchema::MDFTYPE_OPTION:
362 $QueryIndex = "ResourceNameInts".$FieldId;
363 if (!isset($Queries[$QueryIndex]))
364 {
365 $Queries[$QueryIndex] =
366 "SELECT DISTINCT ResourceId"
367 ." FROM ResourceNameInts, ControlledNames "
368 ." WHERE ControlledNames.FieldId = "
369 .intval($FieldId)
370 ." AND ( ";
371 $CloseQuery[$QueryIndex] = TRUE;
372 $ComparisonCount[$QueryIndex] = 1;
373 $ComparisonCountField[$QueryIndex] = "ControlledName";
374 }
375 else
376 {
377 $Queries[$QueryIndex] .= " OR ";
378 $ComparisonCount[$QueryIndex]++;
379 }
380 $Queries[$QueryIndex] .=
381 "(ResourceNameInts.ControlledNameId"
382 ." = ControlledNames.ControlledNameId"
383 ." AND ".$this->GetTextComparisonSql(
384 "ControlledName", $Operator, $Value)
385 .")";
386 break;
387
388 case MetadataSchema::MDFTYPE_TREE:
389 $QueryIndex = "ResourceClassInts".$FieldId;
390 if (!isset($Queries[$QueryIndex]))
391 {
392 $Queries[$QueryIndex] = "SELECT DISTINCT ResourceId"
393 ." FROM ResourceClassInts, Classifications"
394 ." WHERE ResourceClassInts.ClassificationId"
395 ." = Classifications.ClassificationId"
396 ." AND Classifications.FieldId"
397 ." = ".intval($FieldId)." AND ( ";
398 $CloseQuery[$QueryIndex] = TRUE;
399 $ComparisonCount[$QueryIndex] = 1;
400 $ComparisonCountField[$QueryIndex] = "ClassificationName";
401 }
402 else
403 {
404 $Queries[$QueryIndex] .= " OR ";
405 $ComparisonCount[$QueryIndex]++;
406 }
407 $Queries[$QueryIndex] .= $this->GetTextComparisonSql(
408 "ClassificationName", $Operator, $Value);
409 break;
410
411 case MetadataSchema::MDFTYPE_TIMESTAMP:
412 # if we have an SQL conditional
413 $TimestampConditional = $this->GetTimeComparisonSql(
414 $Field, $Operator, $Value);
415 if ($TimestampConditional)
416 {
417 # add conditional
418 $QueryConditions["Resources"][] = $TimestampConditional;
419 }
420 break;
421
422 case MetadataSchema::MDFTYPE_DATE:
423 $Date = new Date($Value);
424 if ($Date->Precision())
425 {
426 $QueryConditions["Resources"][] =
427 " ( ".$Date->SqlCondition(
428 $Field->DBFieldName()."Begin",
429 $Field->DBFieldName()."End", $Operator)." ) ";
430 }
431 break;
432
433 case MetadataSchema::MDFTYPE_REFERENCE:
434 $QueryIndex = "ReferenceInts".$FieldId;
435 if (!isset($Queries[$QueryIndex]))
436 {
437 $Queries[$QueryIndex] =
438 "SELECT DISTINCT RI.SrcResourceId AS ResourceId"
439 ." FROM ReferenceInts AS RI, Resources AS R "
440 ." WHERE RI.FieldId = ".intval($FieldId)
441 ." AND (";
442 $CloseQuery[$QueryIndex] = TRUE;
443 }
444 else
445 {
446 $Queries[$QueryIndex] .= $CombineWord;
447 }
448
449 # iterate over all the schemas this field can reference,
450 # gluing together an array of subqueries for the mapped
451 # title field of each as we go
452 $SchemaIds = $Field->ReferenceableSchemaIds();
453
454 # if no referenceable schemas configured, fall back to
455 # searching all schemas
456 if (count($SchemaIds)==0)
457 {
458 $SchemaIds = MetadataSchema::GetAllSchemaIds();
459 }
460
461 $Subqueries = array();
462 foreach ($SchemaIds as $SchemaId)
463 {
464 $Schema = new MetadataSchema($SchemaId);
465 $MappedTitle = $Schema->GetFieldByMappedName("Title");
466
467 $Subqueries[]= $this->GetTextComparisonSql(
468 $MappedTitle->DBFieldName(), $Operator, $Value, "R");
469 }
470
471 # OR together all the subqueries, add it to the query
472 # for our field
473 $Queries[$QueryIndex] .=
474 "((".implode(" OR ", $Subqueries).")"
475 ." AND R.ResourceId = RI.DstResourceId)";
476 break;
477
478 case "Modification Comparison":
479 # if we have an SQL conditional
480 $TimestampConditional = $this->GetTimeComparisonSql(
481 $Field, $Operator, $Value);
482 if ($TimestampConditional)
483 {
484 # add conditional
485 $QueryConditions["ResourceFieldTimestamps"][] =
486 $TimestampConditional;
487 }
488 break;
489
490 default:
491 throw new Exception("Search of unknown field type ("
492 .$ProcessingType.").");
493 break;
494 }
495 }
496
497 # if query conditions found
498 if (isset($QueryConditions))
499 {
500 # for each query condition group
501 foreach ($QueryConditions as $TargetTable => $Conditions)
502 {
503 # add entry with conditions to query list
504 if (isset($Queries[$TargetTable]))
505 {
506 $Queries[$TargetTable] .= $CombineWord
507 .implode($CombineWord, $Conditions);
508 }
509 else
510 {
511 $Queries[$TargetTable] = "SELECT DISTINCT ResourceId"
512 ." FROM ".$TargetTable." WHERE "
513 .implode($CombineWord, $Conditions);
514 }
515 }
516 }
517
518 # if queries found
519 if (isset($Queries))
520 {
521 # for each assembled query
522 foreach ($Queries as $QueryIndex => $Query)
523 {
524 # if query has multiple parts
525 if (is_array($Query))
526 {
527 # for each part of query
528 $ResourceIds = array();
529 foreach ($Query as $PartIndex => $PartQuery)
530 {
531 # add closing paren if query was flagged to be closed
532 if (isset($CloseQuery[$QueryIndex][$PartIndex]))
533 {
534 $PartQuery .= ") ";
535 if (($Logic == "AND")
536 && ($ComparisonCount[$QueryIndex][$PartIndex] > 1))
537 {
538 $PartQuery .= "GROUP BY ResourceId"
539 ." HAVING COUNT(DISTINCT "
540 .$ComparisonCountField[$QueryIndex][$PartIndex]
541 .") = "
542 .$ComparisonCount[$QueryIndex][$PartIndex];
543 }
544 }
545
546 # perform query and retrieve IDs
547 $this->DMsg(5, "Performing comparison query <i>"
548 .$PartQuery."</i>");
549 $this->DB->Query($PartQuery);
550 $ResourceIds = $ResourceIds
551 + $this->DB->FetchColumn("ResourceId");
552 $this->DMsg(5, "Comparison query produced <i>"
553 .count($ResourceIds)."</i> results");
554 }
555 }
556 else
557 {
558 # add closing paren if query was flagged to be closed
559 if (isset($CloseQuery[$QueryIndex]))
560 {
561 $Query .= ") ";
562 if (($Logic == "Logic")
563 && ($ComparisonCount[$QueryIndex] > 1))
564 {
565 $Query .= "GROUP BY ResourceId"
566 ." HAVING COUNT(DISTINCT "
567 .$ComparisonCountField[$QueryIndex]
568 .") = "
569 .$ComparisonCount[$QueryIndex];
570 }
571 }
572
573 # perform query and retrieve IDs
574 $this->DMsg(5, "Performing comparison query <i>".$Query."</i>");
575 $this->DB->Query($Query);
576 $ResourceIds = $this->DB->FetchColumn("ResourceId");
577 $this->DMsg(5, "Comparison query produced <i>"
578 .count($ResourceIds)."</i> results");
579 }
580
581 # if we already have some results
582 if (isset($Results))
583 {
584 # if search logic is set to AND
585 if ($Logic == "AND")
586 {
587 # remove anything from results that was not returned from query
588 $Results = array_intersect($Results, $ResourceIds);
589 }
590 else
591 {
592 # add values returned from query to results
593 $Results = array_unique(array_merge($Results, $ResourceIds));
594 }
595 }
596 else
597 {
598 # set results to values returned from query
599 $Results = $ResourceIds;
600 }
601 }
602 }
603 else
604 {
605 # initialize results to empty list
606 $Results = array();
607 }
608
609 # return results to caller
610 return $Results;
611 }
612
621 public static function GetItemIdsSortedByField(
622 $ItemType, $FieldId, $SortDescending)
623 {
624 $RFactory = new ResourceFactory($ItemType);
625 return $RFactory->GetResourceIdsSortedBy($FieldId, !$SortDescending);
626 }
627
634 public static function QueueUpdateForItem($ItemOrItemId, $TaskPriority = NULL)
635 {
636 if (is_numeric($ItemOrItemId))
637 {
638 $ItemId = $ItemOrItemId;
639 $Item = new Resource($ItemId);
640 }
641 else
642 {
643 $Item = $ItemOrItemId;
644 $ItemId = $Item->Id();
645 }
646
647 # if no priority was provided, use the default
648 if ($TaskPriority === NULL)
649 {
650 $TaskPriority = self::$TaskPriority;
651 }
652
653 # assemble task description
654 $Title = $Item->GetMapped("Title");
655 if (!strlen($Title))
656 {
657 $Title = "Item #".$ItemId;
658 }
659 $TaskDescription = "Update search data for"
660 ." <a href=\"r".$ItemId."\"><i>"
661 .$Title."</i></a>";
662
663 # queue update
664 $GLOBALS["AF"]->QueueUniqueTask(array(__CLASS__, "RunUpdateForItem"),
665 array(intval($ItemId)), $TaskPriority, $TaskDescription);
666 }
667
672 public static function RunUpdateForItem($ItemId)
673 {
674 # bail out if item no longer exists
675 $Resource = new Resource($ItemId);
676
677 # bail out if item is a temporary record
678 if ($Resource->IsTempResource()) { return; }
679
680 # retrieve schema ID of item to use for item type
681 $ItemType = $Resource->SchemaId();
682
683 # update search data for resource
684 $SearchEngine = new SPTSearchEngine();
685 $SearchEngine->UpdateForItem($ItemId, $ItemType);
686 }
687
696 public static function GetResultFacets($SearchResults, $User)
697 {
698 # classifications and names associated with these search results
699 $SearchClasses = array();
700 $SearchNames = array();
701
702 # make sure we're not faceting too many resources
703 $SearchResults = array_slice(
704 $SearchResults, 0,
705 self::$NumResourcesForFacets,
706 TRUE);
707
708 # disable DB cache for the search suggestions process,
709 # this avoids memory exhaustion.
710 $DB = new Database();
711 $DB->Caching(FALSE);
712
713 # number of resources to include in a chunk
714 # a mysql BIGINT is at most 21 characters long and the
715 # default max_packet_size is 1 MiB, so we can pack about
716 # 1 MiB / (22 bytes) = 47,663 ResourceIds into a query before
717 # we need to worry about length problems
718 $ChunkSize = 47600;
719
720 if (count($SearchResults)>0)
721 {
722 foreach (array_chunk($SearchResults, $ChunkSize, TRUE) as $Chunk)
723 {
724 # pull out all the Classifications that were associated
725 # with our search results along with all their parents
726 $DB->Query("SELECT ResourceId,ClassificationId FROM ResourceClassInts "
727 ."WHERE ResourceId IN "
728 ."(".implode(",", array_keys($Chunk)).")");
729 $Rows = $DB->FetchRows();
730 foreach ($Rows as $Row)
731 {
732 $CurId = $Row["ClassificationId"];
733 while ($CurId !== FALSE)
734 {
735 $SearchClasses[$CurId][]=$Row["ResourceId"] ;
736 $CurId = self::FindParentClass($CurId);
737 }
738 }
739
740 # also pull out controlled names
741 $DB->Query("SELECT ResourceId,ControlledNameId FROM ResourceNameInts "
742 ."WHERE ResourceId in "
743 ."(".implode(",", array_keys($Chunk)).")");
744 $Rows = $DB->FetchRows();
745 foreach ($Rows as $Row)
746 {
747 $SearchNames[$Row["ControlledNameId"]][]= $Row["ResourceId"];
748 }
749 }
750
751 # make sure we haven't double-counted resources that have
752 # a classification and some of its children assigned
753 $TmpClasses = array();
754 foreach ($SearchClasses as $ClassId => $Resources)
755 {
756 $TmpClasses[$ClassId] = array_unique($Resources);
757 }
758 $SearchClasses = $TmpClasses;
759 }
760
761 # generate a map of FieldId -> Field Names for all of the generated facets:
762 $SuggestionsById = array();
763
764 # pull relevant Classification names out of the DB
765 if (count($SearchClasses)>0)
766 {
767 foreach (array_chunk($SearchClasses, $ChunkSize, TRUE) as $Chunk)
768 {
769 $DB->Query("SELECT FieldId,ClassificationId,ClassificationName"
770 ." FROM Classifications"
771 ." WHERE ClassificationId"
772 ." IN (".implode(",", array_keys($Chunk)).")");
773 foreach ($DB->FetchRows() as $Row)
774 {
775 $SuggestionsById[$Row["FieldId"]][]=
776 array("Id" => $Row["ClassificationId"],
777 "Name" => $Row["ClassificationName"],
778 "Count" => count(
779 $SearchClasses[$Row["ClassificationId"]]));
780 }
781 }
782 }
783
784 # pull relevant ControlledNames out of the DB
785 if (count($SearchNames)>0)
786 {
787 foreach (array_chunk($SearchNames, $ChunkSize, TRUE) as $Chunk)
788 {
789 $DB->Query("SELECT FieldId,ControlledNameId,ControlledName"
790 ." FROM ControlledNames"
791 ." WHERE ControlledNameId"
792 ." IN (".implode(",", array_keys($SearchNames)).")");
793 foreach ($DB->FetchRows() as $Row)
794 {
795 $SuggestionsById[$Row["FieldId"]][]=
796 array("Id" => $Row["ControlledNameId"],
797 "Name" => $Row["ControlledName"],
798 "Count" => count(
799 $SearchNames[$Row["ControlledNameId"]]));
800 }
801 }
802 }
803
804 # translate the suggestions that we have in terms of the
805 # FieldIds to suggestions in terms of the field names
806 $SuggestionsByFieldName = array();
807
808 # if we have suggestions to offer
809 if (count($SuggestionsById)>0)
810 {
811 # gill in an array that maps FieldNames to search links
812 # which would be appropriate for that field
813 foreach ($SuggestionsById as $FieldId => $FieldValues)
814 {
815 try
816 {
817 $ThisField = new MetadataField($FieldId);
818 }
819 catch (Exception $Exception)
820 {
821 $ThisField = NULL;
822 }
823
824 # bail on fields that didn't exist and on fields that the
825 # current user cannot view, and on fields that are
826 # disabled for advanced searching
827 if (is_object($ThisField) &&
828 $ThisField->Status() == MetadataSchema::MDFSTAT_OK &&
829 $ThisField->IncludeInFacetedSearch() &&
830 $ThisField->Enabled() &&
831 $User->HasPriv($ThisField->ViewingPrivileges()))
832 {
833 $SuggestionsByFieldName[$ThisField->Name()] = array();
834
835 foreach ($FieldValues as $Value)
836 {
837 $SuggestionsByFieldName[$ThisField->Name()][$Value["Id"]] =
838 array("Name" => $Value["Name"], "Count" => $Value["Count"] );
839 }
840 }
841 }
842 }
843
844 ksort($SuggestionsByFieldName);
845
846 return $SuggestionsByFieldName;
847 }
848
854 public static function SetUpdatePriority($NewPriority)
855 {
856 self::$TaskPriority = $NewPriority;
857 }
858
863 public static function SetNumResourcesForFacets($NumToUse)
864 {
865 self::$NumResourcesForFacets = $NumToUse;
866 }
867
868 # ---- BACKWARD COMPATIBILITY --------------------------------------------
869
889 public function GroupedSearch(
890 $SearchGroups, $StartingResult = 0, $NumberOfResults = 10,
891 $SortByField = NULL, $SortDescending = TRUE)
892 {
893 if ($SearchGroups instanceof SearchParameterSet)
894 {
895 # if search parameter set was passed in, use it directly
896 $SearchParams = $SearchGroups;
897 }
898 else
899 {
900 # otherwise, convert legacy array into SearchParameterSet
901 $SearchParams = new SearchParameterSet();
902 $SearchParams->SetFromLegacyArray($SearchGroups);
903 }
904
905 # perform search
906 $Results = $this->Search(
907 $SearchParams, $StartingResult, $NumberOfResults,
908 $SortByField, $SortDescending);
909
910 # pull out the resoults for the Resource schema
911 if (isset($Results[MetadataSchema::SCHEMAID_DEFAULT]))
912 {
913 $Results = $Results[MetadataSchema::SCHEMAID_DEFAULT];
914 }
915 else
916 {
917 $Results = array();
918 }
919
920 # return the results
921 return $Results;
922 }
923
943 public static function ConvertToDisplayParameters($SearchParams)
944 {
945 # create display parameters, used to make a more user-friendly
946 # version of the search
947 $DisplayParams = new SearchParameterSet();
948
949 # copy keyword searches as is
950 $DisplayParams->AddParameter(
951 $SearchParams->GetKeywordSearchStrings() );
952
953 # copy field searches as is
954 $SearchStrings = $SearchParams->GetSearchStrings();
955 foreach ($SearchStrings as $FieldId => $Params)
956 {
957 $DisplayParams->AddParameter($Params, $FieldId);
958 }
959
960 # iterate over the search groups, looking for the 'is or begins
961 # with' group that we add when faceting and displaying them as
962 # IS parameters rather than the literal subgroup that we
963 # actually use
964 $Groups = $SearchParams->GetSubgroups();
965 foreach ($Groups as $Group)
966 {
967 # start off assuming that we'll just copy the group without conversion
968 $CopyGroup = TRUE;
969
970 # if this group uses OR logic for a single field, then it
971 # might be one of the subgroups we want to match and will require further
972 # investigation
973 if ($Group->Logic() == "OR" &&
974 count($Group->GetFields()) == 1)
975 {
976 # pull out the search strings for this field
977 $SearchStrings = $Group->GetSearchStrings();
978 $FieldId = key($SearchStrings);
979 $Values = current($SearchStrings);
980
981 # check if there are two search strings, one an 'is'
982 # and the other a 'begins with' that both start with the
983 # same prefix, as would be added by the search facet code
984 if ( count($Values) == 2 &&
985 preg_match('/^=(.*)$/', $Values[0], $FirstMatch) &&
986 preg_match('/^\\^(.*) -- $/', $Values[1], $SecondMatch) &&
987 $FirstMatch[1] == $SecondMatch[1] )
988 {
989 # check if this field is valid anywhere
990 if (MetadataSchema::FieldExistsInAnySchema($FieldId))
991 {
992 $Field = new MetadataField($FieldId);
993
994 # and check if this field is a tree field
995 if ($Field->Type() == MetadataSchema::MDFTYPE_TREE)
996 {
997 # okay, this group matches the form that
998 # the faceting code would add for an 'is or
999 # begins with' group; convert it to just an
1000 # 'is' group for display
1001 $DisplayParams->AddParameter("=".$FirstMatch[1], $FieldId);
1002 $CopyGroup = FALSE;
1003 }
1004 }
1005 }
1006 }
1007
1008 # if this group didn't require conversion, attempt to copy
1009 # it verbatim
1010 if ($CopyGroup)
1011 {
1012 try
1013 {
1014 $DisplayParams->AddSet($Group);
1015 }
1016 catch (Exception $e)
1017 {
1018 # if group could not be added for any reason, skip
1019 # it and move on to the next group
1020 }
1021 }
1022 }
1023
1024 return $DisplayParams;
1025 }
1026
1027 # ---- PRIVATE INTERFACE -------------------------------------------------
1028
1029 private $FieldTypes;
1030 private $Schemas;
1031
1032 private static $TaskPriority = ApplicationFramework::PRIORITY_BACKGROUND;
1033 private static $NumResourcesForFacets = 500;
1034
1043 private function GetTextComparisonSql($DBField, $Operator, $Value, $Prefix="")
1044 {
1045 # if we were given a prefix, add the necessary period so we can use it
1046 if (strlen($Prefix))
1047 {
1048 $Prefix = $Prefix.".";
1049 }
1050
1051 if ($Operator == "^")
1052 {
1053 $EscapedValue = str_replace(
1054 array("%", "_"),
1055 array("\%", "\_"),
1056 addslashes($Value));
1057 $Comparison = $Prefix."`".$DBField."` LIKE '".$EscapedValue."%' ";
1058 }
1059 elseif ($Operator == '$')
1060 {
1061 $EscapedValue = str_replace(
1062 array("%", "_"),
1063 array("\%", "\_"),
1064 addslashes($Value));
1065 $Comparison = $Prefix."`".$DBField."` LIKE '%".$EscapedValue."' ";
1066 }
1067 elseif ($Operator == '!=')
1068 {
1069 $Comparison =
1070 "(".$Prefix."`".$DBField."` ".$Operator." '".addslashes($Value)."'"
1071 ." AND ".$Prefix."`".$DBField."` IS NOT NULL)";
1072 }
1073 else
1074 {
1075 $Comparison = $Prefix."`".$DBField."` "
1076 .$Operator." '".addslashes($Value)."' ";
1077 }
1078 return $Comparison;
1079 }
1080
1089 private function GetTimeComparisonSql($Field, $Operator, $Value)
1090 {
1091 # check if this is a field modification comparison
1092 $ModificationComparison = ($Operator{0} == "@") ? TRUE : FALSE;
1093
1094 # if value appears to have time component or text description
1095 if (strpos($Value, ":")
1096 || strstr($Value, "day")
1097 || strstr($Value, "week")
1098 || strstr($Value, "month")
1099 || strstr($Value, "year")
1100 || strstr($Value, "hour")
1101 || strstr($Value, "minute"))
1102 {
1103 # adjust operator if necessary
1104 if ($Operator == "@")
1105 {
1106 $Operator = ">=";
1107 }
1108 else
1109 {
1110 if ($ModificationComparison)
1111 {
1112 $Operator = substr($Operator, 1);
1113 }
1114
1115 if (strstr($Value, "ago"))
1116 {
1117 $OperatorFlipMap = array(
1118 "<" => ">=",
1119 ">" => "<=",
1120 "<=" => ">",
1121 ">=" => "<",
1122 );
1123 $Operator = isset($OperatorFlipMap[$Operator])
1124 ? $OperatorFlipMap[$Operator] : $Operator;
1125 }
1126 }
1127
1128 # translate common words-to-numbers
1129 $WordsForNumbers = array(
1130 '/^a /i' => '1 ',
1131 '/^an /i' => '1 ',
1132 '/^one /i' => '1 ',
1133 '/^two /i' => '2 ',
1134 '/^three /i' => '3 ',
1135 '/^four /i' => '4 ',
1136 '/^five /i' => '5 ',
1137 '/^six /i' => '6 ',
1138 '/^seven /i' => '7 ',
1139 '/^eight /i' => '8 ',
1140 '/^nine /i' => '9 ',
1141 '/^ten /i' => '10 ',
1142 '/^eleven /i' => '11 ',
1143 '/^twelve /i' => '12 ',
1144 '/^thirteen /i' => '13 ',
1145 '/^fourteen /i' => '14 ',
1146 '/^fifteen /i' => '15 ',
1147 '/^sixteen /i' => '16 ',
1148 '/^seventeen /i' => '17 ',
1149 '/^eighteen /i' => '18 ',
1150 '/^nineteen /i' => '19 ',
1151 '/^twenty /i' => '20 ',
1152 '/^thirty /i' => '30 ',
1153 '/^forty /i' => '40 ',
1154 '/^fourty /i' => '40 ', # (common misspelling)
1155 '/^fifty /i' => '50 ',
1156 '/^sixty /i' => '60 ',
1157 '/^seventy /i' => '70 ',
1158 '/^eighty /i' => '80 ',
1159 '/^ninety /i' => '90 ');
1160 $Value = preg_replace(
1161 array_keys($WordsForNumbers), $WordsForNumbers, $Value);
1162
1163 # use strtotime method to build condition
1164 $TimestampValue = strtotime($Value);
1165 if (($TimestampValue !== FALSE) && ($TimestampValue != -1))
1166 {
1167 if ((date("H:i:s", $TimestampValue) == "00:00:00")
1168 && (strpos($Value, "00:00") === FALSE)
1169 && ($Operator == "<="))
1170 {
1171 $NormalizedValue =
1172 date("Y-m-d", $TimestampValue)." 23:59:59";
1173 }
1174 else
1175 {
1176 $NormalizedValue = date(
1177 "Y-m-d H:i:s", $TimestampValue);
1178 }
1179 }
1180 else
1181 {
1182 $NormalizedValue = addslashes($Value);
1183 }
1184
1185 # build SQL conditional
1186 if ($ModificationComparison)
1187 {
1188 $Conditional = " ( FieldId = ".$Field->Id()
1189 ." AND Timestamp ".$Operator
1190 ." '".$NormalizedValue."' ) ";
1191 }
1192 else
1193 {
1194 $Conditional = " ( `".$Field->DBFieldName()."` "
1195 .$Operator." '".$NormalizedValue."' ) ";
1196 }
1197 }
1198 else
1199 {
1200 # adjust operator if necessary
1201 if ($ModificationComparison)
1202 {
1203 $Operator = ($Operator == "@") ? ">="
1204 : substr($Operator, 1);
1205 }
1206
1207 # use Date object method to build conditional
1208 $Date = new Date($Value);
1209 if ($Date->Precision())
1210 {
1211 if ($ModificationComparison)
1212 {
1213 $Conditional = " ( FieldId = ".$Field->Id()
1214 ." AND ".$Date->SqlCondition(
1215 "Timestamp", NULL, $Operator)." ) ";
1216 }
1217 else
1218 {
1219 $Conditional = " ( ".$Date->SqlCondition(
1220 $Field->DBFieldName(), NULL, $Operator)." ) ";
1221 }
1222 }
1223 }
1224
1225 # return assembled conditional to caller
1226 return $Conditional;
1227 }
1228
1236 private static function FindParentClass($ClassId)
1237 {
1238 static $ParentMap;
1239
1240 # first time through, fetch the mapping of parent values we need
1241 if (!isset($ParentMap))
1242 {
1243 $DB = new Database();
1244
1245 # result here will be a parent/child mapping for all used
1246 # classifications; avoid caching it as it can be quite large
1247 $PreviousSetting = $DB->Caching();
1248 $DB->Caching(FALSE);
1249 $DB->Query(
1250 "SELECT ParentId, ClassificationId FROM Classifications "
1251 ."WHERE DEPTH > 0 AND FullResourceCount > 0 "
1252 ."AND FieldId IN (SELECT FieldId FROM MetadataFields "
1253 ." WHERE IncludeInFacetedSearch=1)"
1254 );
1255 $DB->Caching($PreviousSetting);
1256
1257 $ParentMap = $DB->FetchColumn("ParentId", "ClassificationId");
1258 }
1259
1260 return isset($ParentMap[$ClassId]) ? $ParentMap[$ClassId] : FALSE;
1261 }
1262 }
MetadataSchema\MDFTYPE_IMAGE
const MDFTYPE_IMAGE
Definition: MetadataSchema.php:31
MetadataSchema\MDFTYPE_TIMESTAMP
const MDFTYPE_TIMESTAMP
Definition: MetadataSchema.php:25
SearchEngine\AddField
AddField($FieldId, $FieldType, $ItemTypes, $Weight, $UsedInKeywordSearch)
Add field to include in searching.
Definition: SearchEngine.php:66
SearchEngine\$DB
$DB
Definition: SearchEngine.php:723
SPTSearchEngine
Definition: SPTSearchEngine.php:10
MetadataSchema\MDFTYPE_FILE
const MDFTYPE_FILE
Definition: MetadataSchema.php:32
MetadataSchema
Metadata schema (in effect a Factory class for MetadataField).
Definition: MetadataSchema.php:13
SearchParameterSet
Set of parameters used to perform a search.
Definition: SearchParameterSet.php:13
SPTSearchEngine\SetUpdatePriority
static SetUpdatePriority($NewPriority)
Set the default priority for background tasks.
Definition: SPTSearchEngine.php:854
Database
SQL database abstraction object with smart query caching.
Definition: Database.php:22
SPTSearchEngine\SetNumResourcesForFacets
static SetNumResourcesForFacets($NumToUse)
Set the number of resources used for search facets.
Definition: SPTSearchEngine.php:863
MetadataSchema\MDFTYPE_REFERENCE
const MDFTYPE_REFERENCE
Definition: MetadataSchema.php:35
MetadataSchema\MDFSTAT_OK
const MDFSTAT_OK
Definition: MetadataSchema.php:43
MetadataSchema\MDFTYPE_NUMBER
const MDFTYPE_NUMBER
Definition: MetadataSchema.php:23
MetadataSchema\MDFTYPE_TREE
const MDFTYPE_TREE
Definition: MetadataSchema.php:27
Date
Definition: Date.php:18
MetadataSchema\MDFTYPE_OPTION
const MDFTYPE_OPTION
Definition: MetadataSchema.php:29
MetadataSchema\MDFTYPE_FLAG
const MDFTYPE_FLAG
Definition: MetadataSchema.php:26
MetadataSchema\MDFTYPE_CONTROLLEDNAME
const MDFTYPE_CONTROLLEDNAME
Definition: MetadataSchema.php:28
SPTSearchEngine\GetItemIdsSortedByField
static GetItemIdsSortedByField($ItemType, $FieldId, $SortDescending)
Return item IDs sorted by a specified field.
Definition: SPTSearchEngine.php:621
MetadataSchema\GetAllSchemaIds
static GetAllSchemaIds()
Get IDs for all existing metadata schemas.
Definition: MetadataSchema.php:1778
PHP
PHP
Definition: RSSClient--Test.php:12
MetadataSchema\SCHEMAID_DEFAULT
const SCHEMAID_DEFAULT
Definition: MetadataSchema.php:53
SPTSearchEngine\RunUpdateForItem
static RunUpdateForItem($ItemId)
Update search index for an item.
Definition: SPTSearchEngine.php:672
MetadataSchema\FieldExistsInAnySchema
static FieldExistsInAnySchema($Field)
Determine if a Field exists in any schema.
Definition: MetadataSchema.php:1499
SPTSearchEngine\GroupedSearch
GroupedSearch($SearchGroups, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
Perform search with logical groups of fielded searches.
Definition: SPTSearchEngine.php:889
SPTSearchEngine\SearchFieldForPhrases
SearchFieldForPhrases($FieldId, $Phrase)
Perform phrase searching.
Definition: SPTSearchEngine.php:125
SearchEngine\Search
Search($SearchParams, $StartingResult=0, $NumberOfResults=PHP_INT_MAX, $SortByField=NULL, $SortDescending=TRUE)
Perform search with specified parameters.
Definition: SearchEngine.php:138
MetadataField
Object representing a locally-defined type of metadata field.
Definition: MetadataField.php:13
MetadataSchema\MDFTYPE_PARAGRAPH
const MDFTYPE_PARAGRAPH
Definition: MetadataSchema.php:22
MetadataSchema\MDFTYPE_DATE
const MDFTYPE_DATE
Definition: MetadataSchema.php:24
SearchEngine\DMsg
DMsg($Level, $Msg)
Print debug message if level set high enough.
Definition: SearchEngine.php:2205
Resource
Represents a "resource" in CWIS.
Definition: Resource.php:13
MetadataSchema\MDFTYPE_URL
const MDFTYPE_URL
Definition: MetadataSchema.php:33
SPTSearchEngine\GetResultFacets
static GetResultFacets($SearchResults, $User)
Generate a list of suggested additional search terms that can be used for faceted searching...
Definition: SPTSearchEngine.php:696
MetadataSchema\MDFTYPE_TEXT
const MDFTYPE_TEXT
Definition: MetadataSchema.php:21
SPTSearchEngine\GetFieldContent
GetFieldContent($ItemId, $FieldId)
Overloaded version of method to retrieve text from DB.
Definition: SPTSearchEngine.php:89
SearchEngine
Core metadata archive search engine class.
Definition: SearchEngine.php:13
SPTSearchEngine\ConvertToDisplayParameters
static ConvertToDisplayParameters($SearchParams)
Get a simplified SearchParameterSet for display purposes.
Definition: SPTSearchEngine.php:943
MetadataSchema\MDFTYPE_USER
const MDFTYPE_USER
Definition: MetadataSchema.php:30
SPTSearchEngine\SearchFieldsForComparisonMatches
SearchFieldsForComparisonMatches($FieldIds, $Operators, $Values, $Logic)
Perform comparison searches.
Definition: SPTSearchEngine.php:269
SearchEngine\DebugLevel
DebugLevel($NewValue)
Set debug output level.
Definition: SearchEngine.php:112
MetadataSchema\MDFTYPE_POINT
const MDFTYPE_POINT
Definition: MetadataSchema.php:34
ResourceFactory
Factory for Resource objects.
Definition: ResourceFactory.php:13
CWUser
CWIS-specific user class.
Definition: CWUser.php:13
MetadataSchema\GetAllSchemas
static GetAllSchemas()
Get all existing metadata schemas.
Definition: MetadataSchema.php:1794
SPTSearchEngine\__construct
__construct()
Class constructor.
Definition: SPTSearchEngine.php:15
ApplicationFramework\PRIORITY_BACKGROUND
const PRIORITY_BACKGROUND
Lowest priority.
Definition: ApplicationFramework.php:2612
SPTSearchEngine\QueueUpdateForItem
static QueueUpdateForItem($ItemOrItemId, $TaskPriority=NULL)
Queue background update for an item.
Definition: SPTSearchEngine.php:634