ShopSearchSolr::getFieldDefinitions()   B
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 5
Bugs 1 Features 2
Metric Value
c 5
b 1
f 2
dl 0
loc 19
ccs 0
cts 11
cp 0
rs 8.8571
cc 5
eloc 9
nc 6
nop 0
crap 30
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 13 and the first side effect is on line 3.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
if (!class_exists('SolrIndex')) {
3
    return;
4
}
5
6
/**
7
 * Search driver for the fulltext module with solr backend.
8
 *
9
 * @author Mark Guinn <[email protected]>
10
 * @date 08.29.2013
11
 * @package shop_search
12
 */
13
class ShopSearchSolr extends SolrIndex implements ShopSearchAdapter
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
14
{
15
    /** @var array - maps our names for fields to Solr's names (i.e. Title => SiteTree_Title) */
16
    protected $fieldMap = array();
17
18
    /**
19
     * Sets up the index
20
     */
21
    public function init()
22
    {
23
        $searchables = ShopSearch::get_searchable_classes();
24
25
        // Add each class to the index
26
        foreach ($searchables as $class) {
27
            $this->addClass($class);
28
        }
29
30
        // add the fields they've specifically asked for
31
        $fields = $this->getFulltextSpec();
32
        foreach ($fields as $def) {
33
            $this->addFulltextField($def['field'], $def['type'], $def['params']);
34
        }
35
36
        // add the filters they've asked for
37
        $filters = $this->getFilterSpec();
38
        foreach ($filters as $filterName => $def) {
39
            // NOTE: I'm pulling the guts out of this function so we can access Solr's full name
40
            // for the field (SiteTree_Title for Title) and build the fieldMap in one step instead
41
            // of two.
42
            //$this->addFilterField($def['field'], $def['type'], $def['params']);
0 ignored issues
show
Unused Code Comprehensibility introduced by
87% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
43
            $singleFilter = $this->fieldData($def['field'], $def['type'], $def['params']);
44
            $this->filterFields = array_merge($this->filterFields, $singleFilter);
45
            foreach ($singleFilter as $solrName => $solrDef) {
46
                if ($def['field'] == $solrDef['field']) {
47
                    $this->fieldMap[$filterName] = $solrName;
48
                }
49
            }
50
        }
51
52
//		Debug::dump($this->filterFields);
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
53
54
        // Add spellcheck fields
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
55
//		$spellFields = $cfg->get('ShopSearch', 'spellcheck_dictionary_source');
56
//		if (empty($spellFields) || !is_array($spellFields)) {
57
//			$spellFields = array();
58
//			$ftFields = $this->getFulltextFields();
59
//			foreach	($ftFields as $name => $fieldDef) {
60
//				$spellFields[] = $name;
61
//			}
62
//		}
63
//
64
//		foreach ($spellFields as $f) {
65
//			$this->addCopyField($f, '_spellcheckContent');
66
//		}
67
68
        // Technically, filter and sort fields are the same in Solr/Lucene
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
69
//		$this->addSortField('ViewCount');
70
//		$this->addSortField('LastEdited', 'SSDatetime');
71
72
        // Aggregate fields for spelling checks
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
73
//		$this->addCopyField('Title', 'spellcheckData');
74
//		$this->addCopyField('Content', 'spellcheckData');
75
76
//		$this->addFullTextField('Category', 'Int', array(
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
77
//			'multi_valued'  => true,
78
//			'stored'        => true,
79
//			'lookup_chain'  => array(
80
//				'call'      => 'method',
81
//				'method'    => 'getAllProductCategoryIDs',
82
//			)
83
//		));
84
85
        // I can't get this to work. Need a way to create the Category field that get used
86
//		$this->addFilterField('Category', 'Int');
87
//		$this->addFilterField('Parent.ID');
88
//		$this->addFilterField('ProductCategories.ID');
89
//		$this->addCopyField('SiteTree_Parent_ID', 'Category');
90
//		$this->addCopyField('Product_ProductCategories_ID', 'Category');
91
92
        // These will be added in a pull request to shop module. If they're not present they'll be ignored
0 ignored issues
show
Unused Code Comprehensibility introduced by
42% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
93
//		$this->addFilterField('AllCategoryIDs', 'Int', array('multiValued' => 'true'));
94
//		$this->addFilterField('AllRecursiveCategoryIDs', 'Int', array('multiValued' => 'true'));
95
96
        // This will cause only live pages to be indexed. There are two ways to do
97
        // this. See fulltextsearch/docs/en/index.md for more information.
98
        // Not sure if this is really the way to go or not, but for now this is it.
99
        $this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
100
    }
101
102
103
    /**
104
     * Transforms different formats of field list into something we can pass to solr
105
     * @param array $in
106
     * @return array
107
     */
108
    protected function scrubFieldList($in)
109
    {
110
        $out = array();
111
        if (empty($in) || !is_array($in)) {
112
            return $out;
113
        }
114
115
        foreach ($in as $name => $val) {
116
            // supports an indexed array format of simple field names
117
            if (is_numeric($name)) {
118
                $name = $val;
119
                $val = true;
120
            }
121
122
            // supports a boolean value meaning "use the default setup"
123
            $params = !is_array($val) ? array() : array_slice($val, 0);
124
125
            // build a normalized structur
126
            $def = array(
127
                'field'     => isset($params['field']) ? $params['field'] : $name,
128
                'type'      => isset($params['type']) ? $params['type'] : null,
129
                'params'    => $params,
130
            );
131
132
            if (isset($def['params']['field'])) {
133
                unset($def['params']['field']);
134
            }
135
            if (isset($def['params']['type'])) {
136
                unset($def['params']['type']);
137
            }
138
139
            $out[$name] = $def;
140
        }
141
142
        return $out;
143
    }
144
145
146
    /**
147
     * @return array
148
     */
149
    protected function getFulltextSpec()
150
    {
151
        $fields = Config::inst()->get('ShopSearch', 'solr_fulltext_fields');
152
        if (empty($fields)) {
153
            $fields = array('Title', 'Content');
154
        }
155
        return $this->scrubFieldList($fields);
156
    }
157
158
159
    /**
160
     *
161
     */
162
    protected function getFilterSpec()
163
    {
164
        $fields = Config::inst()->get('ShopSearch', 'solr_filter_fields');
165
        return $this->scrubFieldList($fields);
166
    }
167
168
169
    /**
170
     * @return string
171
     */
172
    public function getFieldDefinitions()
173
    {
174
        $xml = parent::getFieldDefinitions();
175
//		$xml .= "\n\t\t<field name='_spellcheckContent' type='htmltext' indexed='true' stored='false' multiValued='true' />";
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
176
177
        // create a sorting column
178
        if (isset($this->fieldMap['Title']) || ShopSearch::config()->solr_title_sort_field) {
179
            $f = empty(ShopSearch::config()->title_sort_field) ? '_titleSort' : ShopSearch::config()->solr_title_sort_field;
180
            $xml .= "\n\t\t" . '<field name="' . $f . '" type="alphaOnlySort" indexed="true" stored="false" required="false" multiValued="false" />';
181
            $xml .= "\n\t\t" . '<copyField source="SiteTree_Title" dest="' . $f . '"/>';
182
        }
183
184
        // create an autocomplete column
185
        if (ShopSearch::config()->suggest_enabled) {
186
            $xml .= "\n\t\t<field name='_autocomplete' type='autosuggest_text' indexed='true' stored='false' multiValued='true'/>";
187
        }
188
189
        return $xml;
190
    }
191
192
193
    /**
194
     * @return string
195
     */
196
    public function getCopyFieldDefinitions()
197
    {
198
        $xml = parent::getCopyFieldDefinitions();
199
200
        if (ShopSearch::config()->suggest_enabled) {
201
            foreach ($this->fulltextFields as $name => $field) {
202
                $xml .= "\n\t<copyField source='{$name}' dest='_autocomplete' />";
203
                //$xml .= "\n\t<copyField source='{$name}' dest='_spellcheckContent' />";
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
204
            }
205
        }
206
207
        return $xml;
208
    }
209
210
211
        /**
212
     * Overrides the parent to add a field for autocomplete
213
     * @return HTMLText
214
     */
215
    public function getTypes()
216
    {
217
        $val = parent::getTypes();
218
        if (!$val || !is_object($val)) {
219
            return $val;
220
        }
221
        $xml = $val->getValue();
222
        $xml .= <<<XML
223
224
	        <fieldType name="autosuggest_text" class="solr.TextField"
225
	                   positionIncrementGap="100">
226
	            <analyzer type="index">
227
	                <tokenizer class="solr.StandardTokenizerFactory"/>
228
	                <filter class="solr.LowerCaseFilterFactory"/>
229
	                <filter class="solr.ShingleFilterFactory" minShingleSize="2" maxShingleSize="4" outputUnigrams="true" outputUnigramsIfNoShingles="true" />
230
	                <filter class="solr.PatternReplaceFilterFactory" pattern="^([0-9. ])*$" replacement=""
231
	                        replace="all"/>
232
	                <filter class="solr.RemoveDuplicatesTokenFilterFactory"/>
233
	            </analyzer>
234
	            <analyzer type="query">
235
	                <tokenizer class="solr.StandardTokenizerFactory"/>
236
	                <filter class="solr.LowerCaseFilterFactory"/>
237
	            </analyzer>
238
	        </fieldType>
239
240
XML;
241
        $val->setValue($xml);
242
        return $val;
243
    }
244
245
    /**
246
     * This is an intermediary to bridge the search form input
247
     * and the SearchQuery class. It allows us to have other
248
     * drivers that may not use the FullTextSearch module.
249
     *
250
     * @param string $keywords
251
     * @param array $filters [optional]
252
     * @param array $facetSpec [optional]
253
     * @param int $start [optional]
254
     * @param int $limit [optional]
255
     * @param string $sort [optional]
256
     * @return ArrayData
257
     */
258
    public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='score desc')
259
    {
260
        $query = new SearchQuery();
261
        $params = array(
262
            'sort'  => $sort,
263
        );
264
265
        // swap out title search
266
        if ($params['sort'] == 'SiteTree_Title') {
267
            $params['sort'] = '_titleSort';
268
        }
269
270
        // search by keywords
271
        $query->search(empty($keywords) ? '*:*' : $keywords);
272
273
        // search by filter
274
        foreach ($filters as $k => $v) {
275
            if (isset($this->fieldMap[$k])) {
276
                if (is_string($v) && preg_match('/^RANGE\~(.+)\~(.+)$/', $v, $m)) {
277
                    // Is it a range value?
278
                    $range = new SearchQuery_Range($m[1], $m[2]);
279
                    $query->filter($this->fieldMap[$k], $range);
280
                } else {
281
                    // Or a normal scalar value
282
                    $query->filter($this->fieldMap[$k], $v);
283
                }
284
            }
285
        }
286
287
        // add facets
288
        $facetSpec = FacetHelper::inst()->expandFacetSpec($facetSpec);
289
        $params += $this->buildFacetParams($facetSpec);
290
291
        // TODO: add spellcheck
292
293
        return $this->search($query, $start, $limit, $params, $facetSpec);
294
    }
295
296
297
    /**
298
     * @param string $keywords
299
     * @param array  $filters
300
     * @return array
301
     */
302
    public function suggestWithResults($keywords, array $filters = array())
303
    {
304
        $limit      = (int)ShopSearch::config()->sayt_limit;
305
306
        // process the keywords a bit
307
        $terms      = preg_split('/\s+/', trim(strtolower($keywords)));
308
        $lastTerm   = count($terms) > 0 ? array_pop($terms) : '';
309
        $prefix     = count($terms) > 0 ? implode(' ', $terms) . ' ' : '';
310
        //$terms[]    = $lastTerm;
0 ignored issues
show
Unused Code Comprehensibility introduced by
63% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
311
        $terms[]    = $lastTerm . '*'; // this allows for partial words to still match
312
313
        // convert that to something solr adapater can handle
314
        $query = new SearchQuery();
315
        $query->search('+' . implode(' +', $terms));
316
317
        $params = array(
318
            'sort'          => 'score desc',
319
            'facet'         => 'true',
320
            'facet.field'   => '_autocomplete',
321
            'facet.limit'   => ShopSearch::config()->suggest_limit,
322
            'facet.prefix'  => $lastTerm,
323
        );
324
325
//		$facetSpec = array(
0 ignored issues
show
Unused Code Comprehensibility introduced by
49% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
326
//			'_autocomplete' => array(
327
//				'Type'      => ShopSearch::FACET_TYPE_LINK,
328
//				'Label'     => 'Suggestions',
329
//				'Source'    => '_autocomplete',
330
//			),
331
//		);
332
//
333
//		Debug::dump($query);
334
//
335
//		$search     = $this->search($query, 0, $limit, $params, $facetSpec);
336
//		Debug::dump($search);
337
//		$prodList   = $search->Matches;
338
//
339
//		$suggestsion = array();
340
////		if ($)
341
342
        $service = $this->getService();
343
344
        SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
345
346
        $q = $terms;
347
        $fq = array();
348
349
        // Build the search itself
0 ignored issues
show
Unused Code Comprehensibility introduced by
57% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
350
//		foreach ($query->search as $search) {
351
//			$text = $search['text'];
352
//			preg_match_all('/"[^"]*"|\S+/', $text, $parts);
353
//
354
//			$fuzzy = $search['fuzzy'] ? '~' : '';
355
//
356
//			foreach ($parts[0] as $part) {
357
//				$fields = (isset($search['fields'])) ? $search['fields'] : array();
358
//				if(isset($search['boost'])) $fields = array_merge($fields, array_keys($search['boost']));
359
//				if ($fields) {
360
//					$searchq = array();
361
//					foreach ($fields as $field) {
362
//						$boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
363
//						$searchq[] = "{$field}:".$part.$fuzzy.$boost;
364
//					}
365
//					$q[] = '+('.implode(' OR ', $searchq).')';
366
//				}
367
//				else {
368
//					$q[] = '+'.$part.$fuzzy;
369
//				}
370
//			}
371
//		}
372
373
        // Filter by class if requested
374
        $classq = array();
375
376 View Code Duplication
        foreach ($query->classes as $class) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
377
            if (!empty($class['includeSubclasses'])) {
378
                $classq[] = 'ClassHierarchy:'.$class['class'];
379
            } else {
380
                $classq[] = 'ClassName:'.$class['class'];
381
            }
382
        }
383
384
        if ($classq) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classq of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
385
            $fq[] = '+('.implode(' ', $classq).')';
386
        }
387
388
        // Filter by filters
389 View Code Duplication
        foreach ($query->require as $field => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
390
            $requireq = array();
391
392
            foreach ($values as $value) {
393
                if ($value === SearchQuery::$missing) {
394
                    $requireq[] = "(*:* -{$field}:[* TO *])";
395
                } elseif ($value === SearchQuery::$present) {
396
                    $requireq[] = "{$field}:[* TO *]";
397
                } elseif ($value instanceof SearchQuery_Range) {
0 ignored issues
show
Bug introduced by
The class SearchQuery_Range does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
398
                    $start = $value->start;
399
                    if ($start === null) {
400
                        $start = '*';
401
                    }
402
                    $end = $value->end;
403
                    if ($end === null) {
404
                        $end = '*';
405
                    }
406
                    $requireq[] = "$field:[$start TO $end]";
407
                } else {
408
                    $requireq[] = $field.':"'.$value.'"';
409
                }
410
            }
411
412
            $fq[] = '+('.implode(' ', $requireq).')';
413
        }
414
415 View Code Duplication
        foreach ($query->exclude as $field => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
416
            $excludeq = array();
417
            $missing = false;
418
419
            foreach ($values as $value) {
420
                if ($value === SearchQuery::$missing) {
421
                    $missing = true;
422
                } elseif ($value === SearchQuery::$present) {
423
                    $excludeq[] = "{$field}:[* TO *]";
424
                } elseif ($value instanceof SearchQuery_Range) {
0 ignored issues
show
Bug introduced by
The class SearchQuery_Range does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
425
                    $start = $value->start;
426
                    if ($start === null) {
427
                        $start = '*';
428
                    }
429
                    $end = $value->end;
430
                    if ($end === null) {
431
                        $end = '*';
432
                    }
433
                    $excludeq[] = "$field:[$start TO $end]";
434
                } else {
435
                    $excludeq[] = $field.':"'.$value.'"';
436
                }
437
            }
438
439
            $fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
440
        }
441
442
//		if(!headers_sent()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
443
//			if ($q) header('X-Query: '.implode(' ', $q));
444
//			if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
445
//		}
446
447
        $params = array_merge($params, array('fq' => implode(' ', $fq)));
448
449
        $res = $service->search(
450
            implode(' ', $q),
451
            0,
452
            $limit,
453
            $params,
454
            Apache_Solr_Service::METHOD_POST
455
        );
456
457
        $results = new ArrayList();
458
        if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
459
            foreach ($res->response->docs as $doc) {
460
                $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
461
                if ($result) {
462
                    $results->push($result);
463
                }
464
            }
465
            $numFound = $res->response->numFound;
466
        } else {
467
            $numFound = 0;
468
        }
469
470
        $ret = array();
471
        $ret['products'] = new PaginatedList($results);
472
        $ret['products']->setLimitItems(false);
473
        $ret['products']->setTotalItems($numFound);
474
        $ret['products']->setPageStart(0);
475
        $ret['products']->setPageLength($limit);
476
477
        // Facets (this is how we're doing suggestions for now...
478
        $ret['suggestions'] = array();
479
        if (isset($res->facet_counts->facet_fields->_autocomplete)) {
480
            foreach ($res->facet_counts->facet_fields->_autocomplete as $term => $count) {
481
                $ret['suggestions'][] = $prefix . $term;
482
            }
483
        }
484
485
        // Suggestions (requires custom setup, assumes spellcheck.collate=true)
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
486
//		if(isset($res->spellcheck->suggestions->collation)) {
487
//			$ret['Suggestion'] = $res->spellcheck->suggestions->collation;
488
//		}
489
490
        return $ret;
491
    }
492
493
    /**
494
     * @param $facets
495
     * @return array
496
     */
497
    protected function buildFacetParams(array $facets)
498
    {
499
        $params = array();
500
501
        if (!empty($facets)) {
502
            $params['facet'] = 'true';
503
504
            foreach ($facets as $name => $spec) {
505
                // With our current implementation, "range" facets aren't true facets in solr terms.
506
                // They're just a type of filter which can be handled elsewhere.
507
                // For the other types we just ignore the rest of the spec and let Solr do its thing
508
                if ($spec['Type'] != ShopSearch::FACET_TYPE_RANGE && isset($this->fieldMap[$name])) {
509
                    $params['facet.field'] = $this->fieldMap[$name];
510
                }
511
            }
512
        }
513
514
        return $params;
515
    }
516
517
518
    /**
519
     * Fulltextsearch module doesn't yet support facets very well, so I've just copied this function here so
520
     * we have access to the results. I'd prefer to modify it minimally so we can eventually get rid of it
521
     * once they add faceting or hooks to get directly at the returned response.
522
     *
523
     * @param SearchQuery $query
524
     * @param integer $offset
525
     * @param integer $limit
526
     * @param  Array $params Extra request parameters passed through to Solr
527
     * @param array $facetSpec - Added for ShopSearch so we can process the facets
528
     * @return ArrayData Map with the following keys:
529
     *  - 'Matches': ArrayList of the matched object instances
530
     */
531
    public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array(), $facetSpec = array())
532
    {
533
        $service = $this->getService();
534
535
        SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
536
537
        $q = array();
538
        $fq = array();
539
540
        // Build the search itself
541
542
        foreach ($query->search as $search) {
543
            $text = $search['text'];
544
            preg_match_all('/"[^"]*"|\S+/', $text, $parts);
545
546
            $fuzzy = $search['fuzzy'] ? '~' : '';
547
548
            foreach ($parts[0] as $part) {
549
                $fields = (isset($search['fields'])) ? $search['fields'] : array();
550
                if (isset($search['boost'])) {
551
                    $fields = array_merge($fields, array_keys($search['boost']));
552
                }
553
                if ($fields) {
554
                    $searchq = array();
555
                    foreach ($fields as $field) {
556
                        $boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
557
                        $searchq[] = "{$field}:".$part.$fuzzy.$boost;
558
                    }
559
                    $q[] = '+('.implode(' OR ', $searchq).')';
560
                } else {
561
                    $q[] = '+'.$part.$fuzzy;
562
                }
563
            }
564
        }
565
566
        // Filter by class if requested
567
568
        $classq = array();
569
570 View Code Duplication
        foreach ($query->classes as $class) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
571
            if (!empty($class['includeSubclasses'])) {
572
                $classq[] = 'ClassHierarchy:'.$class['class'];
573
            } else {
574
                $classq[] = 'ClassName:'.$class['class'];
575
            }
576
        }
577
578
        if ($classq) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $classq of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

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.

Loading history...
579
            $fq[] = '+('.implode(' ', $classq).')';
580
        }
581
582
        // Filter by filters
583
584 View Code Duplication
        foreach ($query->require as $field => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
585
            $requireq = array();
586
587
            foreach ($values as $value) {
588
                if ($value === SearchQuery::$missing) {
589
                    $requireq[] = "(*:* -{$field}:[* TO *])";
590
                } elseif ($value === SearchQuery::$present) {
591
                    $requireq[] = "{$field}:[* TO *]";
592
                } elseif ($value instanceof SearchQuery_Range) {
0 ignored issues
show
Bug introduced by
The class SearchQuery_Range does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
593
                    $start = $value->start;
594
                    if ($start === null) {
595
                        $start = '*';
596
                    }
597
                    $end = $value->end;
598
                    if ($end === null) {
599
                        $end = '*';
600
                    }
601
                    $requireq[] = "$field:[$start TO $end]";
602
                } else {
603
                    $requireq[] = $field.':"'.$value.'"';
604
                }
605
            }
606
607
            $fq[] = '+('.implode(' ', $requireq).')';
608
        }
609
610 View Code Duplication
        foreach ($query->exclude as $field => $values) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
611
            $excludeq = array();
612
            $missing = false;
613
614
            foreach ($values as $value) {
615
                if ($value === SearchQuery::$missing) {
616
                    $missing = true;
617
                } elseif ($value === SearchQuery::$present) {
618
                    $excludeq[] = "{$field}:[* TO *]";
619
                } elseif ($value instanceof SearchQuery_Range) {
0 ignored issues
show
Bug introduced by
The class SearchQuery_Range does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
620
                    $start = $value->start;
621
                    if ($start === null) {
622
                        $start = '*';
623
                    }
624
                    $end = $value->end;
625
                    if ($end === null) {
626
                        $end = '*';
627
                    }
628
                    $excludeq[] = "$field:[$start TO $end]";
629
                } else {
630
                    $excludeq[] = $field.':"'.$value.'"';
631
                }
632
            }
633
634
            $fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
635
        }
636
637
//		if(!headers_sent()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
62% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
638
//			if ($q) header('X-Query: '.implode(' ', $q));
639
//			if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
640
//		}
641
642
        if ($offset == -1) {
643
            $offset = $query->start;
644
        }
645
        if ($limit == -1) {
646
            $limit = $query->limit;
647
        }
648
        if ($limit == -1) {
649
            $limit = SearchQuery::$default_page_size;
650
        }
651
652
        $params = array_merge($params, array('fq' => implode(' ', $fq)));
653
654
        $res = $service->search(
655
            $q ? implode(' ', $q) : '*:*',
656
            $offset,
657
            $limit,
658
            $params,
659
            Apache_Solr_Service::METHOD_POST
660
        );
661
        //Debug::dump($res);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
662
663
        $results = new ArrayList();
664
        if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
665
            foreach ($res->response->docs as $doc) {
666
                $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
667
                if ($result) {
668
                    $results->push($result);
669
670
                    // Add highlighting (optional)
671
                    $docId = $doc->_documentid;
672
                    if ($res->highlighting && $res->highlighting->$docId) {
673
                        // TODO Create decorator class for search results rather than adding arbitrary object properties
674
                        // TODO Allow specifying highlighted field, and lazy loading
675
                        // in case the search API needs another query (similar to SphinxSearchable->buildExcerpt()).
676
                        $combinedHighlights = array();
677
                        foreach ($res->highlighting->$docId as $field => $highlights) {
678
                            $combinedHighlights = array_merge($combinedHighlights, $highlights);
679
                        }
680
681
                        // Remove entity-encoded U+FFFD replacement character. It signifies non-displayable characters,
682
                        // and shows up as an encoding error in browsers.
683
                        $result->Excerpt = DBField::create_field(
684
                            'HTMLText',
685
                            str_replace(
686
                                '&#65533;',
687
                                '',
688
                                implode(' ... ', $combinedHighlights)
689
                            )
690
                        );
691
                    }
692
                }
693
            }
694
            $numFound = $res->response->numFound;
695
        } else {
696
            $numFound = 0;
697
        }
698
699
        $ret = array();
700
        $ret['Matches'] = new PaginatedList($results);
701
        $ret['Matches']->setLimitItems(false);
702
        // Tell PaginatedList how many results there are
703
        $ret['Matches']->setTotalItems($numFound);
704
        // Results for current page start at $offset
705
        $ret['Matches']->setPageStart($offset);
706
        // Results per page
707
        $ret['Matches']->setPageLength($limit);
708
709
        // Facets
710
        //Debug::dump($res);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
711
        if (isset($res->facet_counts->facet_fields)) {
712
            $ret['Facets'] = $this->buildFacetResults($res->facet_counts->facet_fields, $facetSpec);
713
        }
714
715
        // Suggestions (requires custom setup, assumes spellcheck.collate=true)
716
        if (isset($res->spellcheck->suggestions->collation)) {
717
            $ret['Suggestion'] = $res->spellcheck->suggestions->collation;
718
        }
719
720
        return new ArrayData($ret);
721
    }
722
723
724
    /**
725
     * @param stdClass $facetFields
726
     * @param array $facetSpec
727
     * @return ArrayList
728
     */
729
    protected function buildFacetResults($facetFields, array $facetSpec)
730
    {
731
        $out = new ArrayList;
732
733
        foreach ($facetSpec as $field => $facet) {
734
            if ($facet['Type'] == ShopSearch::FACET_TYPE_RANGE) {
735
                // If it's a range facet, set up the min/max
736
                // TODO: we could probably get the real min and max with solr's range faceting if we tried
737
                if (isset($facet['RangeMin'])) {
738
                    $facet['MinValue'] = $facet['RangeMin'];
739
                }
740
                if (isset($facet['RangeMax'])) {
741
                    $facet['MaxValue'] = $facet['RangeMax'];
742
                }
743
                $out->push(new ArrayData($facet));
744
            } elseif (isset($this->fieldMap[$field])) {
745
                // Otherwise, look through Solr's results
746
                $mySolrName = $this->fieldMap[$field];
747
                foreach ($facetFields as $solrName => $values) {
0 ignored issues
show
Bug introduced by
The expression $facetFields of type object<stdClass> is not traversable.
Loading history...
748
                    if ($solrName == $mySolrName) {
749
                        // we found a match, look through the values we were given
750
                        foreach ($values as $val => $count) {
751
                            if (!isset($facet['Values'][$val])) {
752
                                // for link type facets we want to add anything
753
                                // for checkboxes, if it's not in the provided list we leave it out
754
                                if ($facet['Type'] != ShopSearch::FACET_TYPE_CHECKBOX && $count > 0) {
755
                                    $facet['Values'][$val] = new ArrayData(array(
756
                                        'Label'     => $val,
757
                                        'Value'     => $val,
758
                                        'Count'     => $count,
759
                                    ));
760
                                }
761
                            } elseif ($facet['Values'][$val]) {
762
                                $facet['Values'][$val]->Count = $count;
763
                            }
764
                        }
765
                    }
766
                }
767
768
                // then add that to the stack
769
                $facet['Values'] = new ArrayList($facet['Values']);
770
                $out->push(new ArrayData($facet));
771
            }
772
        }
773
774
        //Debug::dump($out);
0 ignored issues
show
Unused Code Comprehensibility introduced by
72% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
775
        return $out;
776
    }
777
}
778