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-2013 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 $Operator = $Operators[$Index];
279 $Value = $Values[$Index];
280
281 if (MetadataSchema::FieldExistsInAnySchema($FieldId) )
282 {
283 $Field = new MetadataField($FieldId);
284 switch ($Field->Type())
285 {
286 case MetadataSchema::MDFTYPE_TEXT:
287 case MetadataSchema::MDFTYPE_PARAGRAPH:
288 case MetadataSchema::MDFTYPE_NUMBER:
289 case MetadataSchema::MDFTYPE_FLAG:
290 case MetadataSchema::MDFTYPE_URL:
291 if (isset($Queries["Resources"]))
292 {
293 $Queries["Resources"] .= $CombineWord;
294 }
295 else
296 {
297 $Queries["Resources"] = "SELECT DISTINCT ResourceId"
298 ." FROM Resources WHERE ";
299 }
300 $Queries["Resources"] .= $this->GetTextComparisonSql(
301 $Field->DBFieldName(), $Operator, $Value);
302 break;
303
304 case MetadataSchema::MDFTYPE_USER:
305 $QueryIndex = "ResourceUserInts".$FieldId;
306
307 if (isset($Queries[$QueryIndex]))
308 {
309 $Queries[$QueryIndex] .= $CombineWord;
310 }
311 else
312 {
313 $Queries[$QueryIndex] = "SELECT DISTINCT ResourceId"
314 ." FROM ResourceUserInts WHERE ";
315 }
316
317 $User = new CWUser($Value);
318
319 $Queries[$QueryIndex] .=
320 "(UserId = ".$User->Id()." AND FieldId = "
321 .intval($FieldId).")";
322 break;
323
324 case MetadataSchema::MDFTYPE_CONTROLLEDNAME:
325 $QueryIndex = "ResourceNameInts".$Field->Id();
326 if (!isset($Queries[$QueryIndex]["A"]))
327 {
328 $Queries[$QueryIndex]["A"] =
329 "SELECT DISTINCT ResourceId"
330 ." FROM ResourceNameInts, ControlledNames "
331 ." WHERE ControlledNames.FieldId = "
332 .intval($FieldId)
333 ." AND ( ";
334 $CloseQuery[$QueryIndex]["A"] = TRUE;
335 $ComparisonCount[$QueryIndex]["A"] = 1;
336 $ComparisonCountField[$QueryIndex]["A"] = "ControlledName";
337 }
338 else
339 {
340 $Queries[$QueryIndex]["A"] .= " OR ";
341 $ComparisonCount[$QueryIndex]["A"]++;
342 }
343 $Queries[$QueryIndex]["A"] .=
344 "(ResourceNameInts.ControlledNameId"
345 ." = ControlledNames.ControlledNameId"
346 ." AND ".$this->GetTextComparisonSql(
347 "ControlledName", $Operator, $Value)
348 .")";
349 if (!isset($Queries[$QueryIndex]["B"]))
350 {
351 $Queries[$QueryIndex]["B"] =
352 "SELECT DISTINCT ResourceId"
353 . " FROM ResourceNameInts, ControlledNames,"
354 ." VariantNames "
355 ." WHERE ControlledNames.FieldId = "
356 .intval($Field->Id())
357 ." AND ( ";
358 $CloseQuery[$QueryIndex]["B"] = TRUE;
359 $ComparisonCount[$QueryIndex]["B"] = 1;
360 $ComparisonCountField[$QueryIndex]["B"] = "ControlledName";
361 }
362 else
363 {
364 $Queries[$QueryIndex]["B"] .= " OR ";
365 $ComparisonCount[$QueryIndex]["B"]++;
366 }
367 $Queries[$QueryIndex]["B"] .=
368 "(ResourceNameInts.ControlledNameId"
369 ." = ControlledNames.ControlledNameId"
370 ." AND ResourceNameInts.ControlledNameId"
371 ." = VariantNames.ControlledNameId"
372 ." AND ".$this->GetTextComparisonSql(
373 "VariantName", $Operator, $Value)
374 .")";
375 break;
376
377 case MetadataSchema::MDFTYPE_OPTION:
378 $QueryIndex = "ResourceNameInts".$FieldId;
379 if (!isset($Queries[$QueryIndex]))
380 {
381 $Queries[$QueryIndex] =
382 "SELECT DISTINCT ResourceId"
383 ." FROM ResourceNameInts, ControlledNames "
384 ." WHERE ControlledNames.FieldId = "
385 .intval($FieldId)
386 ." AND ( ";
387 $CloseQuery[$QueryIndex] = TRUE;
388 $ComparisonCount[$QueryIndex] = 1;
389 $ComparisonCountField[$QueryIndex] = "ControlledName";
390 }
391 else
392 {
393 $Queries[$QueryIndex] .= " OR ";
394 $ComparisonCount[$QueryIndex]++;
395 }
396 $Queries[$QueryIndex] .=
397 "(ResourceNameInts.ControlledNameId"
398 ." = ControlledNames.ControlledNameId"
399 ." AND ".$this->GetTextComparisonSql(
400 "ControlledName", $Operator, $Value)
401 .")";
402 break;
403
404 case MetadataSchema::MDFTYPE_TREE:
405 $QueryIndex = "ResourceClassInts".$FieldId;
406 if (!isset($Queries[$QueryIndex]))
407 {
408 $Queries[$QueryIndex] = "SELECT DISTINCT ResourceId"
409 ." FROM ResourceClassInts, Classifications"
410 ." WHERE ResourceClassInts.ClassificationId"
411 ." = Classifications.ClassificationId"
412 ." AND Classifications.FieldId"
413 ." = ".intval($FieldId)." AND ( ";
414 $CloseQuery[$QueryIndex] = TRUE;
415 $ComparisonCount[$QueryIndex] = 1;
416 $ComparisonCountField[$QueryIndex] = "ClassificationName";
417 }
418 else
419 {
420 $Queries[$QueryIndex] .= " OR ";
421 $ComparisonCount[$QueryIndex]++;
422 }
423 $Queries[$QueryIndex] .= $this->GetTextComparisonSql(
424 "ClassificationName", $Operator, $Value);
425 break;
426
427 case MetadataSchema::MDFTYPE_TIMESTAMP:
428 # if value appears to have time component or text description
429 if (strpos($Value, ":")
430 || strstr($Value, "day")
431 || strstr($Value, "week")
432 || strstr($Value, "month")
433 || strstr($Value, "year")
434 || strstr($Value, "hour")
435 || strstr($Value, "minute"))
436 {
437 if (isset($Queries["Resources"]))
438 {
439 $Queries["Resources"] .= $CombineWord;
440 }
441 else
442 {
443 $Queries["Resources"] = "SELECT DISTINCT ResourceId"
444 ." FROM Resources WHERE ";
445 }
446
447 # flip operator if necessary
448 if (strstr($Value, "ago"))
449 {
450 $OperatorFlipMap = array(
451 "<" => ">=",
452 ">" => "<=",
453 "<=" => ">",
454 ">=" => "<",
455 );
456 $Operator = isset($OperatorFlipMap[$Operator])
457 ? $OperatorFlipMap[$Operator] : $Operator;
458 }
459
460 # use strtotime method to build condition
461 $TimestampValue = strtotime($Value);
462 if (($TimestampValue !== FALSE) && ($TimestampValue != -1))
463 {
464 if ((date("H:i:s", $TimestampValue) == "00:00:00")
465 && (strpos($Value, "00:00") === FALSE)
466 && ($Operator == "<="))
467 {
468 $NormalizedValue =
469 date("Y-m-d", $TimestampValue)." 23:59:59";
470 }
471 else
472 {
473 $NormalizedValue = date(
474 "Y-m-d H:i:s", $TimestampValue);
475 }
476 }
477 else
478 {
479 $NormalizedValue = addslashes($Value);
480 }
481 $Queries["Resources"] .=
482 " ( `".$Field->DBFieldName()."` "
483 .$Operator
484 ." '".$NormalizedValue."' ) ";
485 }
486 else
487 {
488 # use Date object method to build condition
489 $Date = new Date($Value);
490 if ($Date->Precision())
491 {
492 if (isset($Queries["Resources"]))
493 {
494 $Queries["Resources"] .= $CombineWord;
495 }
496 else
497 {
498 $Queries["Resources"] = "SELECT DISTINCT ResourceId"
499 ." FROM Resources WHERE ";
500 }
501 $Queries["Resources"] .= " ( ".$Date->SqlCondition(
502 $Field->DBFieldName(), NULL, $Operator)." ) ";
503 }
504 }
505 break;
506
507 case MetadataSchema::MDFTYPE_DATE:
508 $Date = new Date($Value);
509 if ($Date->Precision())
510 {
511 if (isset($Queries["Resources"]))
512 {
513 $Queries["Resources"] .= $CombineWord;
514 }
515 else
516 {
517 $Queries["Resources"] = "SELECT DISTINCT ResourceId"
518 ." FROM Resources WHERE ";
519 }
520 $Queries["Resources"] .= " ( ".$Date->SqlCondition(
521 $Field->DBFieldName()."Begin",
522 $Field->DBFieldName()."End", $Operator)." ) ";
523 }
524 break;
525
526 case MetadataSchema::MDFTYPE_REFERENCE:
527 $QueryIndex = "ReferenceInts".$FieldId;
528 if (!isset($Queries[$QueryIndex]))
529 {
530 $Queries[$QueryIndex] =
531 "SELECT DISTINCT RI.SrcResourceId AS ResourceId"
532 ." FROM ReferenceInts AS RI, Resources AS R "
533 ." WHERE RI.FieldId = ".intval($FieldId)
534 ." AND (";
535 $CloseQuery[$QueryIndex] = TRUE;
536 }
537 else
538 {
539 $Queries[$QueryIndex] .= $CombineWord;
540 }
541
542 # iterate over all the schemas this field can reference,
543 # gluing together an array of subqueries for the mapped
544 # title field of each as we go
545 $SchemaIds = $Field->ReferenceableSchemaIds();
546
547 # if no referenceable schemas configured, fall back to
548 # searching all schemas
549 if (count($SchemaIds)==0)
550 {
551 $SchemaIds = MetadataSchema::GetAllSchemaIds();
552 }
553
554 $Subqueries = array();
555 foreach ($SchemaIds as $SchemaId)
556 {
557 $Schema = new MetadataSchema($SchemaId);
558 $MappedTitle = $Schema->GetFieldByMappedName("Title");
559
560 $Subqueries[]= $this->GetTextComparisonSql(
561 $MappedTitle->DBFieldName(), $Operator, $Value, "R");
562 }
563
564 # OR together all the subqueries, add it to the query
565 # for our field
566 $Queries[$QueryIndex] .=
567 "((".implode(" OR ", $Subqueries).")"
568 ." AND R.ResourceId = RI.DstResourceId)";
569 break;
570
571 case MetadataSchema::MDFTYPE_IMAGE:
572 case MetadataSchema::MDFTYPE_FILE:
573 # (these types not yet handled by search engine for comparisons)
574 break;
575 }
576 }
577 }
578
579 # if queries found
580 if (isset($Queries))
581 {
582 # for each assembled query
583 foreach ($Queries as $QueryIndex => $Query)
584 {
585 # if query has multiple parts
586 if (is_array($Query))
587 {
588 # for each part of query
589 $ResourceIds = array();
590 foreach ($Query as $PartIndex => $PartQuery)
591 {
592 # add closing paren if query was flagged to be closed
593 if (isset($CloseQuery[$QueryIndex][$PartIndex]))
594 {
595 $PartQuery .= ") ";
596 if (($Logic == "AND")
597 && ($ComparisonCount[$QueryIndex][$PartIndex] > 1))
598 {
599 $PartQuery .= "GROUP BY ResourceId"
600 ." HAVING COUNT(DISTINCT "
601 .$ComparisonCountField[$QueryIndex][$PartIndex]
602 .") = "
603 .$ComparisonCount[$QueryIndex][$PartIndex];
604 }
605 }
606
607 # perform query and retrieve IDs
608 $this->DMsg(5, "Performing comparison query <i>"
609 .$PartQuery."</i>");
610 $this->DB->Query($PartQuery);
611 $ResourceIds = $ResourceIds
612 + $this->DB->FetchColumn("ResourceId");
613 $this->DMsg(5, "Comparison query produced <i>"
614 .count($ResourceIds)."</i> results");
615 }
616 }
617 else
618 {
619 # add closing paren if query was flagged to be closed
620 if (isset($CloseQuery[$QueryIndex]))
621 {
622 $Query .= ") ";
623 if (($Logic == "Logic")
624 && ($ComparisonCount[$QueryIndex] > 1))
625 {
626 $Query .= "GROUP BY ResourceId"
627 ." HAVING COUNT(DISTINCT "
628 .$ComparisonCountField[$QueryIndex]
629 .") = "
630 .$ComparisonCount[$QueryIndex];
631 }
632 }
633
634 # perform query and retrieve IDs
635 $this->DMsg(5, "Performing comparison query <i>".$Query."</i>");
636 $this->DB->Query($Query);
637 $ResourceIds = $this->DB->FetchColumn("ResourceId");
638 $this->DMsg(5, "Comparison query produced <i>"
639 .count($ResourceIds)."</i> results");
640 }
641
642 # if we already have some results
643 if (isset($Results))
644 {
645 # if search logic is set to AND
646 if ($Logic == "AND")
647 {
648 # remove anything from results that was not returned from query
649 $Results = array_intersect($Results, $ResourceIds);
650 }
651 else
652 {
653 # add values returned from query to results
654 $Results = array_unique(array_merge($Results, $ResourceIds));
655 }
656 }
657 else
658 {
659 # set results to values returned from query
660 $Results = $ResourceIds;
661 }
662 }
663 }
664 else
665 {
666 # initialize results to empty list
667 $Results = array();
668 }
669
670 # return results to caller
671 return $Results;
672 }
673
682 public static function GetItemIdsSortedByField(
683 $ItemType, $FieldId, $SortDescending)
684 {
685 $RFactory = new ResourceFactory($ItemType);
686 return $RFactory->GetResourceIdsSortedBy($FieldId, !$SortDescending);
687 }
688
695 public static function QueueUpdateForItem($ItemOrItemId, $TaskPriority = NULL)
696 {
697 if (is_numeric($ItemOrItemId))
698 {
699 $ItemId = $ItemOrItemId;
700 $Item = new Resource($ItemId);
701 }
702 else
703 {
704 $Item = $ItemOrItemId;
705 $ItemId = $Item->Id();
706 }
707
708 # if no priority was provided, use the default
709 if ($TaskPriority === NULL)
710 {
711 $TaskPriority = self::$TaskPriority;
712 }
713
714 # assemble task description
715 $Title = $Item->GetMapped("Title");
716 if (!strlen($Title))
717 {
718 $Title = "Item #".$ItemId;
719 }
720 $TaskDescription = "Update search data for"
721 ." <a href=\"r".$ItemId."\"><i>"
722 .$Title."</i></a>";
723
724 # queue update
725 $GLOBALS["AF"]->QueueUniqueTask(array(__CLASS__, "RunUpdateForItem"),
726 array(intval($ItemId)), $TaskPriority, $TaskDescription);
727 }
728
733 public static function RunUpdateForItem($ItemId)
734 {
735 # bail out if item no longer exists
736 $Resource = new Resource($ItemId);
737 if ($Resource->Status() == -1) { return; }
738
739 # bail out if item is a temporary record
740 if ($Resource->IsTempResource()) { return; }
741
742 # retrieve schema ID of item to use for item type
743 $ItemType = $Resource->SchemaId();
744
745 # update search data for resource
746 $SearchEngine = new SPTSearchEngine();
747 $SearchEngine->UpdateForItem($ItemId, $ItemType);
748 }
749
758 public static function GetResultFacets($SearchResults, $User)
759 {
760 # classifications and names associated with these search results
761 $SearchClasses = array();
762 $SearchNames = array();
763
764 # make sure we're not faceting too many resources
765 $SearchResults = array_slice(
766 $SearchResults, 0,
767 self::$NumResourcesForFacets,
768 TRUE);
769
770 # disable DB cache for the search suggestions process,
771 # this avoids memory exhaustion.
772 $DB = new Database();
773 $DB->Caching(FALSE);
774
775 # number of resources to include in a chunk
776 # a mysql BIGINT is at most 21 characters long and the
777 # default max_packet_size is 1 MiB, so we can pack about
778 # 1 MiB / (22 bytes) = 47,663 ResourceIds into a query before
779 # we need to worry about length problems
780 $ChunkSize = 47600;
781
782 if (count($SearchResults)>0)
783 {
784 foreach (array_chunk($SearchResults, $ChunkSize, TRUE) as $Chunk)
785 {
786 # pull out all the Classifications that were associated
787 # with our search results along with all their parents
788 $DB->Query("SELECT ResourceId,ClassificationId FROM ResourceClassInts "
789 ."WHERE ResourceId IN "
790 ."(".implode(",", array_keys($Chunk)).")");
791 $Rows = $DB->FetchRows();
792 foreach ($Rows as $Row)
793 {
794 $CurId = $Row["ClassificationId"];
795 while ($CurId !== FALSE)
796 {
797 $SearchClasses[$CurId][]=$Row["ResourceId"] ;
798 $CurId = self::FindParentClass($CurId);
799 }
800 }
801
802 # also pull out controlled names
803 $DB->Query("SELECT ResourceId,ControlledNameId FROM ResourceNameInts "
804 ."WHERE ResourceId in "
805 ."(".implode(",", array_keys($Chunk)).")");
806 $Rows = $DB->FetchRows();
807 foreach ($Rows as $Row)
808 {
809 $SearchNames[$Row["ControlledNameId"]][]= $Row["ResourceId"];
810 }
811 }
812
813 # make sure we haven't double-counted resources that have
814 # a classification and some of its children assigned
815 $TmpClasses = array();
816 foreach ($SearchClasses as $ClassId => $Resources)
817 {
818 $TmpClasses[$ClassId] = array_unique($Resources);
819 }
820 $SearchClasses = $TmpClasses;
821 }
822
823 # generate a map of FieldId -> Field Names for all of the generated facets:
824 $SuggestionsById = array();
825
826 # pull relevant Classification names out of the DB
827 if (count($SearchClasses)>0)
828 {
829 foreach (array_chunk($SearchClasses, $ChunkSize, TRUE) as $Chunk)
830 {
831 $DB->Query("SELECT FieldId,ClassificationId,ClassificationName"
832 ." FROM Classifications"
833 ." WHERE ClassificationId"
834 ." IN (".implode(",", array_keys($Chunk)).")");
835 foreach ($DB->FetchRows() as $Row)
836 {
837 $SuggestionsById[$Row["FieldId"]][]=
838 array("Id" => $Row["ClassificationId"],
839 "Name" => $Row["ClassificationName"],
840 "Count" => count(
841 $SearchClasses[$Row["ClassificationId"]]));
842 }
843 }
844 }
845
846 # pull relevant ControlledNames out of the DB
847 if (count($SearchNames)>0)
848 {
849 foreach (array_chunk($SearchNames, $ChunkSize, TRUE) as $Chunk)
850 {
851 $DB->Query("SELECT FieldId,ControlledNameId,ControlledName"
852 ." FROM ControlledNames"
853 ." WHERE ControlledNameId"
854 ." IN (".implode(",", array_keys($SearchNames)).")");
855 foreach ($DB->FetchRows() as $Row)
856 {
857 $SuggestionsById[$Row["FieldId"]][]=
858 array("Id" => $Row["ControlledNameId"],
859 "Name" => $Row["ControlledName"],
860 "Count" => count(
861 $SearchNames[$Row["ControlledNameId"]]));
862 }
863 }
864 }
865
866 # translate the suggestions that we have in terms of the
867 # FieldIds to suggestions in terms of the field names
868 $SuggestionsByFieldName = array();
869
870 # if we have suggestions to offer
871 if (count($SuggestionsById)>0)
872 {
873 # gill in an array that maps FieldNames to search links
874 # which would be appropriate for that field
875 foreach ($SuggestionsById as $FieldId => $FieldValues)
876 {
877 try
878 {
879 $ThisField = new MetadataField($FieldId);
880 }
881 catch (Exception $Exception)
882 {
883 $ThisField = NULL;
884 }
885
886 # bail on fields that didn't exist and on fields that the
887 # current user cannot view, and on fields that are
888 # disabled for advanced searching
889 if (is_object($ThisField) &&
890 $ThisField->Status() == MetadataSchema::MDFSTAT_OK &&
891 $ThisField->IncludeInFacetedSearch() &&
892 $ThisField->Enabled() &&
893 $User->HasPriv($ThisField->ViewingPrivileges()))
894 {
895 $SuggestionsByFieldName[$ThisField->Name()] = array();
896
897 foreach ($FieldValues as $Value)
898 {
899 $SuggestionsByFieldName[$ThisField->Name()][$Value["Id"]] =
900 array("Name" => $Value["Name"], "Count" => $Value["Count"] );
901 }
902 }
903 }
904 }
905
906 ksort($SuggestionsByFieldName);
907
908 return $SuggestionsByFieldName;
909 }
910
915 public function UpdateForResource($ItemId)
916 {
917 $this->UpdateForItem($ItemId);
918 }
919
925 public static function SetUpdatePriority($NewPriority)
926 {
927 self::$TaskPriority = $NewPriority;
928 }
929
934 public static function SetNumResourcesForFacets($NumToUse)
935 {
936 self::$NumResourcesForFacets = $NumToUse;
937 }
938
939 # ---- BACKWARD COMPATIBILITY --------------------------------------------
940
960 public function GroupedSearch(
961 $SearchGroups, $StartingResult = 0, $NumberOfResults = 10,
962 $SortByField = NULL, $SortDescending = TRUE)
963 {
964 if ($SearchGroups instanceof SearchParameterSet)
965 {
966 # if search parameter set was passed in, use it directly
967 $SearchParams = $SearchGroups;
968 }
969 else
970 {
971 # otherwise, convert legacy array into SearchParameterSet
972 $SearchParams = new SearchParameterSet();
973 $SearchParams->SetFromLegacyArray($SearchGroups);
974 }
975
976 # perform search
977 $Results = $this->Search(
978 $SearchParams, $StartingResult, $NumberOfResults,
979 $SortByField, $SortDescending);
980
981 # pull out the resoults for the Resource schema
982 if (isset($Results[MetadataSchema::SCHEMAID_DEFAULT]))
983 {
984 $Results = $Results[MetadataSchema::SCHEMAID_DEFAULT];
985 }
986 else
987 {
988 $Results = array();
989 }
990
991 # return the results
992 return $Results;
993 }
994
1000 public static function ConvertToDisplayParameters($SearchParams)
1001 {
1002 # create display parameters, used to make a more user-friendly
1003 # version of the search
1004 $DisplayParams = new SearchParameterSet();
1005
1006 # copy keyword searches as is
1007 $DisplayParams->AddParameter(
1008 $SearchParams->GetKeywordSearchStrings() );
1009
1010 # copy field searches as is
1011 $SearchStrings = $SearchParams->GetSearchStrings();
1012 foreach ($SearchStrings as $FieldId => $Params)
1013 {
1014 $DisplayParams->AddParameter($Params, $FieldId);
1015 }
1016
1017 # iterate over the search groups, looking for the 'is or begins
1018 # with' group that we add when faceting
1019 $Groups = $SearchParams->GetSubgroups();
1020 foreach ($Groups as $Group)
1021 {
1022 # if this group uses OR logic for a single field
1023 if ($Group->Logic() == "OR" &&
1024 count($Group->GetFields()) == 1)
1025 {
1026 # pull out the search strings for this field
1027 $SearchStrings = $Group->GetSearchStrings();
1028 $FieldId = key($SearchStrings);
1029 $Values = current($SearchStrings);
1030
1031 # check if this field is valid anywhere
1032 if (MetadataSchema::FieldExistsInAnySchema($FieldId))
1033 {
1034 $Field = new MetadataField($FieldId);
1035 # if this is a tree field, and we're doing a prefix match
1036 # on it, we'll just want to display this subgroup as an "IS"
1037 # otherwise display the group unmodified
1038 if ($Field->Type() == MetadataSchema::MDFTYPE_TREE &&
1039 preg_match('/^=(.*)$/', $Values[0], $FirstMatch) &&
1040 preg_match('/^\\^(.*) -- $/', $Values[1], $SecondMatch) &&
1041 $FirstMatch[1] == $SecondMatch[1] )
1042 {
1043 $DisplayParams->AddParameter("=".$FirstMatch[1], $FieldId);
1044 }
1045 else
1046 {
1047 $DisplayParams->AddSet($Group);
1048 }
1049 }
1050 }
1051 else
1052 {
1053 # otherwise, just attempt to include the group as-is
1054 try
1055 {
1056 $DisplayParams->AddSet($Group);
1057 }
1058 catch (Exception $e)
1059 {
1060 # if group could not be added for any reason, continue
1061 }
1062 }
1063 }
1064
1065 return $DisplayParams;
1066 }
1067
1068 # ---- PRIVATE INTERFACE -------------------------------------------------
1069
1070 private $FieldTypes;
1071 private $Schemas;
1072
1073 private static $TaskPriority = ApplicationFramework::PRIORITY_BACKGROUND;
1074 private static $NumResourcesForFacets = 500;
1075
1084 private function GetTextComparisonSql($DBField, $Operator, $Value, $Prefix="")
1085 {
1086 # if we were given a prefix, add the necessary period so we can use it
1087 if (strlen($Prefix))
1088 {
1089 $Prefix = $Prefix.".";
1090 }
1091
1092 if ($Operator == "^")
1093 {
1094 $EscapedValue = str_replace(
1095 array("%", "_"),
1096 array("\%", "\_"),
1097 addslashes($Value));
1098 $Comparison = $Prefix."`".$DBField."` LIKE '".$EscapedValue."%' ";
1099 }
1100 elseif ($Operator == '$')
1101 {
1102 $EscapedValue = str_replace(
1103 array("%", "_"),
1104 array("\%", "\_"),
1105 addslashes($Value));
1106 $Comparison = $Prefix."`".$DBField."` LIKE '%".$EscapedValue."' ";
1107 }
1108 elseif ($Operator == '!=')
1109 {
1110 $Comparison =
1111 "(".$Prefix."`".$DBField."` ".$Operator." '".addslashes($Value)."'"
1112 ." AND ".$Prefix."`".$DBField."` IS NOT NULL)";
1113 }
1114 else
1115 {
1116 $Comparison = $Prefix."`".$DBField."` "
1117 .$Operator." '".addslashes($Value)."' ";
1118 }
1119 return $Comparison;
1120 }
1121
1129 private static function FindParentClass($ClassId)
1130 {
1131 static $ParentMap;
1132
1133 # first time through, fetch the mapping of parent values we need
1134 if (!isset($ParentMap))
1135 {
1136 $DB = new Database();
1137
1138 # result here will be a parent/child mapping for all used
1139 # classifications; avoid caching it as it can be quite large
1140 $PreviousSetting = $DB->Caching();
1141 $DB->Caching(FALSE);
1142 $DB->Query(
1143 "SELECT ParentId, ClassificationId FROM Classifications "
1144 ."WHERE DEPTH > 0 AND FullResourceCount > 0 "
1145 ."AND FieldId IN (SELECT FieldId FROM MetadataFields "
1146 ." WHERE IncludeInFacetedSearch=1)"
1147 );
1148 $DB->Caching($PreviousSetting);
1149
1150 $ParentMap = $DB->FetchColumn("ParentId", "ClassificationId");
1151 }
1152
1153 return isset($ParentMap[$ClassId]) ? $ParentMap[$ClassId] : FALSE;
1154 }
1155 }
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:925
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:934
MetadataSchema\MDFTYPE_REFERENCE
const MDFTYPE_REFERENCE
Definition: MetadataSchema.php:35
SPTSearchEngine\UpdateForResource
UpdateForResource($ItemId)
Compat function for old SPT code to update resources.
Definition: SPTSearchEngine.php:915
MetadataSchema\MDFSTAT_OK
const MDFSTAT_OK
Definition: MetadataSchema.php:43
MetadataSchema\MDFTYPE_NUMBER
const MDFTYPE_NUMBER
Definition: MetadataSchema.php:23
SearchEngine\UpdateForItem
UpdateForItem($ItemId, $ItemType)
Update search database for the specified item.
Definition: SearchEngine.php:317
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:682
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:733
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:960
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:2213
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:758
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:1000
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
SPTSearchEngine\QueueUpdateForItem
static QueueUpdateForItem($ItemOrItemId, $TaskPriority=NULL)
Queue background update for an item.
Definition: SPTSearchEngine.php:695