CWIS Developer Documentation

- Home
- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

QuickSearchHelper.php
Go to the documentation of this file.
1 <?PHP
2 #
3 # FILE: QuickSearchHelper.php
4 #
5 # Part of the Collection Workflow Integration System (CWIS)
6 # Copyright 2002-2015 Edward Almasy and Internet Scout Research Group
7 # http://scout.wisc.edu/cwis/
8 #
9
14 class QuickSearchHelper
15 {
16
26 public static function SearchField(
27 MetadataField $Field,
28 $SearchString,
29 array $IdExclusions=array(),
30 array $ValueExclusions=array())
31 {
32 $MaxResults = $Field->NumAjaxResults();
33
34 switch ($Field->Type())
35 {
36 case MetadataSchema::MDFTYPE_USER:
37 return self::SearchForUsers(
38 $SearchString, $MaxResults, $IdExclusions, $ValueExclusions);
39
40 case MetadataSchema::MDFTYPE_REFERENCE:
41 if (count($ValueExclusions))
42 {
43 throw new Exception(
44 "Cannot exclude resource by value. "
45 ."Did you want IdExclusions instead?");
46 }
47
48 return self::SearchForResources(
49 $Field, $SearchString, $MaxResults, $IdExclusions);
50
51 default:
52 return self::SearchForValues(
53 $Field, $SearchString, $MaxResults, $IdExclusions, $ValueExclusions);
54 }
55 }
56
63 public static function HighlightSearchString($SearchTerms, $LabelForFormatting)
64 {
65 if(!is_array($SearchTerms))
66 {
67 $SearchTerms = array($SearchTerms);
68 }
69
70 foreach ($SearchTerms as $SearchString)
71 {
72 $SearchString = trim($SearchString);
73 $ExplodedSearch = preg_split('/\s+/', $SearchString);
74 $Patterns = array();
75 $Index = 0;
76 $InQuote = FALSE;
77
78 #Iterate through each term in the search string
79 foreach ($ExplodedSearch as $Term)
80 {
81 #Handle quoted terms differently
82 #if the first character is a quote
83 if ($Term[0] == '"')
84 {
85 $InQuote = TRUE;
86 }
87
88 if (substr($Term, -1) == '"')
89 {
90 #last character is a quote means that we've found the end of the term.
91 $InQuote = FALSE;
92 }
93
94 #remove all of the quotes if we're matched
95 $Term = str_replace('"', "", $Term);
96
97 #Add the term to the list of patterns we'll be highlighting in the result
98 # string at the current index (quoted terms will be appended to the index,
99 # unquoted terms are added at a new index).
100 $Patterns[$Index] = (isset($Patterns[$Index]) ?
101 $Patterns[$Index]." ":"").$Term;
102
103 if (!$InQuote)
104 {
105 # if we are not in a quoted term, the next term should go at
106 # a new index in the pattern array.
107 $Index++;
108 }
109 }
110
111 # iterate over our terms, escaping them and including bolding
112 # for segments two more ore characters longer
113 $PregPatterns = array();
114 foreach ($Patterns as $Term)
115 {
116 if (strlen($Term)>=2)
117 {
118 $PregPatterns = "/".preg_quote($Term, "/")."/i";
119 }
120 }
121
122 # do the highlighting
123 $LabelForFormatting = preg_replace(
124 $PregPatterns, "<b>$0</b>", $LabelForFormatting);
125
126 }
127
128 return $LabelForFormatting;
129 }
130
141 public static function PrintQuickSearchField(
142 $FieldId, $CurrentValue, $CurrentDisplayValue,
143 $CloneAfter=FALSE, $FormFieldName=NULL)
144 {
145 $GLOBALS["AF"]->RequireUIFile('jquery-ui.css', ApplicationFramework::ORDER_FIRST);
146 $GLOBALS["AF"]->RequireUIFile("jquery-ui.js");
147 $GLOBALS["AF"]->RequireUIFile("CW-QuickSearch.js");
148
149 if (is_numeric($FieldId))
150 {
151 $Field = new MetadataField($FieldId);
152
153 if ($FormFieldName === NULL)
154 {
155 $FormFieldName = "F_".$Field->DBFieldName();
156 }
157 $SafeFieldId = intval($FieldId);
158 }
159 elseif ($FieldId == "UserSearch")
160 {
161 if ($FormFieldName === NULL)
162 {
163 throw new Exception(
164 "FormFieldName required for User Quick Search elements.");
165 }
166 $SafeFieldId = $FieldId;
167 }
168 else
169 {
170 throw new Exception("Invalid FieldId");
171 }
172
173
174 $SafeCurrentValue = defaulthtmlentities($CurrentValue);
175 $SafeCurrentDisplayValue = defaulthtmlentities($CurrentDisplayValue);
176 // @codingStandardsIgnoreStart
177 ?>
178 <div class="cw-quicksearch cw-quicksearch-fieldid-<?= $SafeFieldId ?>"
179 data-fieldid="<?= $SafeFieldId ?>">
180 <textarea class="cw-quicksearch-display cw-resourceeditor-metadatafield
181 <?= $FormFieldName; ?> cw-autoresize"><?= $SafeCurrentDisplayValue ?></textarea>
182 <input name="<?= $FormFieldName ?>[]"
183 class="cw-quicksearch-value" type="hidden" value="<?= $SafeCurrentValue ?>" />
184 <div style="display: none;" class='cw-quicksearch-menu'>
185 <div class='cw-quicksearch-message ui-front'></div>
186 </div>
187 </div>
188 <?PHP if ($CloneAfter) {?>
189 <div class="cw-quicksearch-template cw-quicksearch cw-quicksearch-fieldid-<?= $SafeFieldId; ?>"
190 style="display: none;" data-fieldid="<?= $SafeFieldId; ?>">
191 <textarea class="cw-quicksearch-display cw-resourceeditor-metadatafield
192 <?= $FormFieldName; ?> cw-autoresize"></textarea>
193 <input name="<?= $FormFieldName; ?>[]"
194 class="cw-quicksearch-value" type="hidden" value="<?= $SafeCurrentValue ?>" />
195 <div style="display: none;" class='cw-quicksearch-menu'>
196 <div class='cw-quicksearch-message ui-front'></div>
197 </div>
198 </div>
199 <?PHP
200 }
201 // @codingStandardsIgnoreEnd
202 }
203
213 public static function SearchForUsers(
214 $SearchString,
215 $MaxResults = 15,
216 array $IdExclusions=array(),
217 array $ValueExclusions=array())
218 {
219 # the factory used for searching
220 $UserFactory = new CWUserFactory();
221
222 # get the minimum word length for fuzzy query matching
223 $MysqlSysVars = new MysqlSystemVariables($GLOBALS["DB"]);
224 $MinLen = intval($MysqlSysVars->Get("ft_min_word_len"));
225
226 # initialize the result variables
227 $SearchResults = array();
228 $ResultsNeeded = $MaxResults;
229
230 # if the search string is less than the minimum length, do exact query
231 # matching first
232 if (strlen($SearchString) < $MinLen)
233 {
234 $SearchResults = $UserFactory->FindUserNames(
235 $SearchString,
236 "UserName", "UserName", 0, # defaults
237 PHP_INT_MAX,
238 $IdExclusions,
239 $ValueExclusions);
240
241 # decrement the max results by how many were found
242 $ResultsNeeded -= count($SearchResults);
243 }
244
245 # if there are still some results to fetch, perform fuzzy matching
246 if ($ResultsNeeded > 0)
247 {
248 $FuzzyResults = $UserFactory->GetMatchingUsers(
249 $SearchString,
250 "UserName");
251
252 # filter results based on Id and Value exclusions
253 foreach ($FuzzyResults as $UserId => $Result)
254 {
255 if (!in_array($UserId, $IdExclusions) &&
256 !in_array($Result["UserName"], $ValueExclusions))
257 {
258 $SearchResults[$UserId] = $Result["UserName"];
259 }
260 }
261 }
262
263 # slice out just the results we want
264 $TotalResults = count($SearchResults);
265 $SearchResults = array_slice($SearchResults, 0, $MaxResults, TRUE);
266
267 $NumResults = count($SearchResults);
268 $NumAdditionalResults = $TotalResults - $NumResults;
269
270 return array($NumResults, $NumAdditionalResults, $SearchResults);
271 }
272
278 private static function PrepareSearchString($SearchString)
279 {
280 # remove "--", which causes searches to fail and is often in classifications
281 # Also remove unwanted punctuation
282 $SearchString = str_replace(
283 array("--",",",".", ":"), " ", $SearchString);
284
285 # split the search string into words
286 $Words = preg_split('/\s+/', $SearchString, -1, PREG_SPLIT_NO_EMPTY);
287
288 # the variable that holds the prepared search string
289 $PreparedSearchString = "";
290 $InQuotedString = FALSE;
291
292 # iterate over all the words
293 foreach ($Words as $Word)
294 {
295 # include quoted strings directly
296 $InQuotedString |= preg_match('/^[+-]?"/', $Word);
297 if ($InQuotedString)
298 {
299 $PreparedSearchString .= $Word." ";
300 $InQuotedString &= (substr($Word, -1) != '"');
301 }
302 # append a * to every word outside a quoted string
303 elseif (strlen($Word)>1)
304 {
305 $PreparedSearchString .= $Word."* ";
306 }
307 }
308
309 # clean up trailing whitespace, ensure quotes are closed
310 $PreparedSearchString = trim($PreparedSearchString);
311 if ($InQuotedString)
312 {
313 $PreparedSearchString .= '"';
314 }
315
316 return $PreparedSearchString;
317 }
318
343 private static function SortSearchResults($Results, $SearchString, $MaxResults)
344 {
345 $Matches = array(
346 "Exact" => array(),
347 "End" => array(),
348 "BegSp" => array(),
349 "Beg" => array(),
350 "MidSp" => array(),
351 "Mid" => array(),
352 "Other" => array() );
353
354 # escape regex characters
355 $SafeStr = preg_quote( trim( preg_replace('/\s+/', " ",
356 str_replace( array("--",",",".", ":"), " ",
357 $SearchString) )), '/');
358
359 # iterate over search results, sorting them into bins
360 foreach ($Results as $Key => $Val)
361 {
362 # apply the same normalization to our value as we did our search string
363 $TestVal = preg_quote( trim( preg_replace('/\s+/', " ",
364 str_replace( array("--",",",".", ":"), " ",
365 $Val) )), '/');
366
367 if (preg_match('/^'.$SafeStr.'$/i', $TestVal))
368 {
369 $ix = "Exact";
370 }
371 elseif (preg_match('/^'.$SafeStr.'\\W/i', $TestVal))
372 {
373 $ix = "BegSp";
374 }
375 elseif (preg_match('/^'.$SafeStr.'/i', $TestVal))
376 {
377 $ix = "Beg";
378 }
379 elseif (preg_match('/'.$SafeStr.'$/i', $TestVal))
380 {
381 $ix = "End";
382 }
383 elseif (preg_match('/'.$SafeStr.'\\W/i', $TestVal))
384 {
385 $ix = "MidSp";
386 }
387 elseif (preg_match('/'.$SafeStr.'/i', $TestVal))
388 {
389 $ix = "Mid";
390 }
391 else
392 {
393 $ix = "Other";
394 }
395
396 $Matches[$ix][$Key] = $Val;
397 }
398
399 # assemble the sorted results
400 $SortedResults = array();
401 foreach (array("Exact", "BegSp", "Beg", "End", "MidSp", "Mid", "Other") as $ix)
402 {
403 asort( $Matches[$ix] );
404 $SortedResults += $Matches[$ix];
405 }
406
407 # trim down the list to the requested number
408 $SortedResults = array_slice($SortedResults, 0, $MaxResults, TRUE);
409
410 return $SortedResults;
411 }
412
424 private static function SearchForResources(
425 $DstField,
426 $SearchString,
427 $MaxResults,
428 array $IdExclusions=array() )
429 {
430 # construct search groups based on the keyword
431 $SearchParams = new SearchParameterSet();
432 $SearchParams->AddParameter($SearchString);
433
434 $SignalResult = $GLOBALS["AF"]->SignalEvent(
435 "EVENT_FIELD_SEARCH_FILTER",
436 array(
437 "Search" => $SearchParams,
438 "Field" => $DstField));
439 $SearchParams = $SignalResult["Search"];
440
441 # perform search
442 $SearchEngine = new SPTSearchEngine();
443 $SearchResults = $SearchEngine->SearchAll($SearchParams);
444
445 # get the list of referenceable schemas for this field
446 $ReferenceableSchemaIds = $DstField->ReferenceableSchemaIds();
447
448 # iterate over search results from desired schemas
449 $SearchResultsNew = array();
450 foreach ($SearchResults as $SchemaId => $SchemaResults)
451 {
452 if (in_array($SchemaId, $ReferenceableSchemaIds))
453 {
454 # filter resources the user cannot see
455 $RFactory = new ResourceFactory($SchemaId);
456 $ViewableIds = $RFactory->FilterNonViewableResources(
457 array_keys($SchemaResults), $GLOBALS["G_User"]);
458
459 # add these results to our list of all search results
460 $SearchResultsNew += array_intersect_key(
461 $SchemaResults, array_flip($ViewableIds));
462 }
463 }
464 $SearchResults = $SearchResultsNew;
465
466 # filter out excluded resource IDs if necessary
467 if (count($IdExclusions))
468 {
469 $SearchResults = array_diff_key(
470 $SearchResults, array_flip($IdExclusions));
471 }
472
473 # pull out mapped titles for all resources
474 $GLOBALS["AF"]->LoadFunction("GetResourceFieldValue");
475 $ResourceData = array();
476 foreach ($SearchResults as $ResourceId => $Score)
477 {
478 $Resource = new Resource($ResourceId);
479 $ResourceData[$ResourceId] = GetResourceFieldValue(
480 $Resource,
481 $Resource->Schema()->GetFieldByMappedName("Title") );
482 }
483
484 # determine how many results we had in total
485 $TotalResults = count($ResourceData);
486
487 # sort resources by title and subset if necessary
488 $ResourceData = self::SortSearchResults(
489 $ResourceData,
490 $SearchString,
491 $MaxResults);
492
493 # compute the number of available and additional results
494 $NumSearchResults = count($ResourceData);
495 $NumAdditionalSearchResults = $TotalResults - count($ResourceData);
496
497 return array($NumSearchResults, $NumAdditionalSearchResults, $ResourceData);
498 }
499
511 private static function SearchForValues(
512 MetadataField $Field,
513 $SearchString,
514 $MaxResults,
515 array $IdExclusions,
516 array $ValueExclusions)
517 {
518 $Factory = $Field->GetFactory();
519
520 # get the minimum word length for fuzzy query matching
521 $MysqlSysVars = new MysqlSystemVariables($GLOBALS["DB"]);
522 $MinLen = intval($MysqlSysVars->Get("ft_min_word_len"));
523
524 # initialize the result variables
525 $Results = array();
526 $Total = 0;
527
528 $SignalResult = $GLOBALS["AF"]->SignalEvent(
529 "EVENT_FIELD_SEARCH_FILTER",
530 array(
531 "Search" => $SearchString,
532 "Field" => $Field));
533 $SearchString = $SignalResult["Search"];
534
535 # if the search string is less than the minimum length, do exact query
536 # matching first
537 if (strlen($SearchString) < $MinLen)
538 {
539 # search for results and get the total
540 $Results += $Factory->SearchForItemNames(
541 $SearchString,
542 $MaxResults,
543 FALSE, TRUE, 0, # defaults
544 $IdExclusions,
545 $ValueExclusions);
546 $Total += $Factory->GetCountForItemNames(
547 $SearchString,
548 FALSE, TRUE, # defaults,
549 $IdExclusions,
550 $ValueExclusions);
551
552 # decrement the max results by how many were returned when doing exact
553 # matching
554 $MaxResults -= count($Results);
555 }
556
557 # if more results should be fetched
558 if ($MaxResults > 0)
559 {
560 $PreparedSearchString = self::PrepareSearchString($SearchString);
561
562 if (strlen($SearchString) >= $MinLen)
563 {
564 $Results += $Factory->FindMatchingRecentlyUsedValues(
565 $PreparedSearchString, 5, $IdExclusions, $ValueExclusions);
566
567 if (count($Results))
568 {
569 $Results += array(-1 => "<hr>");
570 }
571 }
572
573 # search for results and get the total
574 $Results += self::SortSearchResults(
575 $Factory->SearchForItemNames(
576 $PreparedSearchString,
577 2000,
578 FALSE, TRUE, 0, # defaults
579 $IdExclusions,
580 $ValueExclusions),
581 $SearchString,
582 $MaxResults);
583 $Total += $Factory->GetCountForItemNames(
584 $PreparedSearchString,
585 FALSE, TRUE, # defaults,
586 $IdExclusions,
587 $ValueExclusions);
588 }
589
590 # get additional totals
591 $NumSearchResults = count($Results);
592 $NumAdditionalSearchResults = $Total - $NumSearchResults;
593
594 return array($NumSearchResults, $NumAdditionalSearchResults, $Results);
595 }
596 }
SPTSearchEngine
Definition: SPTSearchEngine.php:10
SearchParameterSet
Set of parameters used to perform a search.
Definition: SearchParameterSet.php:13
MetadataSchema\MDFTYPE_REFERENCE
const MDFTYPE_REFERENCE
Definition: MetadataSchema.php:35
MetadataField\GetFactory
GetFactory()
Retrieve item factory object for this field.
Definition: MetadataField.php:1554
QuickSearchHelper\HighlightSearchString
static HighlightSearchString($SearchTerms, $LabelForFormatting)
Highlight all instances of the search string in the result label.
Definition: QuickSearchHelper.php:63
QuickSearchHelper\PrintQuickSearchField
static PrintQuickSearchField($FieldId, $CurrentValue, $CurrentDisplayValue, $CloneAfter=FALSE, $FormFieldName=NULL)
Print the blank text field quick search field for the QuickSearch JS object.
Definition: QuickSearchHelper.php:141
QuickSearchHelper\SearchField
static SearchField(MetadataField $Field, $SearchString, array $IdExclusions=array(), array $ValueExclusions=array())
Search a field for values matching a specified search string.
Definition: QuickSearchHelper.php:26
MysqlSystemVariables
Class that allows permits easier access to MySQL system variables.
Definition: MysqlSystemVariables.php:13
CWUserFactory
CWIS-specific user factory class.
Definition: CWUserFactory.php:13
PHP
PHP
Definition: RSSClient--Test.php:12
ApplicationFramework\ORDER_FIRST
const ORDER_FIRST
Handle item first (i.e.
Definition: ApplicationFramework.php:2504
MetadataField
Object representing a locally-defined type of metadata field.
Definition: MetadataField.php:13
MetadataField\NumAjaxResults
NumAjaxResults($NewValue=DB_NOVALUE)
Get/set the maximum number of results to display in an AJAX dropdown.
Definition: MetadataField.php:905
Resource
Represents a "resource" in CWIS.
Definition: Resource.php:13
QuickSearchHelper\SearchForUsers
static SearchForUsers($SearchString, $MaxResults=15, array $IdExclusions=array(), array $ValueExclusions=array())
Perform a search for users.
Definition: QuickSearchHelper.php:213
MetadataSchema\MDFTYPE_USER
const MDFTYPE_USER
Definition: MetadataSchema.php:30
MetadataField\Type
Type($NewValue=DB_NOVALUE)
Get/set type of metadata field (enumerated value).
Definition: MetadataField.php:45
QuickSearchHelper
Convenience class for QuickSearch responses, making it easy to share functions common to different ty...
Definition: QuickSearchHelper.php:14
ResourceFactory
Factory for Resource objects.
Definition: ResourceFactory.php:13