Completed
Push — dev2 ( 84774d...5ebc36 )
by Gordon
03:38 queued 33s
created

Searchable::requireDefaultRecords()   C

Complexity

Conditions 7
Paths 22

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 3 Features 0
Metric Value
dl 0
loc 51
rs 6.9744
c 4
b 3
f 0
cc 7
eloc 29
nc 22
nop 0

How to fix   Long Method   

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
namespace SilverStripe\Elastica;
4
5
use Elastica\Document;
6
use Elastica\Type\Mapping;
7
use ShortcodeParser;
8
9
/**
10
 * Adds elastic search integration to a data object.
11
 */
12
class Searchable extends \DataExtension {
13
14
	/**
15
	 * Counter used to display progress of indexing
16
	 * @var integer
17
	 */
18
	public static $index_ctr = 0;
19
20
	/**
21
	 * Everytime progressInterval divides $index_ctr exactly display progress
22
	 * @var integer
23
	 */
24
	private static $progressInterval = 0;
25
26
	public static $mappings = array(
27
		'Boolean'     => 'boolean',
28
		'Decimal'     => 'double',
29
		'Currency'    => 'double',
30
		'Double'      => 'double',
31
		'Enum'        => 'string',
32
		'Float'       => 'float',
33
		'HTMLText'    => 'string',
34
		'HTMLVarchar' => 'string',
35
		'Int'         => 'integer',
36
		'Text'        => 'string',
37
		'VarChar'     => 'string',
38
		'Varchar'     => 'string',
39
		'Year'        => 'integer',
40
		'Percentage'  => 'double',
41
		'Time'  => 'date',
42
43
		// The 2 different date types will be stored with different formats
44
		'Date'        => 'date',
45
		'SS_Datetime' => 'date',
46
		'Datetime' => 'date',
47
		'DBLocale'    => 'string'
48
	);
49
50
51
	/**
52
	 * @var ElasticaService associated elastica search service
53
	 */
54
	protected $service;
55
56
57
	/**
58
	 * Array of fields that need HTML parsed
59
	 * @var array
60
	 */
61
	protected $html_fields = array();
62
63
	/**
64
	 * Store a mapping of relationship name to result type
65
	 */
66
	protected $relationship_methods = array();
67
68
69
	/**
70
	 * If importing a large number of items from a fixtures file, or indeed some other source, then
71
	 * it is quicker to set a flag of value IndexingOff => false.  This has the effect of ensuring
72
	 * no indexing happens, a request is normally made per fixture when loading.  One can then run
73
	 * the reindexing teask to bulk index in one HTTP POST request to Elasticsearch
74
	 *
75
	 * @var boolean
76
	 */
77
	private static $IndexingOff = false;
78
79
80
	/**
81
	 * @see getElasticaResult
82
	 * @var \Elastica\Result
83
	 */
84
	protected $elastica_result;
85
86
	public function __construct(ElasticaService $service) {
87
		$this->service = $service;
88
		parent::__construct();
89
	}
90
91
92
	/**
93
	 * Get the elasticsearch type name
94
	 *
95
	 * @return string
96
	 */
97
	public function getElasticaType() {
98
		return get_class($this->owner);
99
	}
100
101
102
	/**
103
	 * If the owner is part of a search result
104
	 * the raw Elastica search result is returned
105
	 * if set via setElasticaResult
106
	 *
107
	 * @return \Elastica\Result
108
	 */
109
	public function getElasticaResult() {
110
		return $this->elastica_result;
111
	}
112
113
114
	/**
115
	 * Set the raw Elastica search result
116
	 *
117
	 * @param \Elastica\Result
118
	 */
119
	public function setElasticaResult(\Elastica\Result $result) {
120
		$this->elastica_result = $result;
121
	}
122
123
124
	/**
125
	 * Gets an array of elastic field definitions.
126
	 *
127
	 * @return array
128
	 */
129
	public function getElasticaFields($storeMethodName = false, $recurse = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $storeMethodName is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $recurse is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
130
		$db = $this->owner->db();
131
		$fields = $this->getAllSearchableFields();
132
		$result = array();
133
134
		foreach($fields as $name => $params) {
135
			$spec = array();
136
			$name = str_replace('()', '', $name);
137
138
			if(array_key_exists($name, $db)) {
139
				$class = $db[$name];
140
				$this->assignSpecForStandardFieldType($class, $spec);
141
			} else {
142
				// field name is not in the db, it could be a method
143
				$has_lists = $this->getListRelationshipMethods();
144
				$has_ones = $this->owner->has_one();
145
146
				// check has_many and many_many relations
147
				if(isset($has_lists[$name])) {
148
					// the classes returned by the list method
149
					$resultType = $has_lists[$name];
150
					$this->assignSpecForManyRelationship($resultType, $spec);
151
				} else if(isset($has_ones[$name])) {
152
					$resultType = $has_ones[$name];
153
					$this->assignSpecForHasOne($resultType, $spec);
154
				}
155
				// otherwise fall back to string - Enum is one such category
156
				else {
157
					$spec["type"] = "string";
158
				}
159
			}
160
161
			// in the case of a relationship type will not be set
162
			if(isset($spec['type'])) {
163
				if($spec['type'] == 'string') {
164
					$unstemmed = array();
165
					$unstemmed['type'] = "string";
166
					$unstemmed['analyzer'] = "unstemmed";
167
					$unstemmed['term_vector'] = "yes";
168
					$extraFields = array('standard' => $unstemmed);
169
170
					$shingles = array();
171
					$shingles['type'] = "string";
172
					$shingles['analyzer'] = "shingles";
173
					$shingles['term_vector'] = "yes";
174
					$extraFields['shingles'] = $shingles;
175
176
					//Add autocomplete field if so required
177
					$autocomplete = \Config::inst()->get($this->owner->ClassName, 'searchable_autocomplete');
178
179
					if(isset($autocomplete) && in_array($name, $autocomplete)) {
180
						$autocompleteField = array();
181
						$autocompleteField['type'] = "string";
182
						$autocompleteField['index_analyzer'] = "autocomplete_index_analyzer";
183
						$autocompleteField['search_analyzer'] = "autocomplete_search_analyzer";
184
						$autocompleteField['term_vector'] = "yes";
185
						$extraFields['autocomplete'] = $autocompleteField;
186
					}
187
188
					$spec['fields'] = $extraFields;
189
					// FIXME - make index/locale specific, get from settings
190
					$spec['analyzer'] = 'stemmed';
191
					$spec['term_vector'] = "yes";
192
				}
193
			}
194
195
			$result[$name] = $spec;
196
		}
197
198
		if($this->owner->hasMethod('updateElasticHTMLFields')) {
199
			$this->html_fields = $this->owner->updateElasticHTMLFields($this->html_fields);
200
		}
201
202
		return $result;
203
	}
204
205
206 View Code Duplication
	private function assignSpecForHasOne($resultType, &$spec) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
207
		$resultTypeInstance = \Injector::inst()->create($resultType);
208
209
		$resultTypeMapping = array();
210
211
		// get the fields for the result type, but do not recurse
212
		if($recurse) {
0 ignored issues
show
Bug introduced by
The variable $recurse 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...
213
			$resultTypeMapping = $resultTypeInstance->getElasticaFields($storeMethodName, false);
0 ignored issues
show
Bug introduced by
The variable $storeMethodName 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...
214
		}
215
216
		$resultTypeMapping['ID'] = array('type' => 'integer');
217
218
		if($storeMethodName) {
219
			$resultTypeMapping['__method'] = $name;
0 ignored issues
show
Bug introduced by
The variable $name seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
220
		}
221
		$spec = array('properties' => $resultTypeMapping);
222
		// we now change the name to the result type, not the method name
223
		$name = $resultType;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
224
	}
225
226 View Code Duplication
	private function assignSpecForManyRelationship($resultType, &$spec) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
227
		$resultTypeInstance = \Injector::inst()->create($resultType);
228
		$resultTypeMapping = array();
229
230
		// get the fields for the result type, but do not recurse
231
		if($recurse) {
0 ignored issues
show
Bug introduced by
The variable $recurse 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...
232
			$resultTypeMapping = $resultTypeInstance->getElasticaFields($storeMethodName, false);
0 ignored issues
show
Bug introduced by
The variable $storeMethodName 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...
233
		}
234
235
		$resultTypeMapping['ID'] = array('type' => 'integer');
236
237
		if($storeMethodName) {
238
			$resultTypeMapping['__method'] = $name;
0 ignored issues
show
Bug introduced by
The variable $name seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
239
		}
240
241
		$spec = array('properties' => $resultTypeMapping);
242
243
244
		// we now change the name to the result type, not the method name
245
		$name = $resultType;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
246
	}
247
248
249
	private function assignSpecForStandardFieldType($class, &$spec) {
250
		if(($pos = strpos($class, '('))) {
251
			// Valid in the case of Varchar(255)
252
			$class = substr($class, 0, $pos);
253
		}
254
255
		if(array_key_exists($class, self::$mappings)) {
256
			$spec['type'] = self::$mappings[$class];
257
			if($spec['type'] === 'date') {
258
				if($class == 'Date') {
259
					$spec['format'] = 'y-M-d';
260
				} elseif($class == 'SS_Datetime') {
261
					$spec['format'] = 'y-M-d H:m:s';
262
				} elseif($class == 'Datetime') {
263
					$spec['format'] = 'y-M-d H:m:s';
264
				} elseif($class == 'Time') {
265
					$spec['format'] = 'H:m:s';
266
				}
267
			}
268
			if($class === 'HTMLText' || $class === 'HTMLVarchar') {
269
				array_push($this->html_fields, $name);
0 ignored issues
show
Bug introduced by
The variable $name 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...
270
			}
271
		}
272
		// no need for an extra case here as all SS types checked in tests
273
	}
274
275
276
	/**
277
	 * Get the elasticsearch mapping for the current document/type
278
	 *
279
	 * @return \Elastica\Type\Mapping
280
	 */
281
	public function getElasticaMapping() {
282
		$mapping = new Mapping();
283
284
		$fields = $this->getElasticaFields(false);
285
286
		$localeMapping = array();
287
288
		if($this->owner->hasField('Locale')) {
289
			$localeMapping['type'] = 'string';
290
			// we wish the locale to be stored as is
291
			$localeMapping['index'] = 'not_analyzed';
292
			$fields['Locale'] = $localeMapping;
293
		}
294
295
		// ADD CUSTOM FIELDS HERE THAT ARE INDEXED BY DEFAULT
296
		// add a mapping to flag whether or not class is in SiteTree
297
		$fields['IsInSiteTree'] = array('type'=>'boolean');
298
		$fields['Link'] = array('type' => 'string', 'index' => 'not_analyzed');
299
300
		$mapping->setProperties($fields);
301
302
		//This concatenates all the fields together into a single field.
303
		//Initially added for suggestions compatibility, in that searching
304
		//_all field picks up all possible suggestions
305
		$mapping->enableAllField();
306
307
		if($this->owner->hasMethod('updateElasticsearchMapping')) {
308
			$mapping = $this->owner->updateElasticsearchMapping($mapping);
309
		}
310
		return $mapping;
311
	}
312
313
314
	/**
315
	 * Get an elasticsearch document
316
	 *
317
	 * @return \Elastica\Document
318
	 */
319
	public function getElasticaDocument() {
320
		self::$index_ctr++;
321
		$fields = $this->getFieldValuesAsArray();
322
		$progress = Controller::curr()->getVar('progress');
323
		if(!empty($progress)) {
324
			self::$progressInterval = (int)$progress;
325
		}
326
327
		if(self::$progressInterval > 0) {
328
			if(self::$index_ctr % self::$progressInterval === 0) {
329
				ElasticaUtil::message("\t" . $this->owner->ClassName . " - Prepared " . self::$index_ctr . " for indexing...");
330
			}
331
		}
332
333
		// Optionally update the document
334
		$document = new Document($this->owner->ID, $fields);
335
		if($this->owner->hasMethod('updateElasticsearchDocument')) {
336
			$document = $this->owner->updateElasticsearchDocument($document);
337
		}
338
339
		// Check if the current classname is part of the site tree or not
340
		// Results are cached to save reprocessing the same
341
		$classname = $this->owner->ClassName;
342
		$inSiteTree = $this->isInSiteTree($classname);
343
344
		$document->set('IsInSiteTree', $inSiteTree);
345
346
		if($inSiteTree) {
347
			$document->set('Link', $this->owner->AbsoluteLink());
348
		}
349
350
		if(isset($this->owner->Locale)) {
351
			$document->set('Locale', $this->owner->Locale);
352
		}
353
354
		return $document;
355
	}
356
357
358
	public function getFieldValuesAsArray($recurse = true) {
359
		$fields = array();
360
		$has_ones = $this->owner->has_one();
361
362
		foreach($this->getElasticaFields($recurse) as $field => $config) {
363
			if(null === $this->owner->$field && is_callable(get_class($this->owner) . "::" . $field)) {
364
				// call a method to get a field value
365
				if(in_array($field, $this->html_fields)) {
366
					// Parse short codes in HTML, and then convert to text
367
					$fields[$field] = $this->owner->$field;
368
					$html = ShortcodeParser::get_active()->parse($this->owner->$field());
369
					$txt = \Convert::html2raw($html);
370
					$fields[$field] = $txt;
371
				} else {
372
					// Plain text
373
					$fields[$field] = $this->owner->$field();
374
				}
375
376
			} else {
377
				if(in_array($field, $this->html_fields)) {
378
					$fields[$field] = $this->owner->$field;
379
					if(gettype($this->owner->$field) !== 'NULL') {
380
						$html = ShortcodeParser::get_active()->parse($this->owner->$field);
381
						$txt = \Convert::html2raw($html);
382
						$fields[$field] = $txt;
383
					}
384
				} else {
385
					if(isset($config['properties']['__method'])) {
386
						$methodName = $config['properties']['__method'];
387
						$data = $this->owner->$methodName();
388
						$relArray = array();
389
390
						// get the fields of a has_one relational object
391
						if(isset($has_ones[$methodName])) {
392
							if($data->ID > 0) {
393
								$item = $data->getFieldValuesAsArray(false);
394
								$relArray = $item;
395
							}
396
397
						// get the fields for a has_many or many_many relational list
398
						} else {
399
							foreach($data->getIterator() as $item) {
400
								if($recurse) {
401
									// populate the subitem but do not recurse any further if more relationships
402
									$itemDoc = $item->getFieldValuesAsArray(false);
403
									array_push($relArray, $itemDoc);
404
								}
405
							}
406
						}
407
						// save the relation as an array (for now)
408
						$fields[$methodName] = $relArray;
409
					} else {
410
						$fields[$field] = $this->owner->$field;
411
					}
412
413
				}
414
415
			}
416
		}
417
418
		return $fields;
419
	}
420
421
422
	/**
423
	 * Returns whether to include the document into the search index.
424
	 * All documents are added unless they have a field "ShowInSearch" which is set to false
425
	 *
426
	 * @return boolean
427
	 */
428
	public function showRecordInSearch() {
429
		return !($this->owner->hasField('ShowInSearch') && false == $this->owner->ShowInSearch);
430
	}
431
432
433
	/**
434
	 * Delete the record from the search index if ShowInSearch is deactivated (non-SiteTree).
435
	 */
436
	public function onBeforeWrite() {
437
		if(($this->owner instanceof \SiteTree)) {
438
			if($this->owner->hasField('ShowInSearch') &&
439
				$this->owner->isChanged('ShowInSearch', 2) && false == $this->owner->ShowInSearch) {
440
				$this->doDeleteDocument();
441
			}
442
		}
443
	}
444
445
446
	/**
447
	 * Delete the record from the search index if ShowInSearch is deactivated (SiteTree).
448
	 */
449
	public function onBeforePublish() {
450
		if(false == $this->owner->ShowInSearch) {
451
			if($this->owner->isPublished()) {
452
				$liveRecord = \Versioned::get_by_stage(get_class($this->owner), 'Live')->
453
					byID($this->owner->ID);
454
				if($liveRecord->ShowInSearch != $this->owner->ShowInSearch) {
455
					$this->doDeleteDocument();
456
				}
457
			}
458
		}
459
	}
460
461
462
	/**
463
	 * Updates the record in the search index (non-SiteTree).
464
	 */
465
	public function onAfterWrite() {
466
		$this->doIndexDocument();
467
	}
468
469
470
	/**
471
	 * Updates the record in the search index (SiteTree).
472
	 */
473
	public function onAfterPublish() {
474
		$this->doIndexDocument();
475
	}
476
477
478
	/**
479
	 * Updates the record in the search index.
480
	 */
481
	protected function doIndexDocument() {
482
		if($this->showRecordInSearch()) {
483
			if(!$this->owner->IndexingOff) {
484
				$this->service->index($this->owner);
485
			}
486
		}
487
	}
488
489
490
	/**
491
	 * Removes the record from the search index (non-SiteTree).
492
	 */
493
	public function onAfterDelete() {
494
		$this->doDeleteDocumentIfInSearch();
495
	}
496
497
498
	/**
499
	 * Removes the record from the search index (non-SiteTree).
500
	 */
501
	public function onAfterUnpublish() {
502
		$this->doDeleteDocumentIfInSearch();
503
	}
504
505
506
	/**
507
	 * Removes the record from the search index if the "ShowInSearch" attribute is set to true.
508
	 */
509
	protected function doDeleteDocumentIfInSearch() {
510
		if($this->showRecordInSearch()) {
511
			$this->doDeleteDocument();
512
		}
513
	}
514
515
516
	/**
517
	 * Removes the record from the search index.
518
	 */
519
	protected function doDeleteDocument() {
520
		try {
521
			if(!$this->owner->IndexingOff) {
522
				// this goes to elastica service
523
				$this->service->remove($this->owner);
524
			}
525
		} catch (\Elastica\Exception\NotFoundException $e) {
526
			trigger_error("Deleted document " . $this->owner->ClassName . " (" . $this->owner->ID .
527
				") not found in search index.", E_USER_NOTICE);
528
		}
529
530
	}
531
532
533
	/**
534
	 * Return all of the searchable fields defined in $this->owner::$searchable_fields and all the parent classes.
535
	 *
536
	 * @param  $recuse Whether or not to traverse relationships. First time round yes, subsequently no
537
	 * @return array searchable fields
538
	 */
539
	public function getAllSearchableFields($recurse = true) {
540
		$fields = \Config::inst()->get(get_class($this->owner), 'searchable_fields');
541
542
		// fallback to default method
543
		if(!$fields) {
544
			user_error('The field $searchable_fields must be set for the class ' . $this->owner->ClassName);
545
		}
546
547
		// get the values of these fields
548
		$elasticaMapping = $this->fieldsToElasticaConfig($fields);
549
550
		if($recurse) {
551
			// now for the associated methods and their results
552
			$methodDescs = \Config::inst()->get(get_class($this->owner), 'searchable_relationships');
553
			$has_ones = $this->owner->has_one();
554
			$has_lists = $this->getListRelationshipMethods();
555
556
			if(isset($methodDescs) && is_array($methodDescs)) {
557
				foreach($methodDescs as $methodDesc) {
558
					// split before the brackets which can optionally list which fields to index
559
					$splits = explode('(', $methodDesc);
560
					$methodName = $splits[0];
561
562
					if(isset($has_lists[$methodName])) {
563
564
						$relClass = $has_lists[$methodName];
565
						$fields = \Config::inst()->get($relClass, 'searchable_fields');
566
						if(!$fields) {
567
							user_error('The field $searchable_fields must be set for the class ' . $relClass);
568
						}
569
						$rewrite = $this->fieldsToElasticaConfig($fields);
570
571
						// mark as a method, the resultant fields are correct
572
						$elasticaMapping[$methodName . '()'] = $rewrite;
573
					} else if(isset($has_ones[$methodName])) {
574
						$relClass = $has_ones[$methodName];
575
						$fields = \Config::inst()->get($relClass, 'searchable_fields');
576
						if(!$fields) {
577
							user_error('The field $searchable_fields must be set for the class ' . $relClass);
578
						}
579
						$rewrite = $this->fieldsToElasticaConfig($fields);
580
581
						// mark as a method, the resultant fields are correct
582
						$elasticaMapping[$methodName . '()'] = $rewrite;
583
					} else {
584
						user_error('The method ' . $methodName . ' not found in class ' . $this->owner->ClassName .
585
								', please check configuration');
586
					}
587
				}
588
			}
589
		}
590
591
		return $elasticaMapping;
592
	}
593
594
595
	/*
596
	Evaluate each field, e.g. 'Title', 'Member.Name'
597
	 */
598
	private function fieldsToElasticaConfig($fields) {
599
		// Copied from DataObject::searchableFields() as there is no separate accessible method
600
		$rewrite = array();
601
		foreach($fields as $name => $specOrName) {
602
			$identifer = (is_int($name)) ? $specOrName : $name;
603
			$rewrite[$identifer] = array();
604
			if(!isset($rewrite[$identifer]['title'])) {
605
				$rewrite[$identifer]['title'] = (isset($labels[$identifer]))
606
					? $labels[$identifer] : \FormField::name_to_label($identifer);
607
			}
608
			if(!isset($rewrite[$identifer]['filter'])) {
609
				$rewrite[$identifer]['filter'] = 'PartialMatchFilter';
610
			}
611
		}
612
613
		return $rewrite;
614
	}
615
616
617
	public function requireDefaultRecords() {
618
		parent::requireDefaultRecords();
619
620
		$searchableFields = $this->getElasticaFields(true, true);
621
622
623
		$doSC = \SearchableClass::get()->filter(array('Name' => $this->owner->ClassName))->first();
624
		if(!$doSC) {
625
			$doSC = new \SearchableClass();
626
			$doSC->Name = $this->owner->ClassName;
627
628
			$inSiteTree = $this->isInSiteTree($this->owner->ClassName);
629
			$doSC->InSiteTree = $inSiteTree;
630
631
			$doSC->write();
632
		}
633
634
		foreach($searchableFields as $name => $searchableField) {
635
			// check for existence of methods and if they exist use that as the name
636
			if(!isset($searchableField['type'])) {
637
				$name = $searchableField['properties']['__method'];
638
			}
639
640
			$filter = array('ClazzName' => $this->owner->ClassName, 'Name' => $name);
641
			$doSF = \SearchableField::get()->filter($filter)->first();
642
643
644
			if(!$doSF) {
645
				$doSF = new \SearchableField();
646
				$doSF->ClazzName = $this->owner->ClassName;
647
				$doSF->Name = $name;
648
649
				if(isset($searchableField['type'])) {
650
					$doSF->Type = $searchableField['type'];
651
				} else {
652
					$doSF->Name = $searchableField['properties']['__method'];
653
					$doSF->Type = 'relationship';
654
				}
655
				$doSF->SearchableClassID = $doSC->ID;
656
657
				if(isset($searchableField['fields']['autocomplete'])) {
658
					$doSF->Autocomplete = true;
659
				}
660
661
				$doSF->write();
662
				\DB::alteration_message("Created new searchable editable field " . $name, "changed");
663
			}
664
665
			// FIXME deal with deletions
666
		}
667
	}
668
669
670
	private function getListRelationshipMethods() {
671
		$has_manys = $this->owner->has_many();
672
		$many_manys = $this->owner->many_many();
673
674
		// array of method name to retuned object ClassName for relationships returning lists
675
		$has_lists = $has_manys;
676
		foreach(array_keys($many_manys) as $key) {
677
			$has_lists[$key] = $many_manys[$key];
678
		}
679
680
		return $has_lists;
681
	}
682
683
684
	private function isInSiteTree($classname) {
685
		$inSiteTree = ($classname === 'SiteTree' ? true : false);
686
		if(!$inSiteTree) {
687
			$class = new \ReflectionClass($this->owner->ClassName);
688
			while($class = $class->getParentClass()) {
689
				$parentClass = $class->getName();
690
				if($parentClass == 'SiteTree') {
691
					$inSiteTree = true;
692
					break;
693
				}
694
			}
695
		}
696
		return $inSiteTree;
697
	}
698
699
700
	/*
701
	Allow the option of overriding the default template with one of <ClassName>ElasticSearchResult
702
	 */
703
	public function RenderResult($linkToContainer = '') {
704
		$vars = new \ArrayData(array('SearchResult' => $this->owner, 'ContainerLink' => $linkToContainer));
705
		$possibleTemplates = array($this->owner->ClassName . 'ElasticSearchResult', 'ElasticSearchResult');
706
		return $this->owner->customise($vars)->renderWith($possibleTemplates);
707
	}
708
709
710
711
	public function getTermVectors() {
712
		return $this->service->getTermVectors($this->owner);
713
	}
714
715
716
	public function updateCMSFields(\FieldList $fields) {
717
		$isIndexed = false;
718
		// SIteTree object must have a live record, ShowInSearch = true
719
		if($this->isInSiteTree($this->owner->ClassName)) {
720
			$liveRecord = \Versioned::get_by_stage(get_class($this->owner), 'Live')->
721
				byID($this->owner->ID);
722
			if($liveRecord->ShowInSearch) {
723
				$isIndexed = true;
724
			} else {
725
				$isIndexed = false;
726
			}
727
		} else {
728
			// In the case of a DataObject we use the ShowInSearchFlag
729
			$isIndexed = true;
730
		}
731
732
		if($isIndexed) {
733
			$termVectors = $this->getTermVectors();
734
			$termFields = array_keys($termVectors);
735
			sort($termFields);
736
737
			foreach($termFields as $field) {
738
				$terms = new \ArrayList();
739
740
				foreach(array_keys($termVectors[$field]['terms']) as $term) {
741
					$do = new \DataObject();
742
					$do->Term = $term;
743
					$stats = $termVectors[$field]['terms'][$term];
744
					if(isset($stats['ttf'])) {
745
						$do->TTF = $stats['ttf'];
746
					}
747
748
					if(isset($stats['doc_freq'])) {
749
						$do->DocFreq = $stats['doc_freq'];
750
					}
751
752
					if(isset($stats['term_freq'])) {
753
						$do->TermFreq = $stats['term_freq'];
754
					}
755
					$terms->push($do);
756
				}
757
758
				$config = \GridFieldConfig_RecordViewer::create(100);
759
				$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
760
					'Term' => 'Term',
761
					'TTF' => 'Total term frequency (how often a term occurs in all documents)',
762
					'DocFreq' => 'n documents with this term',
763
					'TermFreq'=> 'n times this term appears in this field'
764
				));
765
766
			   $underscored = str_replace('.', '_', $field);
767
768
				$gridField = new \GridField(
769
					'TermsFor' . $underscored, // Field name
770
					$field . 'TITLE' . $field, // Field title
771
					$terms,
772
					$config
773
				);
774
			   $fields->addFieldToTab('Root.ElasticaTerms.' . $underscored, $gridField);
775
			}
776
777
		}
778
779
		return $fields;
780
	}
781
782
783
}
784