1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\Elastica; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* Utility methods to help with searching functions, and also testable without fixtures |
7
|
|
|
*/ |
8
|
|
|
class ElasticaUtil { |
9
|
|
|
|
10
|
|
|
/** |
11
|
|
|
* Marker string for pre highlight - can be any string unlikely to appear in a search |
12
|
|
|
*/ |
13
|
|
|
private static $pre_marker = " |PREZXCVBNM12345678"; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* Marker string for psot highlight - can be any string unlikely to appear in a search |
17
|
|
|
*/ |
18
|
|
|
private static $post_marker = "POSTZXCVBNM12345678| "; |
19
|
|
|
|
20
|
|
|
|
21
|
|
|
/** |
22
|
|
|
* Function to display messages only if using the command line |
23
|
|
|
* @var string $content Text to display when in command line mode |
24
|
|
|
*/ |
25
|
|
|
public static function message($content) { |
26
|
|
|
if (\Director::is_cli()) { |
27
|
|
|
echo "$content\n"; |
28
|
|
|
} |
29
|
|
|
} |
30
|
|
|
|
31
|
|
|
|
32
|
|
|
/* |
33
|
|
|
Display a human readable yes or no |
34
|
|
|
*/ |
35
|
|
|
public static function showBooleanHumanReadable($assertion) { |
36
|
|
|
return $assertion ? 'Yes' : 'No'; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
public static function getPhraseSuggestion($alternativeQuerySuggestions) { |
41
|
|
|
$originalQuery = $alternativeQuerySuggestions[0]['text']; |
42
|
|
|
|
43
|
|
|
$highlightsCfg = \Config::inst()->get('Elastica', 'Highlights'); |
44
|
|
|
$preTags = $highlightsCfg['PreTags']; |
45
|
|
|
$postTags = $highlightsCfg['PostTags']; |
46
|
|
|
|
47
|
|
|
//Use the first suggested phrase |
48
|
|
|
$options = $alternativeQuerySuggestions[0]['options']; |
49
|
|
|
|
50
|
|
|
$resultArray = null; |
51
|
|
|
|
52
|
|
|
if (sizeof($options) > 0) { |
53
|
|
|
//take the first suggestion |
54
|
|
|
$suggestedPhrase = $options[0]['text']; |
55
|
|
|
$suggestedPhraseHighlighted = $options[0]['highlighted']; |
56
|
|
|
|
57
|
|
|
// now need to fix capitalisation |
58
|
|
|
$originalParts = explode(' ', $originalQuery); |
59
|
|
|
$suggestedParts = explode(' ', $suggestedPhrase); |
60
|
|
|
|
61
|
|
|
$markedHighlightedParts = ' '.$suggestedPhraseHighlighted.' '; |
62
|
|
|
$markedHighlightedParts = str_replace(' '.$preTags, ' '.self::$pre_marker, $markedHighlightedParts); |
63
|
|
|
|
64
|
|
|
$markedHighlightedParts = str_replace($postTags.' ', self::$post_marker, $markedHighlightedParts); |
65
|
|
|
|
66
|
|
|
$markedHighlightedParts = trim($markedHighlightedParts); |
67
|
|
|
$markedHighlightedParts = trim($markedHighlightedParts); |
68
|
|
|
|
69
|
|
|
$highlightedParts = preg_split('/\s+/', $markedHighlightedParts); |
70
|
|
|
|
71
|
|
|
//Create a mapping of lowercase to uppercase terms |
72
|
|
|
$lowerToUpper = array(); |
73
|
|
|
$lowerToHighlighted = array(); |
74
|
|
|
$ctr = 0; |
75
|
|
|
foreach ($suggestedParts as $lowercaseWord) { |
76
|
|
|
$lowerToUpper[$lowercaseWord] = $originalParts[$ctr]; |
77
|
|
|
$lowerToHighlighted[$lowercaseWord] = $highlightedParts[$ctr]; |
78
|
|
|
$ctr++; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
$plain = array(); |
82
|
|
|
$highlighted = array(); |
83
|
|
|
foreach ($suggestedParts as $lowercaseWord) { |
84
|
|
|
$possiblyUppercase = $lowerToUpper[$lowercaseWord]; |
85
|
|
|
$possiblyUppercaseHighlighted = $lowerToHighlighted[$lowercaseWord]; |
86
|
|
|
|
87
|
|
|
//If the terms are identical other than case, e.g. new => New, then simply swap |
88
|
|
|
if (strtolower($possiblyUppercase) == $lowercaseWord) { |
89
|
|
|
array_push($plain, $possiblyUppercase); |
90
|
|
|
array_push($highlighted, $possiblyUppercase); |
91
|
|
|
} else { |
92
|
|
|
//Need to check capitalisation of terms suggested that are different |
93
|
|
|
|
94
|
|
|
$chr = mb_substr ($possiblyUppercase, 0, 1, "UTF-8"); |
95
|
|
|
if (mb_strtolower($chr, "UTF-8") != $chr) { |
96
|
|
|
$upperLowercaseWord = $lowercaseWord; |
97
|
|
|
$upperLowercaseWord[0] = $chr; |
98
|
|
|
|
99
|
|
|
//$possiblyUppercaseHighlighted = str_replace($lowercaseWord, $possiblyUppercase, $possiblyUppercaseHighlighted); |
|
|
|
|
100
|
|
|
$withHighlights = str_replace($lowercaseWord, $upperLowercaseWord, $possiblyUppercaseHighlighted); |
101
|
|
|
|
102
|
|
|
$lowercaseWord[0] = $chr; |
103
|
|
|
|
104
|
|
|
//str_replace(search, replace, subject) |
|
|
|
|
105
|
|
|
|
106
|
|
|
array_push($plain, $lowercaseWord); |
107
|
|
|
array_push($highlighted, $withHighlights); |
108
|
|
|
} else { |
109
|
|
|
//No need to capitalise, so add suggested word |
110
|
|
|
array_push($plain, $lowercaseWord); |
111
|
|
|
|
112
|
|
|
//No need to capitalise, so add suggested highlighted word |
113
|
|
|
array_push($highlighted, $possiblyUppercaseHighlighted); |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$highlighted = ' '.implode(' ', $highlighted).' '; |
119
|
|
|
$highlighted = str_replace(self::$pre_marker, ' '.$preTags, $highlighted); |
120
|
|
|
$highlighted = str_replace(self::$post_marker, $postTags.' ', $highlighted); |
121
|
|
|
|
122
|
|
|
$resultArray['suggestedQuery'] = implode(' ', $plain); |
123
|
|
|
$resultArray['suggestedQueryHighlighted'] = trim($highlighted); |
124
|
|
|
} |
125
|
|
|
return $resultArray; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* The output format of this function is not documented, so at best this is guess work to an |
131
|
|
|
* extent. Possible formats are: |
132
|
|
|
* - ((Title.standard:great Content.standard:ammunition Content.standard:could |
133
|
|
|
* Content.standard:bair Content.standard:dancing Content.standard:column |
134
|
|
|
* Content.standard:company Content.standard:infantry Content.standard:men |
135
|
|
|
* Content.standard:soldier Content.standard:brigade Content.standard:zealand |
136
|
|
|
* Content.standard:new)~3) |
137
|
|
|
* -ConstantScore(_uid:GutenbergBookExtract#1519) |
138
|
|
|
* (Description: bay Description: mannerstram) |
139
|
|
|
* |
140
|
|
|
* @param string $explanation explanation string for more like this terms from Elasticsearch |
141
|
|
|
* @return array Array of fieldnames mapped to terms |
142
|
|
|
*/ |
143
|
|
|
public static function parseSuggestionExplanation($explanation) { |
144
|
|
|
|
145
|
|
|
$explanation = explode('-ConstantScore', $explanation)[0]; |
146
|
|
|
|
147
|
|
|
$bracketPos = strpos($explanation, ')~'); |
148
|
|
|
|
149
|
|
|
if (substr($explanation, 0,2) == '((') { |
150
|
|
|
$explanation = substr($explanation, 2, $bracketPos-2); |
151
|
|
|
} elseif (substr($explanation, 0,1) == '(') { |
152
|
|
|
$explanation = substr($explanation, 1, $bracketPos-2); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$terms = array(); |
156
|
|
|
|
157
|
|
|
//Field name(s) => terms |
158
|
|
|
$splits = explode(' ', $explanation); |
159
|
|
|
|
160
|
|
|
foreach ($splits as $fieldAndTerm) { |
161
|
|
|
$splits = explode(':', $fieldAndTerm); |
162
|
|
|
|
163
|
|
|
// This is the no terms case |
164
|
|
|
if (sizeof($splits) < 2) { |
165
|
|
|
break; |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
$fieldname = $splits[0]; |
169
|
|
|
$term = $splits[1]; |
170
|
|
|
|
171
|
|
|
if (!isset($terms[$fieldname])) { |
172
|
|
|
$terms[$fieldname] = array(); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
array_push($terms[$fieldname], $term); |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return $terms; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Add attributes necessary for jQuery to execute autocomplete |
183
|
|
|
* @param FormField &$queryField field used to type a search query |
184
|
|
|
*/ |
185
|
|
|
public static function addAutocompleteToQueryField(&$queryField, $classesToSearch, $siteTreeOnly, $link, $slug) { |
186
|
|
|
if($this->AutoCompleteFieldID > 0) { |
|
|
|
|
187
|
|
|
$queryField->setAttribute('data-autocomplete', 'true'); |
188
|
|
|
$queryField->setAttribute('data-autocomplete-field', 'Title'); |
189
|
|
|
$queryField->setAttribute('data-autocomplete-classes', $classesToSearch); |
190
|
|
|
$queryField->setAttribute('data-autocomplete-sitetree', $siteTreeOnly); |
191
|
|
|
$queryField->setAttribute('data-autocomplete-source', $link); |
192
|
|
|
$queryField->setAttribute('data-autocomplete-function', $slug); |
193
|
|
|
} |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.