1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SilverStripe\CMS\Search; |
4
|
|
|
|
5
|
|
|
use SilverStripe\CMS\Model\SiteTree; |
6
|
|
|
use SilverStripe\Control\Controller; |
7
|
|
|
use SilverStripe\Forms\FieldList; |
8
|
|
|
use SilverStripe\Forms\Form; |
9
|
|
|
use SilverStripe\Forms\FormAction; |
10
|
|
|
use SilverStripe\Forms\HiddenField; |
11
|
|
|
use SilverStripe\Forms\TextField; |
12
|
|
|
use SilverStripe\ORM\DB; |
13
|
|
|
use SilverStripe\ORM\SS_List; |
14
|
|
|
use Translatable; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Standard basic search form which conducts a fulltext search on all {@link SiteTree} |
18
|
|
|
* objects. |
19
|
|
|
* |
20
|
|
|
* If multilingual content is enabled through the {@link Translatable} extension, |
21
|
|
|
* only pages the currently set language on the holder for this searchform are found. |
22
|
|
|
* The language is set through a hidden field in the form, which is prepoluated |
23
|
|
|
* with {@link Translatable::get_current_locale()} when then form is constructed. |
24
|
|
|
* |
25
|
|
|
* @see Use ModelController and SearchContext for a more generic search implementation based around DataObject |
26
|
|
|
*/ |
27
|
|
|
class SearchForm extends Form { |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* @var int $pageLength How many results are shown per page. |
31
|
|
|
* Relies on pagination being implemented in the search results template. |
32
|
|
|
*/ |
33
|
|
|
protected $pageLength = 10; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* Classes to search |
37
|
|
|
*/ |
38
|
|
|
protected $classesToSearch = array( |
39
|
|
|
"SilverStripe\\CMS\\Model\\SiteTree", |
40
|
|
|
"SilverStripe\\Assets\\File" |
41
|
|
|
); |
42
|
|
|
|
43
|
|
|
private static $casting = array( |
44
|
|
|
'SearchQuery' => 'Text' |
45
|
|
|
); |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* |
49
|
|
|
* @param Controller $controller |
50
|
|
|
* @param string $name The name of the form (used in URL addressing) |
51
|
|
|
* @param FieldList $fields Optional, defaults to a single field named "Search". Search logic needs to be customized |
52
|
|
|
* if fields are added to the form. |
53
|
|
|
* @param FieldList $actions Optional, defaults to a single field named "Go". |
54
|
|
|
*/ |
55
|
|
|
public function __construct($controller, $name, $fields = null, $actions = null) { |
56
|
|
|
if(!$fields) { |
57
|
|
|
$fields = new FieldList( |
58
|
|
|
new TextField('Search', _t('SearchForm.SEARCH', 'Search') |
59
|
|
|
)); |
60
|
|
|
} |
61
|
|
|
|
62
|
|
|
if(class_exists('Translatable') |
63
|
|
|
&& SiteTree::singleton()->hasExtension('Translatable') |
64
|
|
|
) { |
65
|
|
|
$fields->push(new HiddenField('searchlocale', 'searchlocale', Translatable::get_current_locale())); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
if(!$actions) { |
69
|
|
|
$actions = new FieldList( |
70
|
|
|
new FormAction("getResults", _t('SearchForm.GO', 'Go')) |
71
|
|
|
); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
parent::__construct($controller, $name, $fields, $actions); |
75
|
|
|
|
76
|
|
|
$this->setFormMethod('get'); |
77
|
|
|
|
78
|
|
|
$this->disableSecurityToken(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Set the classes to search. |
83
|
|
|
* Currently you can only choose from "SiteTree" and "File", but a future version might improve this. |
84
|
|
|
* |
85
|
|
|
* @param array $classes |
86
|
|
|
*/ |
87
|
|
|
public function classesToSearch($classes) { |
88
|
|
|
$supportedClasses = array('SilverStripe\\CMS\\Model\\SiteTree', 'SilverStripe\\Assets\\File'); |
89
|
|
|
$illegalClasses = array_diff($classes, $supportedClasses); |
90
|
|
|
if($illegalClasses) { |
|
|
|
|
91
|
|
|
user_error( |
92
|
|
|
"SearchForm::classesToSearch() passed illegal classes '" . implode("', '", $illegalClasses) |
93
|
|
|
. "'. At this stage, only File and SiteTree are allowed", |
94
|
|
|
E_USER_WARNING |
95
|
|
|
); |
96
|
|
|
} |
97
|
|
|
$legalClasses = array_intersect($classes, $supportedClasses); |
98
|
|
|
$this->classesToSearch = $legalClasses; |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Get the classes to search |
103
|
|
|
* |
104
|
|
|
* @return array |
105
|
|
|
*/ |
106
|
|
|
public function getClassesToSearch() { |
107
|
|
|
return $this->classesToSearch; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
/** |
111
|
|
|
* Return dataObjectSet of the results using $_REQUEST to get info from form. |
112
|
|
|
* Wraps around {@link searchEngine()}. |
113
|
|
|
* |
114
|
|
|
* @param int $pageLength DEPRECATED 2.3 Use SearchForm->pageLength |
115
|
|
|
* @param array $data Request data as an associative array. Should contain at least a key 'Search' with all searched keywords. |
116
|
|
|
* @return SS_List |
117
|
|
|
*/ |
118
|
|
|
public function getResults($pageLength = null, $data = null){ |
119
|
|
|
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.org tutorials |
120
|
|
|
if(!isset($data) || !is_array($data)) $data = $_REQUEST; |
121
|
|
|
|
122
|
|
|
// set language (if present) |
123
|
|
View Code Duplication |
if(class_exists('Translatable')) { |
|
|
|
|
124
|
|
|
if(SiteTree::singleton()->hasExtension('Translatable') && isset($data['searchlocale'])) { |
125
|
|
|
if($data['searchlocale'] == "ALL") { |
126
|
|
|
Translatable::disable_locale_filter(); |
127
|
|
|
} else { |
128
|
|
|
$origLocale = Translatable::get_current_locale(); |
129
|
|
|
|
130
|
|
|
Translatable::set_current_locale($data['searchlocale']); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
$keywords = $data['Search']; |
136
|
|
|
|
137
|
|
|
$andProcessor = create_function('$matches',' |
138
|
|
|
return " +" . $matches[2] . " +" . $matches[4] . " "; |
139
|
|
|
'); |
140
|
|
|
$notProcessor = create_function('$matches', ' |
141
|
|
|
return " -" . $matches[3]; |
142
|
|
|
'); |
143
|
|
|
|
144
|
|
|
$keywords = preg_replace_callback('/()("[^()"]+")( and )("[^"()]+")()/i', $andProcessor, $keywords); |
145
|
|
|
$keywords = preg_replace_callback('/(^| )([^() ]+)( and )([^ ()]+)( |$)/i', $andProcessor, $keywords); |
146
|
|
|
$keywords = preg_replace_callback('/(^| )(not )("[^"()]+")/i', $notProcessor, $keywords); |
147
|
|
|
$keywords = preg_replace_callback('/(^| )(not )([^() ]+)( |$)/i', $notProcessor, $keywords); |
148
|
|
|
|
149
|
|
|
$keywords = $this->addStarsToKeywords($keywords); |
150
|
|
|
|
151
|
|
|
if(!$pageLength) $pageLength = $this->pageLength; |
|
|
|
|
152
|
|
|
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0; |
153
|
|
|
|
154
|
|
|
if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) { |
155
|
|
|
$results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength, "\"Relevance\" DESC", "", true); |
156
|
|
|
} else { |
157
|
|
|
$results = DB::get_conn()->searchEngine($this->classesToSearch, $keywords, $start, $pageLength); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
// filter by permission |
161
|
|
|
if($results) foreach($results as $result) { |
162
|
|
|
if(!$result->canView()) $results->remove($result); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// reset locale |
166
|
|
View Code Duplication |
if(class_exists('Translatable')) { |
|
|
|
|
167
|
|
|
if(SiteTree::singleton()->hasExtension('Translatable') && isset($data['searchlocale'])) { |
168
|
|
|
if($data['searchlocale'] == "ALL") { |
169
|
|
|
Translatable::enable_locale_filter(); |
170
|
|
|
} else { |
171
|
|
|
Translatable::set_current_locale($origLocale); |
|
|
|
|
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
return $results; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
protected function addStarsToKeywords($keywords) { |
180
|
|
|
if(!trim($keywords)) return ""; |
181
|
|
|
// Add * to each keyword |
182
|
|
|
$splitWords = preg_split("/ +/" , trim($keywords)); |
183
|
|
|
$newWords = []; |
184
|
|
|
while(list($i,$word) = each($splitWords)) { |
|
|
|
|
185
|
|
|
if($word[0] == '"') { |
186
|
|
|
while(list($i,$subword) = each($splitWords)) { |
|
|
|
|
187
|
|
|
$word .= ' ' . $subword; |
188
|
|
|
if(substr($subword,-1) == '"') break; |
189
|
|
|
} |
190
|
|
|
} else { |
191
|
|
|
$word .= '*'; |
192
|
|
|
} |
193
|
|
|
$newWords[] = $word; |
194
|
|
|
} |
195
|
|
|
return implode(" ", $newWords); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Get the search query for display in a "You searched for ..." sentence. |
200
|
|
|
* |
201
|
|
|
* @param array $data |
202
|
|
|
* @return string |
203
|
|
|
*/ |
204
|
|
|
public function getSearchQuery($data = null) { |
205
|
|
|
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.org tutorials |
206
|
|
|
if(!isset($data)) { |
207
|
|
|
$data = $_REQUEST; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// The form could be rendered without the search being done, so check for that. |
211
|
|
|
if (isset($data['Search'])) { |
212
|
|
|
return $data['Search']; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
return null; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* Set the maximum number of records shown on each page. |
220
|
|
|
* |
221
|
|
|
* @param int $length |
222
|
|
|
*/ |
223
|
|
|
public function setPageLength($length) { |
224
|
|
|
$this->pageLength = $length; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @return int |
229
|
|
|
*/ |
230
|
|
|
public function getPageLength() { |
231
|
|
|
return $this->pageLength; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
|
237
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.