Completed
Push — dev2 ( 593651...2e5a51 )
by Gordon
03:37
created

setStartParamsFromRequest()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 7
rs 9.4286
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
3
use Elastica\Document;
4
use Elastica\Query;
5
use \SilverStripe\Elastica\ResultList;
6
use Elastica\Query\QueryString;
7
use Elastica\Aggregation\Filter;
8
use Elastica\Filter\Term;
9
use Elastica\Filter\BoolAnd;
10
use Elastica\Aggregation\Terms;
11
use Elastica\Query\Filtered;
12
use Elastica\Query\Range;
13
use \SilverStripe\Elastica\ElasticSearcher;
14
use \SilverStripe\Elastica\Searchable;
15
use \SilverStripe\Elastica\QueryGenerator;
16
use \SilverStripe\Elastica\ElasticaUtil;
17
18
class ElasticSearchPage_Controller extends Page_Controller {
19
20
	private static $allowed_actions = array('SearchForm', 'submit', 'index', 'similar');
21
22
	public function init() {
23
		parent::init();
24
25
		Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
26
		Requirements::javascript("elastica/javascript/jquery.autocomplete.js");
27
		Requirements::javascript("elastica/javascript/elastica.js");
28
		Requirements::css("elastica/css/elastica.css");
29
	}
30
31
32
33
	/*
34
	Find DataObjects in Elasticsearch similar to the one selected.  Note that aggregations are not
35
	taken into account, merely the text of the selected document.
36
	 */
37
	public function similar() {
38
		//FIXME double check security, ie if escaping needed
39
		$class = $this->request->param('ID');
40
		$instanceID = $this->request->param('OtherID');
41
42
		$data = array(
43
			'Content' => $this->Content,
44
			'Title' => $this->Title,
45
			'SearchPerformed' => false
46
		);
47
48
		// record the time
49
		$startTime = microtime(true);
50
51
		//instance of ElasticPage associated with this controller
52
		$ep = Controller::curr()->dataRecord;
53
54
		// use an Elastic Searcher, which needs primed from URL params
55
		$es = new ElasticSearcher();
56
57
		$this->setStartParamsFromRequest($es);
58
59
60
		$es->setMinTermFreq($this->MinTermFreq);
61
		$es->setMaxTermFreq($this->MaxTermFreq);
62
		$es->setMinDocFreq($this->MinDocFreq);
63
		$es->setMaxDocFreq($this->MaxDocFreq);
64
		$es->setMinWordLength($this->MinWordLength);
65
		$es->setMaxWordLength($this->MaxWordLength);
66
		$es->setMinShouldMatch($this->MinShouldMatch);
67
		$es->setSimilarityStopWords($this->SimilarityStopWords);
68
69
70
		// filter by class or site tree
71
		if($ep->SiteTreeOnly) {
72
			T7; //FIXME test missing
73
			$es->addFilter('IsInSiteTree', true);
74
		} else {
75
			$es->setClasses($ep->ClassesToSearch);
76
		}
77
78
79
		// get the edited fields to search from the database for this search page
80
		// Convert this into a name => weighting array
81
		$fieldsToSearch = array();
82
		$editedSearchFields = $this->ElasticaSearchableFields()->filter(array(
83
			'Active' => true,
84
			'SimilarSearchable' => true
85
		));
86
87
		foreach($editedSearchFields->getIterator() as $searchField) {
88
			$fieldsToSearch[$searchField->Name] = $searchField->Weight;
89
		}
90
91
		// Use the standard field for more like this, ie not stemmed
92
		foreach($fieldsToSearch as $field => $value) {
93
			$fieldsToSearch[$field . '.standard'] = $value;
94
			unset($fieldsToSearch[$field]);
95
		}
96
97
		try {
98
			// Simulate server being down for testing purposes
99
			if($this->request->getVar('ServerDown')) {
100
				throw new Elastica\Exception\Connection\HttpException('Unable to reach search server');
101
			}
102
			if(class_exists($class)) {
103
				$instance = \DataObject::get_by_id($class, $instanceID);
104
105
				$paginated = $es->moreLikeThis($instance, $fieldsToSearch);
106
107
				$this->Aggregations = $es->getAggregations();
108
				$data['SearchResults'] = $paginated;
109
				$data['SearchPerformed'] = true;
110
				$data['SearchPageLink'] = $ep->Link();
111
				$data['SimilarTo'] = $instance;
112
				$data['NumberOfResults'] = $paginated->getTotalItems();
113
114
115
				$moreLikeThisTerms = $paginated->getList()->MoreLikeThisTerms;
116
				$fieldToTerms = new ArrayList();
117
				foreach(array_keys($moreLikeThisTerms) as $fieldName) {
118
					$readableFieldName = str_replace('.standard', '', $fieldName);
119
					$fieldTerms = new ArrayList();
120
					foreach($moreLikeThisTerms[$fieldName] as $value) {
121
						$do = new DataObject();
122
						$do->Term = $value;
123
						$fieldTerms->push($do);
124
					}
125
126
					$do = new DataObject();
127
					$do->FieldName = $readableFieldName;
128
					$do->Terms = $fieldTerms;
129
					$fieldToTerms->push($do);
130
				}
131
132
				$data['SimilarSearchTerms'] = $fieldToTerms;
133
			} else {
134
				// class does not exist
135
				$data['ErrorMessage'] = "Class $class is either not found or not searchable\n";
136
			}
137
		} catch (\InvalidArgumentException $e) {
138
			$data['ErrorMessage'] = "Class $class is either not found or not searchable\n";
139
		} catch (Elastica\Exception\Connection\HttpException $e) {
140
			$data['ErrorMessage'] = 'Unable to connect to search server';
141
			$data['SearchPerformed'] = false;
142
		}
143
144
145
		// calculate time
146
		$endTime = microtime(true);
147
		$elapsed = round(100 * ($endTime - $startTime)) / 100;
148
149
		// store variables for the template to use
150
		$data['ElapsedTime'] = $elapsed;
151
		$data['Elapsed'] = $elapsed;
152
153
		// allow the optional use of overriding the search result page, e.g. for photos, maps or facets
154
		if($this->hasExtension('PageControllerTemplateOverrideExtension')) {
155
			return $this->useTemplateOverride($data);
156
		} else {
157
			return $data;
158
		}
159
	}
160
161
162
	/**
163
	 * Set the start page from the request and results per page for a given searcher object
164
	 * @param \SilverStripe\Elastica\ElasticSearcher &$elasticSearcher ElasticSearcher object
165
	 */
166
	private function setStartParamsFromRequest(&$elasticSearcher) {
167
		// start, and page length, i.e. pagination
168
		$startParam = $this->request->getVar('start');
169
		$start = isset($startParam) ? $startParam : 0;
170
		$elasticSearcher->setStart($start);
171
		$elasticSearcher->setPageLength($ep->ResultsPerPage);
0 ignored issues
show
Bug introduced by
The variable $ep does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
172
	}
173
174
175
	/*
176
	Display the search form. If the query parameter exists, search against Elastica
177
	and render results accordingly.
178
	 */
179
	public function index() {
180
		$data = array(
181
			'Content' => $this->Content,
182
			'Title' => $this->Title,
183
			'SearchPerformed' => false
184
		);
185
186
		// record the time
187
		$startTime = microtime(true);
188
189
		//instance of ElasticPage associated with this controller
190
		$ep = Controller::curr()->dataRecord;
191
192
		// use an Elastic Searcher, which needs primed from URL params
193
		$es = new ElasticSearcher();
194
195
		$this->setStartParamsFromRequest($es);
196
197
		// Do not show suggestions if this flag is set
198
		$ignoreSuggestions = null !== $this->request->getVar('is');
199
200
201
		// query string
202
		$queryTextParam = $this->request->getVar('q');
203
		$queryText = !empty($queryTextParam) ? $queryTextParam : '';
204
205
		$testMode = !empty($this->request->getVar('TestMode'));
206
207
		// filters for aggregations
208
		$ignore = \Config::inst()->get('Elastica', 'BlackList');
209
		foreach($this->request->getVars() as $key => $value) {
210
			if(!in_array($key, $ignore)) {
211
				$es->addFilter($key, $value);
212
			}
213
		}
214
215
		// filter by class or site tree
216
		if($ep->SiteTreeOnly) {
217
			$es->addFilter('IsInSiteTree', true);
218
		} else {
219
			$es->setClasses($ep->ClassesToSearch);
220
		}
221
222
		// set the optional aggregation manipulator
223
		// In the event of a manipulator being present, show all the results for search
224
		// Otherwise aggregations are all zero
225
		if($this->SearchHelper) {
226
			$es->setQueryResultManipulator($this->SearchHelper);
227
			$es->showResultsForEmptySearch();
228
		} else {
229
			$es->hideResultsForEmptySearch();
230
		}
231
232
		// get the edited fields to search from the database for this search page
233
		// Convert this into a name => weighting array
234
		$fieldsToSearch = array();
235
		$editedSearchFields = $this->ElasticaSearchableFields()->filter(array(
236
			'Active' => true,
237
			'Searchable' => true
238
		));
239
240
		foreach($editedSearchFields->getIterator() as $searchField) {
241
			$fieldsToSearch[$searchField->Name] = $searchField->Weight;
242
		}
243
244
		$paginated = null;
245
		try {
246
			// Simulate server being down for testing purposes
247
			if(!empty($this->request->getVar('ServerDown'))) {
248
				throw new Elastica\Exception\Connection\HttpException('Unable to reach search server');
249
			}
250
251
			// now actually perform the search using the original query
252
			$paginated = $es->search($queryText, $fieldsToSearch, $testMode);
253
254
			// This is the case of the original query having a better one suggested.  Do a
255
			// second search for the suggested query, throwing away the original
256
			if($es->hasSuggestedQuery() && !$ignoreSuggestions) {
257
				$data['SuggestedQuery'] = $es->getSuggestedQuery();
258
				$data['SuggestedQueryHighlighted'] = $es->getSuggestedQueryHighlighted();
259
				//Link for if the user really wants to try their original query
260
				$sifLink = rtrim($this->Link(), '/') . '?q=' . $queryText . '&is=1';
261
				$data['SearchInsteadForLink'] = $sifLink;
262
				$paginated = $es->search($es->getSuggestedQuery(), $fieldsToSearch);
263
264
			}
265
266
			// calculate time
267
			$endTime = microtime(true);
268
			$elapsed = round(100 * ($endTime - $startTime)) / 100;
269
270
			// store variables for the template to use
271
			$data['ElapsedTime'] = $elapsed;
272
			$this->Aggregations = $es->getAggregations();
273
			$data['SearchResults'] = $paginated;
274
			$data['SearchPerformed'] = true;
275
			$data['NumberOfResults'] = $paginated->getTotalItems();
276
277
		} catch (Elastica\Exception\Connection\HttpException $e) {
278
			$data['ErrorMessage'] = 'Unable to connect to search server';
279
			$data['SearchPerformed'] = false;
280
		}
281
282
		$data['OriginalQuery'] = $queryText;
283
		$data['IgnoreSuggestions'] = $ignoreSuggestions;
284
285
		if($this->has_extension('PageControllerTemplateOverrideExtension')) {
286
			return $this->useTemplateOverride($data);
287
		} else {
288
			return $data;
289
		}
290
	}
291
292
293
294
	/*
295
	Return true if the query is not empty
296
	 */
297
	public function QueryIsEmpty() {
298
		return empty($this->request->getVar('q'));
299
	}
300
301
302
	/**
303
	 * Process submission of the search form, redirecting to a URL that will render search results
304
	 * @param  array $data form data
305
	 * @param  Form $form form
306
	 */
307
	public function submit($data, $form) {
308
		$queryText = $data['q'];
309
		$url = $this->Link();
310
		$url = rtrim($url, '/');
311
		$link = rtrim($url, '/') . '?q=' . $queryText . '&sfid=' . $data['identifier'];
312
		$this->redirect($link);
313
	}
314
315
	/*
316
	Obtain an instance of the form
317
	*/
318
319
	public function SearchForm() {
320
		$form = new ElasticSearchForm($this, 'SearchForm');
321
		$fields = $form->Fields();
322
		$ep = Controller::curr()->dataRecord;
323
		$identifierField = new HiddenField('identifier');
324
		$identifierField->setValue($ep->Identifier);
325
		$fields->push($identifierField);
326
		$queryField = $fields->fieldByName('q');
327
328
		 if($this->isParamSet('q') && $this->isParamSet('sfid')) {
329
		 	$sfid = $this->request->getVar('sfid');
330
			if($sfid == $ep->Identifier) {
331
				$queryText = $this->request->getVar('q');
332
				$queryField->setValue($queryText);
333
			}
334
335
		}
336
337
		if($this->action == 'similar') {
338
			$queryField->setDisabled(true);
339
			$actions = $form->Actions();
340
341
			if(!empty($actions)) {
342
				foreach($actions as $field) {
343
					$field->setDisabled(true);
344
				}
345
			}
346
347
		}
348
349
		if($this->AutoCompleteFieldID > 0) {
350
			ElasticaUtil::addAutocompleteToQueryField(
351
				$queryField,
352
				$this->ClassesToSearch,
353
				$this->SiteTreeOnly,
354
				$this->Link(),
355
				$this->AutocompleteFunction()->Slug
356
			);
357
		}
358
		return $form;
359
	}
360
361
362
	/**
363
	 * @param string $paramName
364
	 */
365
	private function isParamSet($paramName) {
366
		return !empty($this->request->getVar($paramName));
367
	}
368
369
}
370