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"][] =
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 try
676 {
677 $Resource = new Resource($ItemId);
678 }
679 catch (InvalidArgumentException $Exception)
680 {
681 return;
682 }
683
684 # bail out if item is a temporary record
685 if ($Resource->IsTempResource()) { return; }
686
687 # retrieve schema ID of item to use for item type
688 $ItemType = $Resource->SchemaId();
689
690 # update search data for resource
691 $SearchEngine = new SPTSearchEngine();
692 $SearchEngine->UpdateForItem($ItemId, $ItemType);
693 }
694
703 public static function GetResultFacets($SearchResults, $User)
704 {
705 # classifications and names associated with these search results
706 $SearchClasses = array();
707 $SearchNames = array();
708
709 # make sure we're not faceting too many resources
710 $SearchResults = array_slice(
711 $SearchResults, 0,
712 self::$NumResourcesForFacets,
713 TRUE);
714
715 # disable DB cache for the search suggestions process,
716 # this avoids memory exhaustion.
717 $DB = new Database();
718 $DB->Caching(FALSE);
719
720 # number of resources to include in a chunk
721 # a mysql BIGINT is at most 21 characters long and the
722 # default max_packet_size is 1 MiB, so we can pack about
723 # 1 MiB / (22 bytes) = 47,663 ResourceIds into a query before
724 # we need to worry about length problems
725 $ChunkSize = 47600;
726
727 if (count($SearchResults)>0)
728 {
729 foreach (array_chunk($SearchResults, $ChunkSize, TRUE) as $Chunk)
730 {
731 # pull out all the Classifications that were associated
732 # with our search results along with all their parents
733 $DB->Query("SELECT ResourceId,ClassificationId FROM ResourceClassInts "
734 ."WHERE ResourceId IN "
735 ."(".implode(",", array_keys($Chunk)).")");
736 $Rows = $DB->FetchRows();
737 foreach ($Rows as $Row)
738 {
739 $CurId = $Row["ClassificationId"];
740 while ($CurId !== FALSE)
741 {
742 $SearchClasses[$CurId][]=$Row["ResourceId"] ;
743 $CurId = self::FindParentClass($CurId);
744 }
745 }
746
747 # also pull out controlled names
748 $DB->Query("SELECT ResourceId,ControlledNameId FROM ResourceNameInts "
749 ."WHERE ResourceId in "
750 ."(".implode(",", array_keys($Chunk)).")");
751 $Rows = $DB->FetchRows();
752 foreach ($Rows as $Row)
753 {
754 $SearchNames[$Row["ControlledNameId"]][]= $Row["ResourceId"];
755 }
756 }
757
758 # make sure we haven't double-counted resources that have
759 # a classification and some of its children assigned
760 $TmpClasses = array();
761 foreach ($SearchClasses as $ClassId => $Resources)
762 {
763 $TmpClasses[$ClassId] = array_unique($Resources);
764 }
765 $SearchClasses = $TmpClasses;
766 }
767
768 # generate a map of FieldId -> Field Names for all of the generated facets:
769 $SuggestionsById = array();
770
771 # pull relevant Classification names out of the DB
772 if (count($SearchClasses)>0)
773 {
774 foreach (array_chunk($SearchClasses, $ChunkSize, TRUE) as $Chunk)
775 {
776 $DB->Query("SELECT FieldId,ClassificationId,ClassificationName"
777 ." FROM Classifications"
778 ." WHERE ClassificationId"
779 ." IN (".implode(",", array_keys($Chunk)).")");
780 foreach ($DB->FetchRows() as $Row)
781 {
782 $SuggestionsById[$Row["FieldId"]][]=
783 array("Id" => $Row["ClassificationId"],
784 "Name" => $Row["ClassificationName"],
785 "Count" => count(
786 $SearchClasses[$Row["ClassificationId"]]));
787 }
788 }
789 }
790
791 # pull relevant ControlledNames out of the DB
792 if (count($SearchNames)>0)
793 {
794 foreach (array_chunk($SearchNames, $ChunkSize, TRUE) as $Chunk)
795 {
796 $DB->Query("SELECT FieldId,ControlledNameId,ControlledName"
797 ." FROM ControlledNames"
798 ." WHERE ControlledNameId"
799 ." IN (".implode(",", array_keys($SearchNames)).")");
800 foreach ($DB->FetchRows() as $Row)
801 {
802 $SuggestionsById[$Row["FieldId"]][]=
803 array("Id" => $Row["ControlledNameId"],
804 "Name" => $Row["ControlledName"],
805 "Count" => count(
806 $SearchNames[$Row["ControlledNameId"]]));
807 }
808 }
809 }
810
811 # translate the suggestions that we have in terms of the
812 # FieldIds to suggestions in terms of the field names
813 $SuggestionsByFieldName = array();
814
815 # if we have suggestions to offer
816 if (count($SuggestionsById)>0)
817 {
818 # gill in an array that maps FieldNames to search links
819 # which would be appropriate for that field
820 foreach ($SuggestionsById as $FieldId => $FieldValues)
821 {
822 try
823 {
824 $ThisField = new MetadataField($FieldId);
825 }
826 catch (Exception $Exception)
827 {
828 $ThisField = NULL;
829 }
830
831 # bail on fields that didn't exist and on fields that the
832 # current user cannot view, and on fields that are
833 # disabled for advanced searching
834 if (is_object($ThisField) &&
835 $ThisField->Status() == MetadataSchema::MDFSTAT_OK &&
836 $ThisField->IncludeInFacetedSearch() &&
837 $ThisField->Enabled() &&
838 $User->HasPriv($ThisField->ViewingPrivileges()))
839 {
840 $SuggestionsByFieldName[$ThisField->Name()] = array();
841
842 foreach ($FieldValues as $Value)
843 {
844 $SuggestionsByFieldName[$ThisField->Name()][$Value["Id"]] =
845 array("Name" => $Value["Name"], "Count" => $Value["Count"] );
846 }
847 }
848 }
849 }
850
851 ksort($SuggestionsByFieldName);
852
853 return $SuggestionsByFieldName;
854 }
855
861 public static function SetUpdatePriority($NewPriority)
862 {
863 self::$TaskPriority = $NewPriority;
864 }
865
870 public static function SetNumResourcesForFacets($NumToUse)
871 {
872 self::$NumResourcesForFacets = $NumToUse;
873 }
874
875 # ---- BACKWARD COMPATIBILITY --------------------------------------------
876
896 public function GroupedSearch(
897 $SearchGroups, $StartingResult = 0, $NumberOfResults = 10,
898 $SortByField = NULL, $SortDescending = TRUE)
899 {
900 if ($SearchGroups instanceof SearchParameterSet)
901 {
902 # if search parameter set was passed in, use it directly
903 $SearchParams = $SearchGroups;
904 }
905 else
906 {
907 # otherwise, convert legacy array into SearchParameterSet
908 $SearchParams = new SearchParameterSet();
909 $SearchParams->SetFromLegacyArray($SearchGroups);
910 }
911
912 # perform search
913 $Results = $this->Search(
914 $SearchParams, $StartingResult, $NumberOfResults,
915 $SortByField, $SortDescending);
916
917 # pull out the resoults for the Resource schema
918 if (isset($Results[MetadataSchema::SCHEMAID_DEFAULT]))
919 {
920 $Results = $Results[MetadataSchema::SCHEMAID_DEFAULT];
921 }
922 else
923 {
924 $Results = array();
925 }
926
927 # return the results
928 return $Results;
929 }
930
950 public static function ConvertToDisplayParameters($SearchParams)
951 {
952 # create display parameters, used to make a more user-friendly
953 # version of the search
954 $DisplayParams = new SearchParameterSet();
955
956 # copy keyword searches as is
957 $DisplayParams->AddParameter(
958 $SearchParams->GetKeywordSearchStrings() );
959
960 # copy field searches as is
961 $SearchStrings = $SearchParams->GetSearchStrings();
962 foreach ($SearchStrings as $FieldId => $Params)
963 {
964 $DisplayParams->AddParameter($Params, $FieldId);
965 }
966
967 # iterate over the search groups, looking for the 'is or begins
968 # with' group that we add when faceting and displaying them as
969 # IS parameters rather than the literal subgroup that we
970 # actually use
971 $Groups = $SearchParams->GetSubgroups();
972 foreach ($Groups as $Group)
973 {
974 # start off assuming that we'll just copy the group without conversion
975 $CopyGroup = TRUE;
976
977 # if this group uses OR logic for a single field, then it
978 # might be one of the subgroups we want to match and will require further
979 # investigation
980 if ($Group->Logic() == "OR" &&
981 count($Group->GetFields()) == 1)
982 {
983 # pull out the search strings for this field
984 $SearchStrings = $Group->GetSearchStrings();
985 $FieldId = key($SearchStrings);
986 $Values = current($SearchStrings);
987
988 # check if there are two search strings, one an 'is'
989 # and the other a 'begins with' that both start with the
990 # same prefix, as would be added by the search facet code
991 if ( count($Values) == 2 &&
992 preg_match('/^=(.*)$/', $Values[0], $FirstMatch) &&
993 preg_match('/^\\^(.*) -- $/', $Values[1], $SecondMatch) &&
994 $FirstMatch[1] == $SecondMatch[1] )
995 {
996 # check if this field is valid anywhere
997 if (MetadataSchema::FieldExistsInAnySchema($FieldId))
998 {
999 $Field = new MetadataField($FieldId);
1000
1001 # and check if this field is a tree field
1002 if ($Field->Type() == MetadataSchema::MDFTYPE_TREE)
1003 {
1004 # okay, this group matches the form that
1005 # the faceting code would add for an 'is or
1006 # begins with' group; convert it to just an
1007 # 'is' group for display
1008 $DisplayParams->AddParameter("=".$FirstMatch[1], $FieldId);
1009 $CopyGroup = FALSE;
1010 }
1011 }
1012 }
1013 }
1014
1015 # if this group didn't require conversion, attempt to copy
1016 # it verbatim
1017 if ($CopyGroup)
1018 {
1019 try
1020 {
1021 $DisplayParams->AddSet($Group);
1022 }
1023 catch (Exception $e)
1024 {
1025 # if group could not be added for any reason, skip
1026 # it and move on to the next group
1027 }
1028 }
1029 }
1030
1031 return $DisplayParams;
1032 }
1033
1034 # ---- PRIVATE INTERFACE -------------------------------------------------
1035
1036 private $FieldTypes;
1037 private $Schemas;
1038
1039 private static $TaskPriority = ApplicationFramework::PRIORITY_BACKGROUND;
1040 private static $NumResourcesForFacets = 500;
1041
1050 private function GetTextComparisonSql($DBField, $Operator, $Value, $Prefix="")
1051 {
1052 # if we were given a prefix, add the necessary period so we can use it
1053 if (strlen($Prefix))
1054 {
1055 $Prefix = $Prefix.".";
1056 }
1057
1058 if ($Operator == "^")
1059 {
1060 $EscapedValue = str_replace(
1061 array("%", "_"),
1062 array("\%", "\_"),
1063 addslashes($Value));
1064 $Comparison = $Prefix."`".$DBField."` LIKE '".$EscapedValue."%' ";
1065 }
1066 elseif ($Operator == '$')
1067 {
1068 $EscapedValue = str_replace(
1069 array("%", "_"),
1070 array("\%", "\_"),
1071 addslashes($Value));
1072 $Comparison = $Prefix."`".$DBField."` LIKE '%".$EscapedValue."' ";
1073 }
1074 elseif ($Operator == '!=')
1075 {
1076 $Comparison =
1077 "(".$Prefix."`".$DBField."` ".$Operator." '".addslashes($Value)."'"
1078 ." AND ".$Prefix."`".$DBField."` IS NOT NULL)";
1079 }
1080 else
1081 {
1082 $Comparison = $Prefix."`".$DBField."` "
1083 .$Operator." '".addslashes($Value)."' ";
1084 }
1085 return $Comparison;
1086 }
1087
1096 private function GetTimeComparisonSql($Field, $Operator, $Value)
1097 {
1098 # check if this is a field modification comparison
1099 $ModificationComparison = ($Operator{0} == "@") ? TRUE : FALSE;
1100
1101 # if value appears to have time component or text description
1102 if (strpos($Value, ":")
1103 || strstr($Value, "day")
1104 || strstr($Value, "week")
1105 || strstr($Value, "month")
1106 || strstr($Value, "year")
1107 || strstr($Value, "hour")
1108 || strstr($Value, "minute"))
1109 {
1110 # adjust operator if necessary
1111 if ($Operator == "@")
1112 {
1113 $Operator = ">=";
1114 }
1115 else
1116 {
1117 if ($ModificationComparison)
1118 {
1119 $Operator = substr($Operator, 1);
1120 }
1121
1122 if (strstr($Value, "ago"))
1123 {
1124 $OperatorFlipMap = array(
1125 "<" => ">=",
1126 ">" => "<=",
1127 "<=" => ">",
1128 ">=" => "<",
1129 );
1130 $Operator = isset($OperatorFlipMap[$Operator])
1131 ? $OperatorFlipMap[$Operator] : $Operator;
1132 }
1133 }
1134
1135 # translate common words-to-numbers
1136 $WordsForNumbers = array(
1137 '/^a /i' => '1 ',
1138 '/^an /i' => '1 ',
1139 '/^one /i' => '1 ',
1140 '/^two /i' => '2 ',
1141 '/^three /i' => '3 ',
1142 '/^four /i' => '4 ',
1143 '/^five /i' => '5 ',
1144 '/^six /i' => '6 ',
1145 '/^seven /i' => '7 ',
1146 '/^eight /i' => '8 ',
1147 '/^nine /i' => '9 ',
1148 '/^ten /i' => '10 ',
1149 '/^eleven /i' => '11 ',
1150 '/^twelve /i' => '12 ',
1151 '/^thirteen /i' => '13 ',
1152 '/^fourteen /i' => '14 ',
1153 '/^fifteen /i' => '15 ',
1154 '/^sixteen /i' => '16 ',
1155 '/^seventeen /i' => '17 ',
1156 '/^eighteen /i' => '18 ',
1157 '/^nineteen /i' => '19 ',
1158 '/^twenty /i' => '20 ',
1159 '/^thirty /i' => '30 ',
1160 '/^forty /i' => '40 ',
1161 '/^fourty /i' => '40 ', # (common misspelling)
1162 '/^fifty /i' => '50 ',
1163 '/^sixty /i' => '60 ',
1164 '/^seventy /i' => '70 ',
1165 '/^eighty /i' => '80 ',
1166 '/^ninety /i' => '90 ');
1167 $Value = preg_replace(
1168 array_keys($WordsForNumbers), $WordsForNumbers, $Value);
1169
1170 # use strtotime method to build condition
1171 $TimestampValue = strtotime($Value);
1172 if (($TimestampValue !== FALSE) && ($TimestampValue != -1))
1173 {
1174 if ((date("H:i:s", $TimestampValue) == "00:00:00")
1175 && (strpos($Value, "00:00") === FALSE)
1176 && ($Operator == "<="))
1177 {
1178 $NormalizedValue =
1179 date("Y-m-d", $TimestampValue)." 23:59:59";
1180 }
1181 else
1182 {
1183 $NormalizedValue = date(
1184 "Y-m-d H:i:s", $TimestampValue);
1185 }
1186 }
1187 else
1188 {
1189 $NormalizedValue = addslashes($Value);
1190 }
1191
1192 # build SQL conditional
1193 if ($ModificationComparison)
1194 {
1195 $Conditional = " ( FieldId = ".$Field->Id()
1196 ." AND Timestamp ".$Operator
1197 ." '".$NormalizedValue."' ) ";
1198 }
1199 else
1200 {
1201 $Conditional = " ( `".$Field->DBFieldName()."` "
1202 .$Operator." '".$NormalizedValue."' ) ";
1203 }
1204 }
1205 else
1206 {
1207 # adjust operator if necessary
1208 if ($ModificationComparison)
1209 {
1210 $Operator = ($Operator == "@") ? ">="
1211 : substr($Operator, 1);
1212 }
1213
1214 # use Date object method to build conditional
1215 $Date = new Date($Value);
1216 if ($Date->Precision())
1217 {
1218 if ($ModificationComparison)
1219 {
1220 $Conditional = " ( FieldId = ".$Field->Id()
1221 ." AND ".$Date->SqlCondition(
1222 "Timestamp", NULL, $Operator)." ) ";
1223 }
1224 else
1225 {
1226 $Conditional = " ( ".$Date->SqlCondition(
1227 $Field->DBFieldName(), NULL, $Operator)." ) ";
1228 }
1229 }
1230 }
1231
1232 # return assembled conditional to caller
1233 return $Conditional;
1234 }
1235
1243 private static function FindParentClass($ClassId)
1244 {
1245 static $ParentMap;
1246
1247 # first time through, fetch the mapping of parent values we need
1248 if (!isset($ParentMap))
1249 {
1250 $DB = new Database();
1251
1252 # result here will be a parent/child mapping for all used
1253 # classifications; avoid caching it as it can be quite large
1254 $PreviousSetting = $DB->Caching();
1255 $DB->Caching(FALSE);
1256 $DB->Query(
1257 "SELECT ParentId, ClassificationId FROM Classifications "
1258 ."WHERE DEPTH > 0 AND FullResourceCount > 0 "
1259 ."AND FieldId IN (SELECT FieldId FROM MetadataFields "
1260 ." WHERE IncludeInFacetedSearch=1)"
1261 );
1262 $DB->Caching($PreviousSetting);
1263
1264 $ParentMap = $DB->FetchColumn("ParentId", "ClassificationId");
1265 }
1266
1267 return isset($ParentMap[$ClassId]) ? $ParentMap[$ClassId] : FALSE;
1268 }
1269 }
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:861
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:870
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:1776
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:1505
SPTSearchEngine\GroupedSearch
GroupedSearch($SearchGroups, $StartingResult=0, $NumberOfResults=10, $SortByField=NULL, $SortDescending=TRUE)
Perform search with logical groups of fielded searches.
Definition: SPTSearchEngine.php:896
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:2246
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:703
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:950
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:1797
SPTSearchEngine\__construct
__construct()
Class constructor.
Definition: SPTSearchEngine.php:15
SPTSearchEngine\QueueUpdateForItem
static QueueUpdateForItem($ItemOrItemId, $TaskPriority=NULL)
Queue background update for an item.
Definition: SPTSearchEngine.php:634