Completed
Push — dev2 ( 2e5a51...f00f25 )
by Gordon
03:58
created

ElasticSearchPage_Controller::index()   F

Complexity

Conditions 12
Paths 2400

Size

Total Lines 114
Code Lines 60

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 114
rs 2
cc 12
eloc 60
nc 2400
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
		$this->setMoreLikeThisParamsFromRequest($es);
59
		$es->setPageLength($ep->ResultsPerPage);
60
61
		// filter by class or site tree
62
		if($ep->SiteTreeOnly) {
63
			T7; //FIXME test missing
64
			$es->addFilter('IsInSiteTree', true);
65
		} else {
66
			$es->setClasses($ep->ClassesToSearch);
67
		}
68
69
		// get the edited fields to search from the database for this search page
70
		// Convert this into a name => weighting array
71
		$fieldsToSearch = array();
72
		$editedSearchFields = $this->ElasticaSearchableFields()->filter(array(
73
			'Active' => true,
74
			'SimilarSearchable' => true
75
		));
76
77
		foreach($editedSearchFields->getIterator() as $searchField) {
78
			$fieldsToSearch[$searchField->Name] = $searchField->Weight;
79
		}
80
81
		// Use the standard field for more like this, ie not stemmed
82
		foreach($fieldsToSearch as $field => $value) {
83
			$fieldsToSearch[$field . '.standard'] = $value;
84
			unset($fieldsToSearch[$field]);
85
		}
86
87
		try {
88
			// Simulate server being down for testing purposes
89
			if($this->request->getVar('ServerDown')) {
90
				throw new Elastica\Exception\Connection\HttpException('Unable to reach search server');
91
			}
92
			if(class_exists($class)) {
93
				$instance = \DataObject::get_by_id($class, $instanceID);
94
95
				$paginated = $es->moreLikeThis($instance, $fieldsToSearch);
96
97
				$this->Aggregations = $es->getAggregations();
98
				$data['SearchResults'] = $paginated;
99
				$data['SearchPerformed'] = true;
100
				$data['SearchPageLink'] = $ep->Link();
101
				$data['SimilarTo'] = $instance;
102
				$data['NumberOfResults'] = $paginated->getTotalItems();
103
104
105
				$moreLikeThisTerms = $paginated->getList()->MoreLikeThisTerms;
106
				$fieldToTerms = new ArrayList();
107
				foreach(array_keys($moreLikeThisTerms) as $fieldName) {
108
					$readableFieldName = str_replace('.standard', '', $fieldName);
109
					$fieldTerms = new ArrayList();
110
					foreach($moreLikeThisTerms[$fieldName] as $value) {
111
						$do = new DataObject();
112
						$do->Term = $value;
113
						$fieldTerms->push($do);
114
					}
115
116
					$do = new DataObject();
117
					$do->FieldName = $readableFieldName;
118
					$do->Terms = $fieldTerms;
119
					$fieldToTerms->push($do);
120
				}
121
122
				$data['SimilarSearchTerms'] = $fieldToTerms;
123
			} else {
124
				// class does not exist
125
				$data['ErrorMessage'] = "Class $class is either not found or not searchable\n";
126
			}
127
		} catch (\InvalidArgumentException $e) {
128
			$data['ErrorMessage'] = "Class $class is either not found or not searchable\n";
129
		} catch (Elastica\Exception\Connection\HttpException $e) {
130
			$data['ErrorMessage'] = 'Unable to connect to search server';
131
			$data['SearchPerformed'] = false;
132
		}
133
134
135
		// calculate time
136
		$endTime = microtime(true);
137
		$elapsed = round(100 * ($endTime - $startTime)) / 100;
138
139
		// store variables for the template to use
140
		$data['ElapsedTime'] = $elapsed;
141
		$data['Elapsed'] = $elapsed;
142
143
		// allow the optional use of overriding the search result page, e.g. for photos, maps or facets
144
		if($this->hasExtension('PageControllerTemplateOverrideExtension')) {
145
			return $this->useTemplateOverride($data);
146
		} else {
147
			return $data;
148
		}
149
	}
150
151
152
153
154
	/*
155
	Display the search form. If the query parameter exists, search against Elastica
156
	and render results accordingly.
157
	 */
158
	public function index() {
159
		$data = array(
160
			'Content' => $this->Content,
161
			'Title' => $this->Title,
162
			'SearchPerformed' => false
163
		);
164
165
		// record the time
166
		$startTime = microtime(true);
167
168
		//instance of ElasticPage associated with this controller
169
		$ep = Controller::curr()->dataRecord;
170
171
		// use an Elastic Searcher, which needs primed from URL params
172
		$es = new ElasticSearcher();
173
174
		$this->setStartParamsFromRequest($es);
175
		$es->setPageLength($ep->ResultsPerPage);
176
177
178
		// Do not show suggestions if this flag is set
179
		$ignoreSuggestions = null !== $this->request->getVar('is');
180
181
182
		// query string
183
		$queryTextParam = $this->request->getVar('q');
184
		$queryText = !empty($queryTextParam) ? $queryTextParam : '';
185
186
		$testMode = !empty($this->request->getVar('TestMode'));
187
188
		// filters for aggregations
189
		$ignore = \Config::inst()->get('Elastica', 'BlackList');
190
		foreach($this->request->getVars() as $key => $value) {
191
			if(!in_array($key, $ignore)) {
192
				$es->addFilter($key, $value);
193
			}
194
		}
195
196
		// filter by class or site tree
197
		if($ep->SiteTreeOnly) {
198
			$es->addFilter('IsInSiteTree', true);
199
		} else {
200
			$es->setClasses($ep->ClassesToSearch);
201
		}
202
203
		// set the optional aggregation manipulator
204
		// In the event of a manipulator being present, show all the results for search
205
		// Otherwise aggregations are all zero
206
		if($this->SearchHelper) {
207
			$es->setQueryResultManipulator($this->SearchHelper);
208
			$es->showResultsForEmptySearch();
209
		} else {
210
			$es->hideResultsForEmptySearch();
211
		}
212
213
		// get the edited fields to search from the database for this search page
214
		// Convert this into a name => weighting array
215
		$fieldsToSearch = array();
216
		$editedSearchFields = $this->ElasticaSearchableFields()->filter(array(
217
			'Active' => true,
218
			'Searchable' => true
219
		));
220
221
		foreach($editedSearchFields->getIterator() as $searchField) {
222
			$fieldsToSearch[$searchField->Name] = $searchField->Weight;
223
		}
224
225
		$paginated = null;
226
		try {
227
			// Simulate server being down for testing purposes
228
			if(!empty($this->request->getVar('ServerDown'))) {
229
				throw new Elastica\Exception\Connection\HttpException('Unable to reach search server');
230
			}
231
232
			// now actually perform the search using the original query
233
			$paginated = $es->search($queryText, $fieldsToSearch, $testMode);
234
235
			// This is the case of the original query having a better one suggested.  Do a
236
			// second search for the suggested query, throwing away the original
237
			if($es->hasSuggestedQuery() && !$ignoreSuggestions) {
238
				$data['SuggestedQuery'] = $es->getSuggestedQuery();
239
				$data['SuggestedQueryHighlighted'] = $es->getSuggestedQueryHighlighted();
240
				//Link for if the user really wants to try their original query
241
				$sifLink = rtrim($this->Link(), '/') . '?q=' . $queryText . '&is=1';
242
				$data['SearchInsteadForLink'] = $sifLink;
243
				$paginated = $es->search($es->getSuggestedQuery(), $fieldsToSearch);
244
245
			}
246
247
			// calculate time
248
			$endTime = microtime(true);
249
			$elapsed = round(100 * ($endTime - $startTime)) / 100;
250
251
			// store variables for the template to use
252
			$data['ElapsedTime'] = $elapsed;
253
			$this->Aggregations = $es->getAggregations();
254
			$data['SearchResults'] = $paginated;
255
			$data['SearchPerformed'] = true;
256
			$data['NumberOfResults'] = $paginated->getTotalItems();
257
258
		} catch (Elastica\Exception\Connection\HttpException $e) {
259
			$data['ErrorMessage'] = 'Unable to connect to search server';
260
			$data['SearchPerformed'] = false;
261
		}
262
263
		$data['OriginalQuery'] = $queryText;
264
		$data['IgnoreSuggestions'] = $ignoreSuggestions;
265
266
		if($this->has_extension('PageControllerTemplateOverrideExtension')) {
267
			return $this->useTemplateOverride($data);
268
		} else {
269
			return $data;
270
		}
271
	}
272
273
274
275
	/*
276
	Return true if the query is not empty
277
	 */
278
	public function QueryIsEmpty() {
279
		return empty($this->request->getVar('q'));
280
	}
281
282
283
	/**
284
	 * Process submission of the search form, redirecting to a URL that will render search results
285
	 * @param  array $data form data
286
	 * @param  Form $form form
287
	 */
288
	public function submit($data, $form) {
289
		$queryText = $data['q'];
290
		$url = $this->Link();
291
		$url = rtrim($url, '/');
292
		$link = rtrim($url, '/') . '?q=' . $queryText . '&sfid=' . $data['identifier'];
293
		$this->redirect($link);
294
	}
295
296
	/*
297
	Obtain an instance of the form
298
	*/
299
300
	public function SearchForm() {
301
		$form = new ElasticSearchForm($this, 'SearchForm');
302
		$fields = $form->Fields();
303
		$ep = Controller::curr()->dataRecord;
304
		$identifierField = new HiddenField('identifier');
305
		$identifierField->setValue($ep->Identifier);
306
		$fields->push($identifierField);
307
		$queryField = $fields->fieldByName('q');
308
309
		 if($this->isParamSet('q') && $this->isParamSet('sfid')) {
310
		 	$sfid = $this->request->getVar('sfid');
311
			if($sfid == $ep->Identifier) {
312
				$queryText = $this->request->getVar('q');
313
				$queryField->setValue($queryText);
314
			}
315
316
		}
317
318
		if($this->action == 'similar') {
319
			$queryField->setDisabled(true);
320
			$actions = $form->Actions();
321
322
			if(!empty($actions)) {
323
				foreach($actions as $field) {
324
					$field->setDisabled(true);
325
				}
326
			}
327
328
		}
329
330
		if($this->AutoCompleteFieldID > 0) {
331
			ElasticaUtil::addAutocompleteToQueryField(
332
				$queryField,
333
				$this->ClassesToSearch,
334
				$this->SiteTreeOnly,
335
				$this->Link(),
336
				$this->AutocompleteFunction()->Slug
337
			);
338
		}
339
		return $form;
340
	}
341
342
343
	/**
344
	 * @param string $paramName
345
	 */
346
	private function isParamSet($paramName) {
347
		return !empty($this->request->getVar($paramName));
348
	}
349
350
351
352
	/**
353
	 * Set the start page from the request and results per page for a given searcher object
354
	 * @param \SilverStripe\Elastica\ElasticSearcher &$elasticSearcher ElasticSearcher object
355
	 */
356
	private function setStartParamsFromRequest(&$elasticSearcher) {
357
		// start, and page length, i.e. pagination
358
		$startParam = $this->request->getVar('start');
359
		$start = isset($startParam) ? $startParam : 0;
360
		$elasticSearcher->setStart($start);
361
	}
362
363
364
	/**
365
	 * Set the admin configured similarity parameters
366
	 * @param \SilverStripe\Elastica\ElasticSearcher &$elasticSearcher ElasticaSearcher object
367
	 */
368
	private function setMoreLikeThisParamsFromRequest(&$elasticSearcher) {
369
		$elasticSearcher->setMinTermFreq($this->MinTermFreq);
370
		$elasticSearcher->setMaxTermFreq($this->MaxTermFreq);
371
		$elasticSearcher->setMinDocFreq($this->MinDocFreq);
372
		$elasticSearcher->setMaxDocFreq($this->MaxDocFreq);
373
		$elasticSearcher->setMinWordLength($this->MinWordLength);
374
		$elasticSearcher->setMaxWordLength($this->MaxWordLength);
375
		$elasticSearcher->setMinShouldMatch($this->MinShouldMatch);
376
		$elasticSearcher->setSimilarityStopWords($this->SimilarityStopWords);
377
	}
378
379
}
380