Completed
Push — master ( 08af57...01b1ac )
by Mark
17:55
created
code/SearchLog.php 1 patch
Indentation   +50 added lines, -50 removed lines patch added patch discarded remove patch
@@ -8,54 +8,54 @@
 block discarded – undo
8 8
  */
9 9
 class SearchLog extends DataObject
10 10
 {
11
-    private static $db = array(
12
-        'Query'         => 'Varchar(255)',
13
-        'Title'         => 'Varchar(255)', // title for breadcrumbs. any new facets added will be reflected here
14
-        'Link'          => 'Varchar(255)',
15
-        'Filters'       => 'Text', // json
16
-        'NumResults'    => 'Int',
17
-    );
18
-
19
-    private static $has_one = array(
20
-        'Member'        => 'Member',
21
-        'ParentSearch'  => 'SearchLog', // used in constructing a search breadcrumb
22
-    );
23
-
24
-
25
-    /**
26
-     * Generate the title if needed
27
-     */
28
-    protected function onBeforeWrite()
29
-    {
30
-        parent::onBeforeWrite();
31
-        if (!$this->Title) {
32
-            $this->Title = empty($this->Query) ? "Search" : "Search: {$this->Query}";
33
-        }
34
-    }
35
-
36
-
37
-    /**
38
-     * @return ArrayList
39
-     */
40
-    public function getBreadcrumbs()
41
-    {
42
-        $out    = new ArrayList();
43
-        $cur    = $this;
44
-
45
-        while ($cur && $cur->exists()) {
46
-            $out->unshift($cur);
47
-            $cur = $cur->ParentSearchID > 0 ? $cur->ParentSearch() : null;
48
-        }
49
-
50
-        return $out;
51
-    }
52
-
53
-
54
-    /**
55
-     * @return array
56
-     */
57
-    public function getFiltersArray()
58
-    {
59
-        return $this->Filters ? json_decode($this->Filters, true) : array();
60
-    }
11
+	private static $db = array(
12
+		'Query'         => 'Varchar(255)',
13
+		'Title'         => 'Varchar(255)', // title for breadcrumbs. any new facets added will be reflected here
14
+		'Link'          => 'Varchar(255)',
15
+		'Filters'       => 'Text', // json
16
+		'NumResults'    => 'Int',
17
+	);
18
+
19
+	private static $has_one = array(
20
+		'Member'        => 'Member',
21
+		'ParentSearch'  => 'SearchLog', // used in constructing a search breadcrumb
22
+	);
23
+
24
+
25
+	/**
26
+	 * Generate the title if needed
27
+	 */
28
+	protected function onBeforeWrite()
29
+	{
30
+		parent::onBeforeWrite();
31
+		if (!$this->Title) {
32
+			$this->Title = empty($this->Query) ? "Search" : "Search: {$this->Query}";
33
+		}
34
+	}
35
+
36
+
37
+	/**
38
+	 * @return ArrayList
39
+	 */
40
+	public function getBreadcrumbs()
41
+	{
42
+		$out    = new ArrayList();
43
+		$cur    = $this;
44
+
45
+		while ($cur && $cur->exists()) {
46
+			$out->unshift($cur);
47
+			$cur = $cur->ParentSearchID > 0 ? $cur->ParentSearch() : null;
48
+		}
49
+
50
+		return $out;
51
+	}
52
+
53
+
54
+	/**
55
+	 * @return array
56
+	 */
57
+	public function getFiltersArray()
58
+	{
59
+		return $this->Filters ? json_decode($this->Filters, true) : array();
60
+	}
61 61
 }
Please login to merge, or discard this patch.
code/ShopSearch.php 1 patch
Indentation   +356 added lines, -356 removed lines patch added patch discarded remove patch
@@ -1,383 +1,383 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Fulltext search index for shop buyables
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 08.29.2013
7
- * @package shop_search
8
- */
3
+	 * Fulltext search index for shop buyables
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 08.29.2013
7
+	 * @package shop_search
8
+	 */
9 9
 class ShopSearch extends Object
10 10
 {
11
-    const FACET_TYPE_LINK       = 'link';
12
-    const FACET_TYPE_CHECKBOX   = 'checkbox';
13
-    const FACET_TYPE_RANGE      = 'range';
11
+	const FACET_TYPE_LINK       = 'link';
12
+	const FACET_TYPE_CHECKBOX   = 'checkbox';
13
+	const FACET_TYPE_RANGE      = 'range';
14 14
 
15
-    /** @var string - class name of adapter class to use */
16
-    private static $adapter_class = 'ShopSearchSimple';
15
+	/** @var string - class name of adapter class to use */
16
+	private static $adapter_class = 'ShopSearchSimple';
17 17
 
18
-    /** @var array - these classes will be added to the index - e.g. Category, Page, etc. */
19
-    private static $searchable = array();
18
+	/** @var array - these classes will be added to the index - e.g. Category, Page, etc. */
19
+	private static $searchable = array();
20 20
 
21
-    /** @var bool - if true, all buyable models will be added to the index automatically  */
22
-    private static $buyables_are_searchable = true;
21
+	/** @var bool - if true, all buyable models will be added to the index automatically  */
22
+	private static $buyables_are_searchable = true;
23 23
 
24
-    /** @var int - size of paging in the search */
25
-    private static $page_size = 10;
24
+	/** @var int - size of paging in the search */
25
+	private static $page_size = 10;
26 26
 
27
-    /** @var bool */
28
-    private static $suggest_enabled = true;
27
+	/** @var bool */
28
+	private static $suggest_enabled = true;
29 29
 
30
-    /** @var int - how many suggestions to provide */
31
-    private static $suggest_limit = 5;
30
+	/** @var int - how many suggestions to provide */
31
+	private static $suggest_limit = 5;
32 32
 
33
-    /** @var bool */
34
-    private static $search_as_you_type_enabled = true;
33
+	/** @var bool */
34
+	private static $search_as_you_type_enabled = true;
35 35
 
36
-    /** @var int - how may sayt (search-as-you-type) entries to provide */
37
-    private static $sayt_limit = 5;
36
+	/** @var int - how may sayt (search-as-you-type) entries to provide */
37
+	private static $sayt_limit = 5;
38 38
 
39
-    /** @var bool - automatically create facets for static attributes */
40
-    private static $auto_facet_attributes = false;
39
+	/** @var bool - automatically create facets for static attributes */
40
+	private static $auto_facet_attributes = false;
41 41
 
42
-    /** @var string - optionally, a different template to run ajax results through (sans-Page.ss) */
43
-    private static $ajax_results_template = '';
42
+	/** @var string - optionally, a different template to run ajax results through (sans-Page.ss) */
43
+	private static $ajax_results_template = '';
44 44
 
45
-    /** @var string - these allow you to use different querystring params in you need to */
46
-    private static $qs_query         = 'q';
47
-    private static $qs_filters       = 'f';
48
-    private static $qs_parent_search = '__ps';
49
-    private static $qs_title         = '__t';
50
-    private static $qs_source        = '__src'; // used to log searches from search-as-you-type
51
-    private static $qs_sort          = 'sort';
45
+	/** @var string - these allow you to use different querystring params in you need to */
46
+	private static $qs_query         = 'q';
47
+	private static $qs_filters       = 'f';
48
+	private static $qs_parent_search = '__ps';
49
+	private static $qs_title         = '__t';
50
+	private static $qs_source        = '__src'; // used to log searches from search-as-you-type
51
+	private static $qs_sort          = 'sort';
52 52
 
53
-    /** @var array - I'm leaving this particularly bare b/c with config merging it's a pain to remove items */
54
-    private static $sort_options = array(
55
-        'score desc'            => 'Relevance',
53
+	/** @var array - I'm leaving this particularly bare b/c with config merging it's a pain to remove items */
54
+	private static $sort_options = array(
55
+		'score desc'            => 'Relevance',
56 56
 //		'SiteTree_Title asc'    => 'Alphabetical (A-Z)',
57 57
 //		'SiteTree_Title dsc'    => 'Alphabetical (Z-A)',
58
-    );
59
-
60
-    /**
61
-     * @var array - default search facets (price, category, etc)
62
-     *   Key    field name - e.g. Price - can be a VirtualFieldIndex field
63
-     *   Value  facet label - e.g. Search By Category - if the value is a relation or returns an array or
64
-     *          list all values will be faceted individually
65
-     *          NOTE: this can also be another array with keys: Label, Type, and Values (for checkbox only)
66
-     */
67
-    private static $facets = array();
68
-
69
-    /** @var array - field definition for Solr only */
70
-    private static $solr_fulltext_fields = array();
71
-
72
-    /** @var array - field definition for Solr only */
73
-    private static $solr_filter_fields = array();
74
-
75
-    /** @var string - if present, will create a copy of SiteTree_Title that's suited for alpha sorting */
76
-    private static $solr_title_sort_field = '';
77
-
78
-    /**
79
-     * @var string - If present, everything matching the following regex will be removed from
80
-     *               keyword search queries before passing to the search adapter.
81
-     */
82
-    private static $keyword_filter_regex = '/[^a-zA-Z0-9\s\-]/';
83
-
84
-
85
-    /**
86
-     * @return array
87
-     */
88
-    public static function get_searchable_classes()
89
-    {
90
-        // First get any explicitly declared searchable classes
91
-        $searchable = Config::inst()->get('ShopSearch', 'searchable');
92
-        if (is_string($searchable) && strlen($searchable) > 0) {
93
-            $searchable = array($searchable);
94
-        } elseif (!is_array($searchable)) {
95
-            $searchable = array();
96
-        }
97
-
98
-        // Add in buyables automatically if asked
99
-        if (Config::inst()->get('ShopSearch', 'buyables_are_searchable')) {
100
-            $buyables = SS_ClassLoader::instance()->getManifest()->getImplementorsOf('Buyable');
101
-            if (is_array($buyables) && count($buyables) > 0) {
102
-                foreach ($buyables as $c) {
103
-                    $searchable[] = $c;
104
-                }
105
-            }
106
-        }
107
-
108
-        return array_unique($searchable);
109
-    }
110
-
111
-    /**
112
-     * Returns an array of categories suitable for a dropdown menu
113
-     * TODO: cache this
114
-     *
115
-     * @param int $parentID [optional]
116
-     * @param string $prefix [optional]
117
-     * @param int $maxDepth [optional]
118
-     * @return array
119
-     * @static
120
-     */
121
-    public static function get_category_hierarchy($parentID = 0, $prefix = '', $maxDepth = 999)
122
-    {
123
-        $out = array();
124
-        $cats = ProductCategory::get()
125
-            ->filter(array(
126
-                'ParentID'      => $parentID,
127
-                'ShowInMenus'   => 1,
128
-            ))
129
-            ->sort('Sort');
130
-
131
-        // If there is a single parent category (usually "Products" or something), we
132
-        // probably don't want that in the hierarchy.
133
-        if ($parentID == 0 && $cats->count() == 1) {
134
-            return self::get_category_hierarchy($cats->first()->ID, $prefix, $maxDepth);
135
-        }
136
-
137
-        foreach ($cats as $cat) {
138
-            $out[$cat->ID] = $prefix . $cat->Title;
139
-            if ($maxDepth > 1) {
140
-                $out += self::get_category_hierarchy($cat->ID, $prefix . $cat->Title . ' > ', $maxDepth - 1);
141
-            }
142
-        }
143
-
144
-        return $out;
145
-    }
146
-
147
-    /**
148
-     * @return ShopSearchAdapter
149
-     */
150
-    public static function adapter()
151
-    {
152
-        $adapterClass = Config::inst()->get('ShopSearch', 'adapter_class');
153
-        return Injector::inst()->get($adapterClass);
154
-    }
155
-
156
-    /**
157
-     * @return ShopSearch
158
-     */
159
-    public static function inst()
160
-    {
161
-        return Injector::inst()->get('ShopSearch');
162
-    }
163
-
164
-    /**
165
-     * The result will contain at least the following:
166
-     *      Matches - SS_List of results
167
-     *      TotalMatches - total # of results, unlimited
168
-     *      Query - query string
169
-     * Also saves a log record.
170
-     *
171
-     * @param array $vars
172
-     * @param bool $logSearch [optional]
173
-     * @param bool $useFacets [optional]
174
-     * @param int $start [optional]
175
-     * @param int $limit [optional]
176
-     * @return ArrayData
177
-     */
178
-    public function search(array $vars, $logSearch=true, $useFacets=true, $start=-1, $limit=-1)
179
-    {
180
-        $qs_q   = $this->config()->get('qs_query');
181
-        $qs_f   = $this->config()->get('qs_filters');
182
-        $qs_ps  = $this->config()->get('qs_parent_search');
183
-        $qs_t   = $this->config()->get('qs_title');
184
-        $qs_sort= $this->config()->get('qs_sort');
185
-        if ($limit < 0) {
186
-            $limit  = $this->config()->get('page_size');
187
-        }
188
-        if ($start < 0) {
189
-            $start  = !empty($vars['start']) ? (int)$vars['start'] : 0;
190
-        } // as far as i can see, fulltextsearch hard codes 'start'
191
-        $facets = $useFacets ? $this->config()->get('facets') : array();
192
-        if (!is_array($facets)) {
193
-            $facets = array();
194
-        }
195
-        if (empty($limit)) {
196
-            $limit = -1;
197
-        }
198
-
199
-        // figure out and scrub the sort
200
-        $sortOptions = $this->config()->get('sort_options');
201
-        $sort        = !empty($vars[$qs_sort]) ? $vars[$qs_sort] : '';
202
-        if (!isset($sortOptions[$sort])) {
203
-            $sort    = current(array_keys($sortOptions));
204
-        }
205
-
206
-        // figure out and scrub the filters
207
-        $filters  = !empty($vars[$qs_f]) ? FacetHelper::inst()->scrubFilters($vars[$qs_f]) : array();
208
-
209
-        // do the search
210
-        $keywords = !empty($vars[$qs_q]) ? $vars[$qs_q] : '';
211
-        if ($keywordRegex = $this->config()->get('keyword_filter_regex')) {
212
-            $keywords = preg_replace($keywordRegex, '', $keywords);
213
-        }
214
-        $results  = self::adapter()->searchFromVars($keywords, $filters, $facets, $start, $limit, $sort);
215
-
216
-        // massage the results a bit
217
-        if (!empty($keywords) && !$results->hasValue('Query')) {
218
-            $results->Query = $keywords;
219
-        }
220
-        if (!empty($filters) && !$results->hasValue('Filters')) {
221
-            $results->Filters = new ArrayData($filters);
222
-        }
223
-        if (!$results->hasValue('Sort')) {
224
-            $results->Sort = $sort;
225
-        }
226
-        if (!$results->hasValue('TotalMatches')) {
227
-            $results->TotalMatches = $results->Matches->hasMethod('getTotalItems')
228
-                ? $results->Matches->getTotalItems()
229
-                : $results->Matches->count();
230
-        }
231
-
232
-        // for some types of facets, update the state
233
-        if ($results->hasValue('Facets')) {
234
-            FacetHelper::inst()->transformHierarchies($results->Facets);
235
-            FacetHelper::inst()->updateFacetState($results->Facets, $filters);
236
-        }
237
-
238
-        // make a hash of the search so we can know if we've already logged it this session
239
-        $loggedFilters = !empty($filters) ? json_encode($filters) : null;
240
-        $loggedQuery   = strtolower($results->Query);
58
+	);
59
+
60
+	/**
61
+	 * @var array - default search facets (price, category, etc)
62
+	 *   Key    field name - e.g. Price - can be a VirtualFieldIndex field
63
+	 *   Value  facet label - e.g. Search By Category - if the value is a relation or returns an array or
64
+	 *          list all values will be faceted individually
65
+	 *          NOTE: this can also be another array with keys: Label, Type, and Values (for checkbox only)
66
+	 */
67
+	private static $facets = array();
68
+
69
+	/** @var array - field definition for Solr only */
70
+	private static $solr_fulltext_fields = array();
71
+
72
+	/** @var array - field definition for Solr only */
73
+	private static $solr_filter_fields = array();
74
+
75
+	/** @var string - if present, will create a copy of SiteTree_Title that's suited for alpha sorting */
76
+	private static $solr_title_sort_field = '';
77
+
78
+	/**
79
+	 * @var string - If present, everything matching the following regex will be removed from
80
+	 *               keyword search queries before passing to the search adapter.
81
+	 */
82
+	private static $keyword_filter_regex = '/[^a-zA-Z0-9\s\-]/';
83
+
84
+
85
+	/**
86
+	 * @return array
87
+	 */
88
+	public static function get_searchable_classes()
89
+	{
90
+		// First get any explicitly declared searchable classes
91
+		$searchable = Config::inst()->get('ShopSearch', 'searchable');
92
+		if (is_string($searchable) && strlen($searchable) > 0) {
93
+			$searchable = array($searchable);
94
+		} elseif (!is_array($searchable)) {
95
+			$searchable = array();
96
+		}
97
+
98
+		// Add in buyables automatically if asked
99
+		if (Config::inst()->get('ShopSearch', 'buyables_are_searchable')) {
100
+			$buyables = SS_ClassLoader::instance()->getManifest()->getImplementorsOf('Buyable');
101
+			if (is_array($buyables) && count($buyables) > 0) {
102
+				foreach ($buyables as $c) {
103
+					$searchable[] = $c;
104
+				}
105
+			}
106
+		}
107
+
108
+		return array_unique($searchable);
109
+	}
110
+
111
+	/**
112
+	 * Returns an array of categories suitable for a dropdown menu
113
+	 * TODO: cache this
114
+	 *
115
+	 * @param int $parentID [optional]
116
+	 * @param string $prefix [optional]
117
+	 * @param int $maxDepth [optional]
118
+	 * @return array
119
+	 * @static
120
+	 */
121
+	public static function get_category_hierarchy($parentID = 0, $prefix = '', $maxDepth = 999)
122
+	{
123
+		$out = array();
124
+		$cats = ProductCategory::get()
125
+			->filter(array(
126
+				'ParentID'      => $parentID,
127
+				'ShowInMenus'   => 1,
128
+			))
129
+			->sort('Sort');
130
+
131
+		// If there is a single parent category (usually "Products" or something), we
132
+		// probably don't want that in the hierarchy.
133
+		if ($parentID == 0 && $cats->count() == 1) {
134
+			return self::get_category_hierarchy($cats->first()->ID, $prefix, $maxDepth);
135
+		}
136
+
137
+		foreach ($cats as $cat) {
138
+			$out[$cat->ID] = $prefix . $cat->Title;
139
+			if ($maxDepth > 1) {
140
+				$out += self::get_category_hierarchy($cat->ID, $prefix . $cat->Title . ' > ', $maxDepth - 1);
141
+			}
142
+		}
143
+
144
+		return $out;
145
+	}
146
+
147
+	/**
148
+	 * @return ShopSearchAdapter
149
+	 */
150
+	public static function adapter()
151
+	{
152
+		$adapterClass = Config::inst()->get('ShopSearch', 'adapter_class');
153
+		return Injector::inst()->get($adapterClass);
154
+	}
155
+
156
+	/**
157
+	 * @return ShopSearch
158
+	 */
159
+	public static function inst()
160
+	{
161
+		return Injector::inst()->get('ShopSearch');
162
+	}
163
+
164
+	/**
165
+	 * The result will contain at least the following:
166
+	 *      Matches - SS_List of results
167
+	 *      TotalMatches - total # of results, unlimited
168
+	 *      Query - query string
169
+	 * Also saves a log record.
170
+	 *
171
+	 * @param array $vars
172
+	 * @param bool $logSearch [optional]
173
+	 * @param bool $useFacets [optional]
174
+	 * @param int $start [optional]
175
+	 * @param int $limit [optional]
176
+	 * @return ArrayData
177
+	 */
178
+	public function search(array $vars, $logSearch=true, $useFacets=true, $start=-1, $limit=-1)
179
+	{
180
+		$qs_q   = $this->config()->get('qs_query');
181
+		$qs_f   = $this->config()->get('qs_filters');
182
+		$qs_ps  = $this->config()->get('qs_parent_search');
183
+		$qs_t   = $this->config()->get('qs_title');
184
+		$qs_sort= $this->config()->get('qs_sort');
185
+		if ($limit < 0) {
186
+			$limit  = $this->config()->get('page_size');
187
+		}
188
+		if ($start < 0) {
189
+			$start  = !empty($vars['start']) ? (int)$vars['start'] : 0;
190
+		} // as far as i can see, fulltextsearch hard codes 'start'
191
+		$facets = $useFacets ? $this->config()->get('facets') : array();
192
+		if (!is_array($facets)) {
193
+			$facets = array();
194
+		}
195
+		if (empty($limit)) {
196
+			$limit = -1;
197
+		}
198
+
199
+		// figure out and scrub the sort
200
+		$sortOptions = $this->config()->get('sort_options');
201
+		$sort        = !empty($vars[$qs_sort]) ? $vars[$qs_sort] : '';
202
+		if (!isset($sortOptions[$sort])) {
203
+			$sort    = current(array_keys($sortOptions));
204
+		}
205
+
206
+		// figure out and scrub the filters
207
+		$filters  = !empty($vars[$qs_f]) ? FacetHelper::inst()->scrubFilters($vars[$qs_f]) : array();
208
+
209
+		// do the search
210
+		$keywords = !empty($vars[$qs_q]) ? $vars[$qs_q] : '';
211
+		if ($keywordRegex = $this->config()->get('keyword_filter_regex')) {
212
+			$keywords = preg_replace($keywordRegex, '', $keywords);
213
+		}
214
+		$results  = self::adapter()->searchFromVars($keywords, $filters, $facets, $start, $limit, $sort);
215
+
216
+		// massage the results a bit
217
+		if (!empty($keywords) && !$results->hasValue('Query')) {
218
+			$results->Query = $keywords;
219
+		}
220
+		if (!empty($filters) && !$results->hasValue('Filters')) {
221
+			$results->Filters = new ArrayData($filters);
222
+		}
223
+		if (!$results->hasValue('Sort')) {
224
+			$results->Sort = $sort;
225
+		}
226
+		if (!$results->hasValue('TotalMatches')) {
227
+			$results->TotalMatches = $results->Matches->hasMethod('getTotalItems')
228
+				? $results->Matches->getTotalItems()
229
+				: $results->Matches->count();
230
+		}
231
+
232
+		// for some types of facets, update the state
233
+		if ($results->hasValue('Facets')) {
234
+			FacetHelper::inst()->transformHierarchies($results->Facets);
235
+			FacetHelper::inst()->updateFacetState($results->Facets, $filters);
236
+		}
237
+
238
+		// make a hash of the search so we can know if we've already logged it this session
239
+		$loggedFilters = !empty($filters) ? json_encode($filters) : null;
240
+		$loggedQuery   = strtolower($results->Query);
241 241
 //		$searchHash    = md5($loggedFilters . $loggedQuery);
242 242
 //		$sessSearches  = Session::get('loggedSearches');
243 243
 //		if (!is_array($sessSearches)) $sessSearches = array();
244 244
 //		Debug::dump($searchHash, $sessSearches);
245 245
 
246
-        // save the log record
247
-        if ($start == 0 && $logSearch && (!empty($keywords) || !empty($filters))) { // && !in_array($searchHash, $sessSearches)) {
248
-            $log = SearchLog::create(array(
249
-                'Query'         => $loggedQuery,
250
-                'Title'         => !empty($vars[$qs_t]) ? $vars[$qs_t] : '',
251
-                'Link'          => Controller::curr()->getRequest()->getURL(true), // I'm not 100% happy with this, but can't think of a better way
252
-                'NumResults'    => $results->TotalMatches,
253
-                'MemberID'      => Member::currentUserID(),
254
-                'Filters'       => $loggedFilters,
255
-                'ParentSearchID'=> !empty($vars[$qs_ps]) ? $vars[$qs_ps] : 0,
256
-            ));
257
-            $log->write();
258
-            $results->SearchLogID = $log->ID;
259
-            $results->SearchBreadcrumbs = $log->getBreadcrumbs();
246
+		// save the log record
247
+		if ($start == 0 && $logSearch && (!empty($keywords) || !empty($filters))) { // && !in_array($searchHash, $sessSearches)) {
248
+			$log = SearchLog::create(array(
249
+				'Query'         => $loggedQuery,
250
+				'Title'         => !empty($vars[$qs_t]) ? $vars[$qs_t] : '',
251
+				'Link'          => Controller::curr()->getRequest()->getURL(true), // I'm not 100% happy with this, but can't think of a better way
252
+				'NumResults'    => $results->TotalMatches,
253
+				'MemberID'      => Member::currentUserID(),
254
+				'Filters'       => $loggedFilters,
255
+				'ParentSearchID'=> !empty($vars[$qs_ps]) ? $vars[$qs_ps] : 0,
256
+			));
257
+			$log->write();
258
+			$results->SearchLogID = $log->ID;
259
+			$results->SearchBreadcrumbs = $log->getBreadcrumbs();
260 260
 
261 261
 //			$sessSearches[] = $searchHash;
262 262
 //			Session::set('loggedSearches', $sessSearches);
263
-        }
264
-
265
-        return $results;
266
-    }
267
-
268
-    /**
269
-     * @param string $str
270
-     * @return SS_Query
271
-     */
272
-    public function getSuggestQuery($str='')
273
-    {
274
-        $hasResults = 'CASE WHEN max("SearchLog"."NumResults") > 0 THEN 1 ELSE 0 END';
275
-        $searchCount = 'count(distinct "SearchLog"."ID")';
276
-        $q = new SQLQuery();
277
-        $q = $q->setSelect('"SearchLog"."Query"')
278
-            // TODO: what to do with filter?
279
-            ->selectField($searchCount, 'SearchCount')
280
-            ->selectField('max("SearchLog"."Created")', 'LastSearch')
281
-            ->selectField('max("SearchLog"."NumResults")', 'NumResults')
282
-            ->selectField($hasResults, 'HasResults')
283
-            ->setFrom('"SearchLog"')
284
-            ->setGroupBy('"SearchLog"."Query"')
285
-            ->setOrderBy(array(
286
-                "$hasResults DESC",
287
-                "$searchCount DESC"
288
-            ))
289
-            ->setLimit(Config::inst()->get('ShopSearch', 'suggest_limit'))
290
-        ;
291
-
292
-        if (strlen($str) > 0) {
293
-            $q = $q->addWhere(sprintf('"SearchLog"."Query" LIKE \'%%%s%%\'', Convert::raw2sql($str)));
294
-        }
295
-
296
-        return $q;
297
-    }
298
-
299
-
300
-    /**
301
-     * @param string $str
302
-     * @return array
303
-     */
304
-    public function suggest($str='')
305
-    {
306
-        $adapter = self::adapter();
307
-        if ($adapter->hasMethod('suggest')) {
308
-            return $adapter->suggest($str);
309
-        } else {
310
-            return $this->getSuggestQuery($str)->execute()->column('Query');
311
-        }
312
-    }
313
-
314
-
315
-    /**
316
-     * Returns an array that can be made into json and passed to the controller
317
-     * containing both term suggestions and a few product matches.
318
-     *
319
-     * @param array $searchVars
320
-     * @return array
321
-     */
322
-    public function suggestWithResults(array $searchVars)
323
-    {
324
-        $qs_q       = $this->config()->get('qs_query');
325
-        $qs_f       = $this->config()->get('qs_filters');
326
-        $keywords   = !empty($searchVars[$qs_q]) ? $searchVars[$qs_q] : '';
327
-        $filters    = !empty($searchVars[$qs_f]) ? $searchVars[$qs_f] : array();
328
-
329
-        $adapter = self::adapter();
330
-
331
-        // get suggestions and product list from the adapter
332
-        if ($adapter->hasMethod('suggestWithResults')) {
333
-            $results = $adapter->suggestWithResults($keywords, $filters);
334
-        } else {
335
-            $limit      = (int)ShopSearch::config()->sayt_limit;
336
-            $search     = self::adapter()->searchFromVars($keywords, $filters, array(), 0, $limit, 'Popularity DESC');
337
-            //$search     = ShopSearch::inst()->search($searchVars, false, false, 0, $limit);
338
-
339
-            $results = array(
340
-                'products'      => $search->Matches,
341
-                'suggestions'   => $this->suggest($keywords),
342
-            );
343
-        }
344
-
345
-        // the adapter just gave us a list of products, which we need to process a little further
346
-        if (!empty($results['products'])) {
347
-            // this gets encoded into the product links
348
-            $searchVars['total'] = $results['products']->hasMethod('getTotalItems')
349
-                ? $results['products']->getTotalItems()
350
-                : $results['products']->count();
351
-
352
-            $products   = array();
353
-            foreach ($results['products'] as $prod) {
354
-                if (!$prod || !$prod->exists()) {
355
-                    continue;
356
-                }
357
-                $img = $prod->hasMethod('ProductImage') ? $prod->ProductImage() : $prod->Image();
358
-                $thumb = ($img && $img->exists()) ? $img->getThumbnail() : null;
359
-
360
-                $json = array(
361
-                    'link'  => $prod->Link() . '?' . ShopSearch::config()->qs_source . '=' . urlencode(base64_encode(json_encode($searchVars))),
362
-                    'title' => $prod->Title,
363
-                    'desc'  => $prod->obj('Content')->Summary(),
364
-                    'thumb' => $thumb ? $thumb->Link() : '',
365
-                    'price' => $prod->obj('Price')->Nice(),
366
-                );
367
-
368
-                if ($prod->hasExtension('HasPromotionalPricing') && $prod->hasValidPromotion()) {
369
-                    $json['original_price'] = $prod->getOriginalPrice()->Nice();
370
-                }
371
-
372
-                $products[] = $json;
373
-            }
374
-
375
-            // replace the list of product objects with json
376
-            $results['products'] = $products;
377
-        }
378
-
379
-        $this->extend('updateSuggestWithResults', $results, $keywords, $filters);
380
-
381
-        return $results;
382
-    }
263
+		}
264
+
265
+		return $results;
266
+	}
267
+
268
+	/**
269
+	 * @param string $str
270
+	 * @return SS_Query
271
+	 */
272
+	public function getSuggestQuery($str='')
273
+	{
274
+		$hasResults = 'CASE WHEN max("SearchLog"."NumResults") > 0 THEN 1 ELSE 0 END';
275
+		$searchCount = 'count(distinct "SearchLog"."ID")';
276
+		$q = new SQLQuery();
277
+		$q = $q->setSelect('"SearchLog"."Query"')
278
+			// TODO: what to do with filter?
279
+			->selectField($searchCount, 'SearchCount')
280
+			->selectField('max("SearchLog"."Created")', 'LastSearch')
281
+			->selectField('max("SearchLog"."NumResults")', 'NumResults')
282
+			->selectField($hasResults, 'HasResults')
283
+			->setFrom('"SearchLog"')
284
+			->setGroupBy('"SearchLog"."Query"')
285
+			->setOrderBy(array(
286
+				"$hasResults DESC",
287
+				"$searchCount DESC"
288
+			))
289
+			->setLimit(Config::inst()->get('ShopSearch', 'suggest_limit'))
290
+		;
291
+
292
+		if (strlen($str) > 0) {
293
+			$q = $q->addWhere(sprintf('"SearchLog"."Query" LIKE \'%%%s%%\'', Convert::raw2sql($str)));
294
+		}
295
+
296
+		return $q;
297
+	}
298
+
299
+
300
+	/**
301
+	 * @param string $str
302
+	 * @return array
303
+	 */
304
+	public function suggest($str='')
305
+	{
306
+		$adapter = self::adapter();
307
+		if ($adapter->hasMethod('suggest')) {
308
+			return $adapter->suggest($str);
309
+		} else {
310
+			return $this->getSuggestQuery($str)->execute()->column('Query');
311
+		}
312
+	}
313
+
314
+
315
+	/**
316
+	 * Returns an array that can be made into json and passed to the controller
317
+	 * containing both term suggestions and a few product matches.
318
+	 *
319
+	 * @param array $searchVars
320
+	 * @return array
321
+	 */
322
+	public function suggestWithResults(array $searchVars)
323
+	{
324
+		$qs_q       = $this->config()->get('qs_query');
325
+		$qs_f       = $this->config()->get('qs_filters');
326
+		$keywords   = !empty($searchVars[$qs_q]) ? $searchVars[$qs_q] : '';
327
+		$filters    = !empty($searchVars[$qs_f]) ? $searchVars[$qs_f] : array();
328
+
329
+		$adapter = self::adapter();
330
+
331
+		// get suggestions and product list from the adapter
332
+		if ($adapter->hasMethod('suggestWithResults')) {
333
+			$results = $adapter->suggestWithResults($keywords, $filters);
334
+		} else {
335
+			$limit      = (int)ShopSearch::config()->sayt_limit;
336
+			$search     = self::adapter()->searchFromVars($keywords, $filters, array(), 0, $limit, 'Popularity DESC');
337
+			//$search     = ShopSearch::inst()->search($searchVars, false, false, 0, $limit);
338
+
339
+			$results = array(
340
+				'products'      => $search->Matches,
341
+				'suggestions'   => $this->suggest($keywords),
342
+			);
343
+		}
344
+
345
+		// the adapter just gave us a list of products, which we need to process a little further
346
+		if (!empty($results['products'])) {
347
+			// this gets encoded into the product links
348
+			$searchVars['total'] = $results['products']->hasMethod('getTotalItems')
349
+				? $results['products']->getTotalItems()
350
+				: $results['products']->count();
351
+
352
+			$products   = array();
353
+			foreach ($results['products'] as $prod) {
354
+				if (!$prod || !$prod->exists()) {
355
+					continue;
356
+				}
357
+				$img = $prod->hasMethod('ProductImage') ? $prod->ProductImage() : $prod->Image();
358
+				$thumb = ($img && $img->exists()) ? $img->getThumbnail() : null;
359
+
360
+				$json = array(
361
+					'link'  => $prod->Link() . '?' . ShopSearch::config()->qs_source . '=' . urlencode(base64_encode(json_encode($searchVars))),
362
+					'title' => $prod->Title,
363
+					'desc'  => $prod->obj('Content')->Summary(),
364
+					'thumb' => $thumb ? $thumb->Link() : '',
365
+					'price' => $prod->obj('Price')->Nice(),
366
+				);
367
+
368
+				if ($prod->hasExtension('HasPromotionalPricing') && $prod->hasValidPromotion()) {
369
+					$json['original_price'] = $prod->getOriginalPrice()->Nice();
370
+				}
371
+
372
+				$products[] = $json;
373
+			}
374
+
375
+			// replace the list of product objects with json
376
+			$results['products'] = $products;
377
+		}
378
+
379
+		$this->extend('updateSuggestWithResults', $results, $keywords, $filters);
380
+
381
+		return $results;
382
+	}
383 383
 }
Please login to merge, or discard this patch.
code/ShopSearchAdapter.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -1,21 +1,21 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Interface for shop search drivers.
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 09.03.2013
7
- * @package shop_search
8
- */
3
+	 * Interface for shop search drivers.
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 09.03.2013
7
+	 * @package shop_search
8
+	 */
9 9
 interface ShopSearchAdapter
10 10
 {
11
-    /**
12
-     * @param string $keywords
13
-     * @param array $filters [optional]
14
-     * @param array $facetSpec [optional]
15
-     * @param int $start [optional]
16
-     * @param int $limit [optional]
17
-     * @param string $sort [optional]
18
-     * @return ArrayData - must contain at least 'Matches' with an list of data objects that match the search
19
-     */
20
-    public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='');
11
+	/**
12
+	 * @param string $keywords
13
+	 * @param array $filters [optional]
14
+	 * @param array $facetSpec [optional]
15
+	 * @param int $start [optional]
16
+	 * @param int $limit [optional]
17
+	 * @param string $sort [optional]
18
+	 * @return ArrayData - must contain at least 'Matches' with an list of data objects that match the search
19
+	 */
20
+	public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='');
21 21
 }
Please login to merge, or discard this patch.
code/ShopSearchAjax.php 1 patch
Indentation   +25 added lines, -25 removed lines patch added patch discarded remove patch
@@ -1,30 +1,30 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Pending some changes introduced to the core shop module, this will supply
4
- * some standard, easily overridable ajax features.
5
- *
6
- * @author Mark Guinn <[email protected]>
7
- * @date 07.21.2014
8
- * @package shop_search
9
- */
3
+	 * Pending some changes introduced to the core shop module, this will supply
4
+	 * some standard, easily overridable ajax features.
5
+	 *
6
+	 * @author Mark Guinn <[email protected]>
7
+	 * @date 07.21.2014
8
+	 * @package shop_search
9
+	 */
10 10
 class ShopSearchAjax extends Extension
11 11
 {
12
-    /**
13
-     * @param SS_HTTPRequest $request
14
-     * @param SS_HTTPResponse $response
15
-     * @param ArrayData $results
16
-     * @param array $data
17
-     */
18
-    public function updateSearchResultsResponse(&$request, &$response, $results, $data)
19
-    {
20
-        if ($request->isAjax() && $this->owner->hasExtension('AjaxControllerExtension')) {
21
-            if (!$response) {
22
-                $response = $this->owner->getAjaxResponse();
23
-            }
24
-            $response->addRenderContext('RESULTS', $results);
25
-            $response->pushRegion('SearchResults', $results);
26
-            $response->pushRegion('SearchHeader', $results);
27
-            $response->triggerEvent('searchresults');
28
-        }
29
-    }
12
+	/**
13
+	 * @param SS_HTTPRequest $request
14
+	 * @param SS_HTTPResponse $response
15
+	 * @param ArrayData $results
16
+	 * @param array $data
17
+	 */
18
+	public function updateSearchResultsResponse(&$request, &$response, $results, $data)
19
+	{
20
+		if ($request->isAjax() && $this->owner->hasExtension('AjaxControllerExtension')) {
21
+			if (!$response) {
22
+				$response = $this->owner->getAjaxResponse();
23
+			}
24
+			$response->addRenderContext('RESULTS', $results);
25
+			$response->pushRegion('SearchResults', $results);
26
+			$response->pushRegion('SearchHeader', $results);
27
+			$response->triggerEvent('searchresults');
28
+		}
29
+	}
30 30
 }
Please login to merge, or discard this patch.
code/ShopSearchControllerExtension.php 1 patch
Indentation   +76 added lines, -76 removed lines patch added patch discarded remove patch
@@ -1,92 +1,92 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Adds SearchForm and results methods to the controller.
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 08.29.2013
7
- * @package shop_search
8
- */
3
+	 * Adds SearchForm and results methods to the controller.
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 08.29.2013
7
+	 * @package shop_search
8
+	 */
9 9
 class ShopSearchControllerExtension extends Extension
10 10
 {
11
-    private static $allowed_actions = array('SearchForm', 'results', 'search_suggest');
11
+	private static $allowed_actions = array('SearchForm', 'results', 'search_suggest');
12 12
 
13
-    /**
14
-     * @return ShopSearchForm
15
-     */
16
-    public function SearchForm()
17
-    {
18
-        return ShopSearchForm::create($this->owner, 'SearchForm', $this->owner->Link() . 'search-suggest');
19
-    }
13
+	/**
14
+	 * @return ShopSearchForm
15
+	 */
16
+	public function SearchForm()
17
+	{
18
+		return ShopSearchForm::create($this->owner, 'SearchForm', $this->owner->Link() . 'search-suggest');
19
+	}
20 20
 
21 21
 
22
-    /**
23
-     * @param SS_HTTPRequest $req
24
-     * @return string
25
-     */
26
-    public function search_suggest(SS_HTTPRequest $req)
27
-    {
28
-        /** @var SS_HTTPResponse $response */
29
-        $response    = $this->owner->getResponse();
30
-        $callback    = $req->requestVar('callback');
22
+	/**
23
+	 * @param SS_HTTPRequest $req
24
+	 * @return string
25
+	 */
26
+	public function search_suggest(SS_HTTPRequest $req)
27
+	{
28
+		/** @var SS_HTTPResponse $response */
29
+		$response    = $this->owner->getResponse();
30
+		$callback    = $req->requestVar('callback');
31 31
 
32
-        // convert the search results into usable json for search-as-you-type
33
-        if (ShopSearch::config()->search_as_you_type_enabled) {
34
-            $searchVars = $req->requestVars();
35
-            $searchVars[ ShopSearch::config()->qs_query ] = $searchVars['term'];
36
-            unset($searchVars['term']);
37
-            $results = ShopSearch::inst()->suggestWithResults($searchVars);
38
-        } else {
39
-            $results = array(
40
-                'suggestions'   => ShopSearch::inst()->suggest($req->requestVar('term')),
41
-            );
42
-        }
32
+		// convert the search results into usable json for search-as-you-type
33
+		if (ShopSearch::config()->search_as_you_type_enabled) {
34
+			$searchVars = $req->requestVars();
35
+			$searchVars[ ShopSearch::config()->qs_query ] = $searchVars['term'];
36
+			unset($searchVars['term']);
37
+			$results = ShopSearch::inst()->suggestWithResults($searchVars);
38
+		} else {
39
+			$results = array(
40
+				'suggestions'   => ShopSearch::inst()->suggest($req->requestVar('term')),
41
+			);
42
+		}
43 43
 
44
-        if ($callback) {
45
-            $response->addHeader('Content-type', 'application/javascript');
46
-            $response->setBody($callback . '(' . json_encode($results) . ');');
47
-        } else {
48
-            $response->addHeader('Content-type', 'application/json');
49
-            $response->setBody(json_encode($results));
50
-        }
51
-        return $response;
52
-    }
44
+		if ($callback) {
45
+			$response->addHeader('Content-type', 'application/javascript');
46
+			$response->setBody($callback . '(' . json_encode($results) . ');');
47
+		} else {
48
+			$response->addHeader('Content-type', 'application/json');
49
+			$response->setBody(json_encode($results));
50
+		}
51
+		return $response;
52
+	}
53 53
 
54 54
 
55
-    /**
56
-     * If there is a search encoded in the link, go ahead and log it.
57
-     * This happens when you click through on a search suggestion
58
-     */
59
-    public function onAfterInit()
60
-    {
61
-        $req = $this->owner->getRequest();
62
-        $src = $req->requestVar(Config::inst()->get('ShopSearch', 'qs_source'));
63
-        if ($src) {
64
-            $qs_q   = Config::inst()->get('ShopSearch', 'qs_query');
65
-            $qs_f   = Config::inst()->get('ShopSearch', 'qs_filters');
66
-            $vars   = json_decode(base64_decode($src), true);
55
+	/**
56
+	 * If there is a search encoded in the link, go ahead and log it.
57
+	 * This happens when you click through on a search suggestion
58
+	 */
59
+	public function onAfterInit()
60
+	{
61
+		$req = $this->owner->getRequest();
62
+		$src = $req->requestVar(Config::inst()->get('ShopSearch', 'qs_source'));
63
+		if ($src) {
64
+			$qs_q   = Config::inst()->get('ShopSearch', 'qs_query');
65
+			$qs_f   = Config::inst()->get('ShopSearch', 'qs_filters');
66
+			$vars   = json_decode(base64_decode($src), true);
67 67
 
68
-            // log the search
69
-            $log = SearchLog::create(array(
70
-                'Query'         => strtolower($vars[$qs_q]),
71
-                'Link'          => $req->getURL(false), // These searches will never have child searches, but this will allow us to know what they clicked
72
-                'NumResults'    => $vars['total'],
73
-                'MemberID'      => Member::currentUserID(),
74
-                'Filters'       => !empty($vars[$qs_f]) ? json_encode($vars[$qs_f]) : null,
75
-            ));
76
-            $log->write();
68
+			// log the search
69
+			$log = SearchLog::create(array(
70
+				'Query'         => strtolower($vars[$qs_q]),
71
+				'Link'          => $req->getURL(false), // These searches will never have child searches, but this will allow us to know what they clicked
72
+				'NumResults'    => $vars['total'],
73
+				'MemberID'      => Member::currentUserID(),
74
+				'Filters'       => !empty($vars[$qs_f]) ? json_encode($vars[$qs_f]) : null,
75
+			));
76
+			$log->write();
77 77
 
78
-            // redirect to the clean page
79
-            $this->owner->redirect($req->getURL(false));
80
-        }
81
-    }
78
+			// redirect to the clean page
79
+			$this->owner->redirect($req->getURL(false));
80
+		}
81
+	}
82 82
 
83 83
 
84
-    /**
85
-     * @param ArrayData $results
86
-     * @param array     $data
87
-     * @return string
88
-     */
89
-    protected function generateLongTitle(ArrayData $results, array $data)
90
-    {
91
-    }
84
+	/**
85
+	 * @param ArrayData $results
86
+	 * @param array     $data
87
+	 * @return string
88
+	 */
89
+	protected function generateLongTitle(ArrayData $results, array $data)
90
+	{
91
+	}
92 92
 }
Please login to merge, or discard this patch.
code/ShopSearchForm.php 1 patch
Indentation   +147 added lines, -147 removed lines patch added patch discarded remove patch
@@ -1,152 +1,152 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Form object for shop search.
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 09.23.2013
7
- * @package shop_search
8
- */
3
+	 * Form object for shop search.
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 09.23.2013
7
+	 * @package shop_search
8
+	 */
9 9
 class ShopSearchForm extends Form
10 10
 {
11
-    /** @var bool - setting this to true will remove the category dropdwon from the form */
12
-    private static $disable_category_dropdown = false;
13
-
14
-    /** @var string - this probably ought to be overridden with a vfi or solr facet unless you're products are only in one category */
15
-    private static $category_field = 'f[ParentID]';
16
-
17
-    /** @var string - setting to 'NONE' will mean the category dropdwon will have no empty option */
18
-    private static $category_empty_string = 'All Products';
19
-
20
-    /** @var int - how deep to list the categories for the dropdown */
21
-    private static $category_max_depth = 10;
22
-
23
-
24
-    /**
25
-     * @param Controller $controller
26
-     * @param String     $method
27
-     * @param string     $suggestURL
28
-     */
29
-    public function __construct($controller, $method, $suggestURL = '')
30
-    {
31
-        $searchField = TextField::create('q', '');
32
-        $searchField->setAttribute('placeholder', _t('ShopSearch.SEARCH', 'Search'));
33
-        if ($suggestURL) {
34
-            $searchField->setAttribute('data-suggest-url', $suggestURL);
35
-        }
36
-
37
-        $fields = FieldList::create($searchField);
38
-        if (!self::config()->disable_category_dropdown) {
39
-            $cats     = ShopSearch::get_category_hierarchy(0, '', self::config()->category_max_depth);
40
-            $catField = DropdownField::create(self::get_category_field(), '', $cats, Session::get('LastSearchCatID'));
41
-
42
-            $emptyString = self::config()->category_empty_string;
43
-            if ($emptyString !== 'NONE') {
44
-                $catField->setEmptyString(_t('ShopSearch.'.$emptyString, $emptyString));
45
-            }
46
-
47
-            $fields->push($catField);
48
-        }
49
-
50
-        parent::__construct($controller, $method, $fields, FieldList::create(array(FormAction::create('results', _t('ShopSearch.GO', 'Go')))));
51
-
52
-        $this->setFormMethod('GET');
53
-        $this->disableSecurityToken();
54
-        if ($c = self::config()->css_classes) {
55
-            $this->addExtraClass($c);
56
-        }
57
-
58
-        Requirements::css(SHOP_SEARCH_FOLDER . '/css/ShopSearch.css');
59
-
60
-        if (Config::inst()->get('ShopSearch', 'suggest_enabled')) {
61
-            Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
62
-            Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
63
-            Requirements::javascript(SHOP_SEARCH_FOLDER . '/javascript/search.suggest.js');
64
-            Requirements::javascript(SHOP_SEARCH_FOLDER . '/javascript/search.js');
65
-        }
66
-    }
67
-
68
-
69
-    /**
70
-     * @return string
71
-     */
72
-    public static function get_category_field()
73
-    {
74
-        return Config::inst()->get('ShopSearchForm', 'category_field');
75
-    }
76
-
77
-
78
-    /**
79
-     * @param array $data
80
-     * @return mixed
81
-     */
82
-    public function results(array $data)
83
-    {
84
-        // do the search
85
-        $results  = ShopSearch::inst()->search($data);
86
-        $request  = $this->controller->getRequest();
87
-        $baseLink = $request->getURL(false);
88
-
89
-        // if there was only one category filter, remember it for the category dropdown to retain it's value
90
-        if (!ShopSearchForm::config()->disable_category_dropdown) {
91
-            $qs_filters  = (string)Config::inst()->get('ShopSearch', 'qs_filters');
92
-            $categoryKey = (string)ShopSearchForm::config()->category_field;
93
-
94
-            if (preg_match('/\[(.+)\]/', $categoryKey, $matches)) {
95
-                // get right of the f[] around the actual key if present
96
-                $categoryKey = $matches[1];
97
-            }
98
-
99
-            if (!empty($data[$qs_filters][$categoryKey])) {
100
-                $categoryID = $data[$qs_filters][$categoryKey];
101
-                if (is_numeric($categoryID)) {
102
-                    // If it's set in the dropdown it will just be a number
103
-                    // If it's set from the checkboxes it will be something like LIST~1,2,3,4
104
-                    // We only want to remember the value in the former case
105
-                    Session::set('LastSearchCatID', $categoryID);
106
-                }
107
-            } else {
108
-                // If they unchecked every value, then clear the dropdown as well
109
-                Session::clear('LastSearchCatID');
110
-            }
111
-        }
112
-
113
-        // add links for any facets
114
-        if ($results->Facets && $results->Facets->count()) {
115
-            $qs_ps      = (string)Config::inst()->get('ShopSearch', 'qs_parent_search');
116
-            $baseParams = array_merge($data, array($qs_ps => $results->SearchLogID));
117
-            unset($baseParams['url']);
118
-            $results->Facets = FacetHelper::inst()->insertFacetLinks($results->Facets, $baseParams, $baseLink);
119
-        }
120
-
121
-        // add a dropdown for sorting
122
-        $qs_sort    = (string)Config::inst()->get('ShopSearch', 'qs_sort');
123
-        $options    = Config::inst()->get('ShopSearch', 'sort_options');
124
-        $sortParams = array_merge($data, array($qs_sort => 'NEWSORTVALUE'));
125
-        unset($sortParams['url']);
126
-        $results->SortControl = DropdownField::create($qs_sort, ShopSearch::config()->sort_label, $options, $results->Sort)
127
-            ->setAttribute('data-url', $baseLink . '?' . http_build_query($sortParams));
128
-
129
-        // a little more output management
130
-        $results->Title = "Search Results";
131
-        $results->Results = $results->Matches; // this makes us compatible with the default search template
132
-
133
-        // Give a hook for the parent controller to format the results, for example,
134
-        // interpreting filters in a specific way to affect the title or content
135
-        // when no results are returned. Since this is domain-specific we just leave
136
-        // it up to the host app.
137
-        if ($this->controller->hasMethod('onBeforeSearchDisplay')) {
138
-            $this->controller->onBeforeSearchDisplay($results);
139
-        }
140
-
141
-        // give a hook for processing ajax requests through a different template (i.e. for returning only fragments)
142
-        $tpl = Config::inst()->get('ShopSearch', 'ajax_results_template');
143
-        if (!empty($tpl) && Director::is_ajax()) {
144
-            return $this->controller->customise($results)->renderWith($tpl);
145
-        }
146
-
147
-        // Give a hook for modifying the search responses
148
-        $this->controller->extend('updateSearchResultsResponse', $request, $response, $results, $data);
149
-
150
-        return $response ?: $this->controller->customise($results)->renderWith(array('ShopSearch_results', 'Page_results', 'Page'));
151
-    }
11
+	/** @var bool - setting this to true will remove the category dropdwon from the form */
12
+	private static $disable_category_dropdown = false;
13
+
14
+	/** @var string - this probably ought to be overridden with a vfi or solr facet unless you're products are only in one category */
15
+	private static $category_field = 'f[ParentID]';
16
+
17
+	/** @var string - setting to 'NONE' will mean the category dropdwon will have no empty option */
18
+	private static $category_empty_string = 'All Products';
19
+
20
+	/** @var int - how deep to list the categories for the dropdown */
21
+	private static $category_max_depth = 10;
22
+
23
+
24
+	/**
25
+	 * @param Controller $controller
26
+	 * @param String     $method
27
+	 * @param string     $suggestURL
28
+	 */
29
+	public function __construct($controller, $method, $suggestURL = '')
30
+	{
31
+		$searchField = TextField::create('q', '');
32
+		$searchField->setAttribute('placeholder', _t('ShopSearch.SEARCH', 'Search'));
33
+		if ($suggestURL) {
34
+			$searchField->setAttribute('data-suggest-url', $suggestURL);
35
+		}
36
+
37
+		$fields = FieldList::create($searchField);
38
+		if (!self::config()->disable_category_dropdown) {
39
+			$cats     = ShopSearch::get_category_hierarchy(0, '', self::config()->category_max_depth);
40
+			$catField = DropdownField::create(self::get_category_field(), '', $cats, Session::get('LastSearchCatID'));
41
+
42
+			$emptyString = self::config()->category_empty_string;
43
+			if ($emptyString !== 'NONE') {
44
+				$catField->setEmptyString(_t('ShopSearch.'.$emptyString, $emptyString));
45
+			}
46
+
47
+			$fields->push($catField);
48
+		}
49
+
50
+		parent::__construct($controller, $method, $fields, FieldList::create(array(FormAction::create('results', _t('ShopSearch.GO', 'Go')))));
51
+
52
+		$this->setFormMethod('GET');
53
+		$this->disableSecurityToken();
54
+		if ($c = self::config()->css_classes) {
55
+			$this->addExtraClass($c);
56
+		}
57
+
58
+		Requirements::css(SHOP_SEARCH_FOLDER . '/css/ShopSearch.css');
59
+
60
+		if (Config::inst()->get('ShopSearch', 'suggest_enabled')) {
61
+			Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
62
+			Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
63
+			Requirements::javascript(SHOP_SEARCH_FOLDER . '/javascript/search.suggest.js');
64
+			Requirements::javascript(SHOP_SEARCH_FOLDER . '/javascript/search.js');
65
+		}
66
+	}
67
+
68
+
69
+	/**
70
+	 * @return string
71
+	 */
72
+	public static function get_category_field()
73
+	{
74
+		return Config::inst()->get('ShopSearchForm', 'category_field');
75
+	}
76
+
77
+
78
+	/**
79
+	 * @param array $data
80
+	 * @return mixed
81
+	 */
82
+	public function results(array $data)
83
+	{
84
+		// do the search
85
+		$results  = ShopSearch::inst()->search($data);
86
+		$request  = $this->controller->getRequest();
87
+		$baseLink = $request->getURL(false);
88
+
89
+		// if there was only one category filter, remember it for the category dropdown to retain it's value
90
+		if (!ShopSearchForm::config()->disable_category_dropdown) {
91
+			$qs_filters  = (string)Config::inst()->get('ShopSearch', 'qs_filters');
92
+			$categoryKey = (string)ShopSearchForm::config()->category_field;
93
+
94
+			if (preg_match('/\[(.+)\]/', $categoryKey, $matches)) {
95
+				// get right of the f[] around the actual key if present
96
+				$categoryKey = $matches[1];
97
+			}
98
+
99
+			if (!empty($data[$qs_filters][$categoryKey])) {
100
+				$categoryID = $data[$qs_filters][$categoryKey];
101
+				if (is_numeric($categoryID)) {
102
+					// If it's set in the dropdown it will just be a number
103
+					// If it's set from the checkboxes it will be something like LIST~1,2,3,4
104
+					// We only want to remember the value in the former case
105
+					Session::set('LastSearchCatID', $categoryID);
106
+				}
107
+			} else {
108
+				// If they unchecked every value, then clear the dropdown as well
109
+				Session::clear('LastSearchCatID');
110
+			}
111
+		}
112
+
113
+		// add links for any facets
114
+		if ($results->Facets && $results->Facets->count()) {
115
+			$qs_ps      = (string)Config::inst()->get('ShopSearch', 'qs_parent_search');
116
+			$baseParams = array_merge($data, array($qs_ps => $results->SearchLogID));
117
+			unset($baseParams['url']);
118
+			$results->Facets = FacetHelper::inst()->insertFacetLinks($results->Facets, $baseParams, $baseLink);
119
+		}
120
+
121
+		// add a dropdown for sorting
122
+		$qs_sort    = (string)Config::inst()->get('ShopSearch', 'qs_sort');
123
+		$options    = Config::inst()->get('ShopSearch', 'sort_options');
124
+		$sortParams = array_merge($data, array($qs_sort => 'NEWSORTVALUE'));
125
+		unset($sortParams['url']);
126
+		$results->SortControl = DropdownField::create($qs_sort, ShopSearch::config()->sort_label, $options, $results->Sort)
127
+			->setAttribute('data-url', $baseLink . '?' . http_build_query($sortParams));
128
+
129
+		// a little more output management
130
+		$results->Title = "Search Results";
131
+		$results->Results = $results->Matches; // this makes us compatible with the default search template
132
+
133
+		// Give a hook for the parent controller to format the results, for example,
134
+		// interpreting filters in a specific way to affect the title or content
135
+		// when no results are returned. Since this is domain-specific we just leave
136
+		// it up to the host app.
137
+		if ($this->controller->hasMethod('onBeforeSearchDisplay')) {
138
+			$this->controller->onBeforeSearchDisplay($results);
139
+		}
140
+
141
+		// give a hook for processing ajax requests through a different template (i.e. for returning only fragments)
142
+		$tpl = Config::inst()->get('ShopSearch', 'ajax_results_template');
143
+		if (!empty($tpl) && Director::is_ajax()) {
144
+			return $this->controller->customise($results)->renderWith($tpl);
145
+		}
146
+
147
+		// Give a hook for modifying the search responses
148
+		$this->controller->extend('updateSearchResultsResponse', $request, $response, $results, $data);
149
+
150
+		return $response ?: $this->controller->customise($results)->renderWith(array('ShopSearch_results', 'Page_results', 'Page'));
151
+	}
152 152
 }
Please login to merge, or discard this patch.
code/adapters/ShopSearchMysql.php 1 patch
Indentation   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -1,89 +1,89 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Adapter that will use MySQL's full text search features.
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 11.13.2013
7
- * @package shop_search
8
- * @subpackage adapters
9
- */
3
+	 * Adapter that will use MySQL's full text search features.
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 11.13.2013
7
+	 * @package shop_search
8
+	 * @subpackage adapters
9
+	 */
10 10
 class ShopSearchMysql extends Object implements ShopSearchAdapter
11 11
 {
12
-    /**
13
-     * @param string $keywords
14
-     * @param array $filters [optional]
15
-     * @param array $facetSpec [optional]
16
-     * @param int $start [optional]
17
-     * @param int $limit [optional]
18
-     * @param string $sort [optional]
19
-     * @return ArrayData
20
-     */
21
-    public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='')
22
-    {
23
-        $searchable = ShopSearch::get_searchable_classes();
24
-        $matches = new ArrayList;
12
+	/**
13
+	 * @param string $keywords
14
+	 * @param array $filters [optional]
15
+	 * @param array $facetSpec [optional]
16
+	 * @param int $start [optional]
17
+	 * @param int $limit [optional]
18
+	 * @param string $sort [optional]
19
+	 * @return ArrayData
20
+	 */
21
+	public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='')
22
+	{
23
+		$searchable = ShopSearch::get_searchable_classes();
24
+		$matches = new ArrayList;
25 25
 
26
-        foreach ($searchable as $className) {
27
-            $list = DataObject::get($className);
26
+		foreach ($searchable as $className) {
27
+			$list = DataObject::get($className);
28 28
 
29
-            // get searchable fields
30
-            $keywordFields = $this->getSearchFields($className);
29
+			// get searchable fields
30
+			$keywordFields = $this->getSearchFields($className);
31 31
 
32
-            // build the filter
33
-            $filter = array();
32
+			// build the filter
33
+			$filter = array();
34 34
 
35
-            // Use parametrized query if SilverStripe >= 3.2
36
-            if (SHOP_SEARCH_IS_SS32) {
37
-                foreach ($keywordFields as $indexFields) {
38
-                    $filter[] = array("MATCH ($indexFields) AGAINST (?)" => $keywords);
39
-                }
40
-                $list = $list->whereAny($filter);
41
-            } else {
42
-                foreach ($keywordFields as $indexFields) {
43
-                    $filter[] = sprintf("MATCH ($indexFields) AGAINST ('%s')", Convert::raw2sql($keywords));
44
-                }
45
-                // join all the filters with an "OR" statement
46
-                $list = $list->where(implode(' OR ', $filter));
47
-            }
35
+			// Use parametrized query if SilverStripe >= 3.2
36
+			if (SHOP_SEARCH_IS_SS32) {
37
+				foreach ($keywordFields as $indexFields) {
38
+					$filter[] = array("MATCH ($indexFields) AGAINST (?)" => $keywords);
39
+				}
40
+				$list = $list->whereAny($filter);
41
+			} else {
42
+				foreach ($keywordFields as $indexFields) {
43
+					$filter[] = sprintf("MATCH ($indexFields) AGAINST ('%s')", Convert::raw2sql($keywords));
44
+				}
45
+				// join all the filters with an "OR" statement
46
+				$list = $list->where(implode(' OR ', $filter));
47
+			}
48 48
 
49
-            // add in any other filters
50
-            $list = FacetHelper::inst()->addFiltersToDataList($list, $filters);
49
+			// add in any other filters
50
+			$list = FacetHelper::inst()->addFiltersToDataList($list, $filters);
51 51
 
52
-            // add any matches to the big list
53
-            $matches->merge($list);
54
-        }
52
+			// add any matches to the big list
53
+			$matches->merge($list);
54
+		}
55 55
 
56
-        return new ArrayData(array(
57
-            'Matches'   => $matches,
58
-            'Facets'    => FacetHelper::inst()->buildFacets($matches, $facetSpec, (bool)Config::inst()->get('ShopSearch', 'auto_facet_attributes')),
59
-        ));
60
-    }
56
+		return new ArrayData(array(
57
+			'Matches'   => $matches,
58
+			'Facets'    => FacetHelper::inst()->buildFacets($matches, $facetSpec, (bool)Config::inst()->get('ShopSearch', 'auto_facet_attributes')),
59
+		));
60
+	}
61 61
 
62 62
 
63
-    /**
64
-     * @param $className
65
-     * @return array an array containing fields per index
66
-     * @throws Exception
67
-     */
68
-    protected function getSearchFields($className)
69
-    {
70
-        $indexes = Config::inst()->get($className, 'indexes');
63
+	/**
64
+	 * @param $className
65
+	 * @return array an array containing fields per index
66
+	 * @throws Exception
67
+	 */
68
+	protected function getSearchFields($className)
69
+	{
70
+		$indexes = Config::inst()->get($className, 'indexes');
71 71
 
72
-        $indexList = array();
73
-        foreach ($indexes as $name => $index) {
74
-            if (is_array($index)) {
75
-                if (!empty($index['type']) && $index['type'] == 'fulltext' && !empty($index['value'])) {
76
-                    $indexList[] = trim($index['value']);
77
-                }
78
-            } elseif (preg_match('/fulltext\((.+)\)/', $index, $m)) {
79
-                $indexList[] = trim($m[1]);
80
-            }
81
-        }
72
+		$indexList = array();
73
+		foreach ($indexes as $name => $index) {
74
+			if (is_array($index)) {
75
+				if (!empty($index['type']) && $index['type'] == 'fulltext' && !empty($index['value'])) {
76
+					$indexList[] = trim($index['value']);
77
+				}
78
+			} elseif (preg_match('/fulltext\((.+)\)/', $index, $m)) {
79
+				$indexList[] = trim($m[1]);
80
+			}
81
+		}
82 82
 
83
-        if (count($indexList) === 0) {
84
-            throw new Exception("Class $className does not appear to have any fulltext indexes");
85
-        }
83
+		if (count($indexList) === 0) {
84
+			throw new Exception("Class $className does not appear to have any fulltext indexes");
85
+		}
86 86
 
87
-        return $indexList;
88
-    }
87
+		return $indexList;
88
+	}
89 89
 }
Please login to merge, or discard this patch.
code/adapters/ShopSearchSimple.php 1 patch
Indentation   +89 added lines, -89 removed lines patch added patch discarded remove patch
@@ -1,104 +1,104 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * VERY simple adapter to use DataList and :PartialMatch searches. Bare mininum
4
- * that will probably get terrible results but doesn't require any
5
- * additional setup.
6
- *
7
- * @author Mark Guinn <[email protected]>
8
- * @date 09.03.2013
9
- * @package shop_search
10
- */
3
+	 * VERY simple adapter to use DataList and :PartialMatch searches. Bare mininum
4
+	 * that will probably get terrible results but doesn't require any
5
+	 * additional setup.
6
+	 *
7
+	 * @author Mark Guinn <[email protected]>
8
+	 * @date 09.03.2013
9
+	 * @package shop_search
10
+	 */
11 11
 class ShopSearchSimple extends Object implements ShopSearchAdapter
12 12
 {
13
-    /**
14
-     * @param string $keywords
15
-     * @param array $filters [optional]
16
-     * @param array $facetSpec [optional]
17
-     * @param int $start [optional]
18
-     * @param int $limit [optional]
19
-     * @param string $sort [optional]
20
-     * @return ArrayData
21
-     */
22
-    public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='')
23
-    {
24
-        $searchable = ShopSearch::get_searchable_classes();
25
-        $matches = new ArrayList;
13
+	/**
14
+	 * @param string $keywords
15
+	 * @param array $filters [optional]
16
+	 * @param array $facetSpec [optional]
17
+	 * @param int $start [optional]
18
+	 * @param int $limit [optional]
19
+	 * @param string $sort [optional]
20
+	 * @return ArrayData
21
+	 */
22
+	public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='')
23
+	{
24
+		$searchable = ShopSearch::get_searchable_classes();
25
+		$matches = new ArrayList;
26 26
 
27
-        foreach ($searchable as $className) {
28
-            $list = DataObject::get($className);
27
+		foreach ($searchable as $className) {
28
+			$list = DataObject::get($className);
29 29
 
30
-            // get searchable fields
31
-            $keywordFields = $this->scaffoldSearchFields($className);
30
+			// get searchable fields
31
+			$keywordFields = $this->scaffoldSearchFields($className);
32 32
 
33
-            // convert that list into something we can pass to Datalist::filter
34
-            $keywordFilter = array();
35
-            if (!empty($keywords)) {
36
-                foreach ($keywordFields as $searchField) {
37
-                    $name = (strpos($searchField, ':') !== false) ? $searchField : "$searchField:PartialMatch";
38
-                    $keywordFilter[$name] = $keywords;
39
-                }
40
-            }
41
-            if (count($keywordFilter) > 0) {
42
-                $list = $list->filterAny($keywordFilter);
43
-            }
33
+			// convert that list into something we can pass to Datalist::filter
34
+			$keywordFilter = array();
35
+			if (!empty($keywords)) {
36
+				foreach ($keywordFields as $searchField) {
37
+					$name = (strpos($searchField, ':') !== false) ? $searchField : "$searchField:PartialMatch";
38
+					$keywordFilter[$name] = $keywords;
39
+				}
40
+			}
41
+			if (count($keywordFilter) > 0) {
42
+				$list = $list->filterAny($keywordFilter);
43
+			}
44 44
 
45
-            // add in any other filters
46
-            $list = FacetHelper::inst()->addFiltersToDataList($list, $filters);
45
+			// add in any other filters
46
+			$list = FacetHelper::inst()->addFiltersToDataList($list, $filters);
47 47
 
48
-            // add any matches to the big list
49
-            $matches->merge($list);
50
-        }
48
+			// add any matches to the big list
49
+			$matches->merge($list);
50
+		}
51 51
 
52
-        return new ArrayData(array(
53
-            'Matches'   => $matches,
54
-            'Facets'    => FacetHelper::inst()->buildFacets($matches, $facetSpec, (bool)Config::inst()->get('ShopSearch', 'auto_facet_attributes')),
55
-        ));
56
-    }
52
+		return new ArrayData(array(
53
+			'Matches'   => $matches,
54
+			'Facets'    => FacetHelper::inst()->buildFacets($matches, $facetSpec, (bool)Config::inst()->get('ShopSearch', 'auto_facet_attributes')),
55
+		));
56
+	}
57 57
 
58 58
 
59
-    /**
60
-     * This is verbatim copied from GridFieldAddExistingAutocompleter, with the exception
61
-     * that the default is 'PartialMatch' instead of 'StartsWith'
62
-     *
63
-     * @param String $dataClass - the class name
64
-     * @return Array|null - names of the searchable fields, with filters if appropriate
65
-     */
66
-    protected function scaffoldSearchFields($dataClass)
67
-    {
68
-        $obj = singleton($dataClass);
69
-        $fields = null;
70
-        if ($fieldSpecs = $obj->searchableFields()) {
71
-            $customSearchableFields = $obj->stat('searchable_fields');
72
-            foreach ($fieldSpecs as $name => $spec) {
73
-                if (is_array($spec) && array_key_exists('filter', $spec)) {
74
-                    // The searchableFields() spec defaults to PartialMatch,
75
-                    // so we need to check the original setting.
76
-                    // If the field is defined $searchable_fields = array('MyField'),
77
-                    // then default to StartsWith filter, which makes more sense in this context.
78
-                    if (!$customSearchableFields || array_search($name, $customSearchableFields)) {
79
-                        $filter = 'PartialMatch';
80
-                    } else {
81
-                        $filter = preg_replace('/Filter$/', '', $spec['filter']);
82
-                    }
59
+	/**
60
+	 * This is verbatim copied from GridFieldAddExistingAutocompleter, with the exception
61
+	 * that the default is 'PartialMatch' instead of 'StartsWith'
62
+	 *
63
+	 * @param String $dataClass - the class name
64
+	 * @return Array|null - names of the searchable fields, with filters if appropriate
65
+	 */
66
+	protected function scaffoldSearchFields($dataClass)
67
+	{
68
+		$obj = singleton($dataClass);
69
+		$fields = null;
70
+		if ($fieldSpecs = $obj->searchableFields()) {
71
+			$customSearchableFields = $obj->stat('searchable_fields');
72
+			foreach ($fieldSpecs as $name => $spec) {
73
+				if (is_array($spec) && array_key_exists('filter', $spec)) {
74
+					// The searchableFields() spec defaults to PartialMatch,
75
+					// so we need to check the original setting.
76
+					// If the field is defined $searchable_fields = array('MyField'),
77
+					// then default to StartsWith filter, which makes more sense in this context.
78
+					if (!$customSearchableFields || array_search($name, $customSearchableFields)) {
79
+						$filter = 'PartialMatch';
80
+					} else {
81
+						$filter = preg_replace('/Filter$/', '', $spec['filter']);
82
+					}
83 83
 
84
-                    if (class_exists($filter . 'Filter')) {
85
-                        $fields[] = "{$name}:{$filter}";
86
-                    } else {
87
-                        $fields[] = $name;
88
-                    }
89
-                } else {
90
-                    $fields[] = $name;
91
-                }
92
-            }
93
-        }
94
-        if (is_null($fields)) {
95
-            if ($obj->hasDatabaseField('Title')) {
96
-                $fields = array('Title');
97
-            } elseif ($obj->hasDatabaseField('Name')) {
98
-                $fields = array('Name');
99
-            }
100
-        }
84
+					if (class_exists($filter . 'Filter')) {
85
+						$fields[] = "{$name}:{$filter}";
86
+					} else {
87
+						$fields[] = $name;
88
+					}
89
+				} else {
90
+					$fields[] = $name;
91
+				}
92
+			}
93
+		}
94
+		if (is_null($fields)) {
95
+			if ($obj->hasDatabaseField('Title')) {
96
+				$fields = array('Title');
97
+			} elseif ($obj->hasDatabaseField('Name')) {
98
+				$fields = array('Name');
99
+			}
100
+		}
101 101
 
102
-        return $fields;
103
-    }
102
+		return $fields;
103
+	}
104 104
 }
Please login to merge, or discard this patch.
code/adapters/ShopSearchSolr.php 1 patch
Indentation   +648 added lines, -648 removed lines patch added patch discarded remove patch
@@ -1,53 +1,53 @@  discard block
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * Search driver for the fulltext module with solr backend.
4
- *
5
- * @author Mark Guinn <[email protected]>
6
- * @date 08.29.2013
7
- * @package shop_search
8
- */
3
+	 * Search driver for the fulltext module with solr backend.
4
+	 *
5
+	 * @author Mark Guinn <[email protected]>
6
+	 * @date 08.29.2013
7
+	 * @package shop_search
8
+	 */
9 9
 class ShopSearchSolr extends SolrIndex implements ShopSearchAdapter
10 10
 {
11
-    /** @var array - maps our names for fields to Solr's names (i.e. Title => SiteTree_Title) */
12
-    protected $fieldMap = array();
13
-
14
-    /**
15
-     * Sets up the index
16
-     */
17
-    public function init()
18
-    {
19
-        $searchables = ShopSearch::get_searchable_classes();
20
-
21
-        // Add each class to the index
22
-        foreach ($searchables as $class) {
23
-            $this->addClass($class);
24
-        }
25
-
26
-        // add the fields they've specifically asked for
27
-        $fields = $this->getFulltextSpec();
28
-        foreach ($fields as $def) {
29
-            $this->addFulltextField($def['field'], $def['type'], $def['params']);
30
-        }
31
-
32
-        // add the filters they've asked for
33
-        $filters = $this->getFilterSpec();
34
-        foreach ($filters as $filterName => $def) {
35
-            // NOTE: I'm pulling the guts out of this function so we can access Solr's full name
36
-            // for the field (SiteTree_Title for Title) and build the fieldMap in one step instead
37
-            // of two.
38
-            //$this->addFilterField($def['field'], $def['type'], $def['params']);
39
-            $singleFilter = $this->fieldData($def['field'], $def['type'], $def['params']);
40
-            $this->filterFields = array_merge($this->filterFields, $singleFilter);
41
-            foreach ($singleFilter as $solrName => $solrDef) {
42
-                if ($def['field'] == $solrDef['field']) {
43
-                    $this->fieldMap[$filterName] = $solrName;
44
-                }
45
-            }
46
-        }
11
+	/** @var array - maps our names for fields to Solr's names (i.e. Title => SiteTree_Title) */
12
+	protected $fieldMap = array();
13
+
14
+	/**
15
+	 * Sets up the index
16
+	 */
17
+	public function init()
18
+	{
19
+		$searchables = ShopSearch::get_searchable_classes();
20
+
21
+		// Add each class to the index
22
+		foreach ($searchables as $class) {
23
+			$this->addClass($class);
24
+		}
25
+
26
+		// add the fields they've specifically asked for
27
+		$fields = $this->getFulltextSpec();
28
+		foreach ($fields as $def) {
29
+			$this->addFulltextField($def['field'], $def['type'], $def['params']);
30
+		}
31
+
32
+		// add the filters they've asked for
33
+		$filters = $this->getFilterSpec();
34
+		foreach ($filters as $filterName => $def) {
35
+			// NOTE: I'm pulling the guts out of this function so we can access Solr's full name
36
+			// for the field (SiteTree_Title for Title) and build the fieldMap in one step instead
37
+			// of two.
38
+			//$this->addFilterField($def['field'], $def['type'], $def['params']);
39
+			$singleFilter = $this->fieldData($def['field'], $def['type'], $def['params']);
40
+			$this->filterFields = array_merge($this->filterFields, $singleFilter);
41
+			foreach ($singleFilter as $solrName => $solrDef) {
42
+				if ($def['field'] == $solrDef['field']) {
43
+					$this->fieldMap[$filterName] = $solrName;
44
+				}
45
+			}
46
+		}
47 47
 
48 48
 //		Debug::dump($this->filterFields);
49 49
 
50
-        // Add spellcheck fields
50
+		// Add spellcheck fields
51 51
 //		$spellFields = $cfg->get('ShopSearch', 'spellcheck_dictionary_source');
52 52
 //		if (empty($spellFields) || !is_array($spellFields)) {
53 53
 //			$spellFields = array();
@@ -61,11 +61,11 @@  discard block
 block discarded – undo
61 61
 //			$this->addCopyField($f, '_spellcheckContent');
62 62
 //		}
63 63
 
64
-        // Technically, filter and sort fields are the same in Solr/Lucene
64
+		// Technically, filter and sort fields are the same in Solr/Lucene
65 65
 //		$this->addSortField('ViewCount');
66 66
 //		$this->addSortField('LastEdited', 'SSDatetime');
67 67
 
68
-        // Aggregate fields for spelling checks
68
+		// Aggregate fields for spelling checks
69 69
 //		$this->addCopyField('Title', 'spellcheckData');
70 70
 //		$this->addCopyField('Content', 'spellcheckData');
71 71
 
@@ -78,144 +78,144 @@  discard block
 block discarded – undo
78 78
 //			)
79 79
 //		));
80 80
 
81
-        // I can't get this to work. Need a way to create the Category field that get used
81
+		// I can't get this to work. Need a way to create the Category field that get used
82 82
 //		$this->addFilterField('Category', 'Int');
83 83
 //		$this->addFilterField('Parent.ID');
84 84
 //		$this->addFilterField('ProductCategories.ID');
85 85
 //		$this->addCopyField('SiteTree_Parent_ID', 'Category');
86 86
 //		$this->addCopyField('Product_ProductCategories_ID', 'Category');
87 87
 
88
-        // These will be added in a pull request to shop module. If they're not present they'll be ignored
88
+		// These will be added in a pull request to shop module. If they're not present they'll be ignored
89 89
 //		$this->addFilterField('AllCategoryIDs', 'Int', array('multiValued' => 'true'));
90 90
 //		$this->addFilterField('AllRecursiveCategoryIDs', 'Int', array('multiValued' => 'true'));
91 91
 
92
-        // This will cause only live pages to be indexed. There are two ways to do
93
-        // this. See fulltextsearch/docs/en/index.md for more information.
94
-        // Not sure if this is really the way to go or not, but for now this is it.
95
-        $this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
96
-    }
97
-
98
-
99
-    /**
100
-     * Transforms different formats of field list into something we can pass to solr
101
-     * @param array $in
102
-     * @return array
103
-     */
104
-    protected function scrubFieldList($in)
105
-    {
106
-        $out = array();
107
-        if (empty($in) || !is_array($in)) {
108
-            return $out;
109
-        }
110
-
111
-        foreach ($in as $name => $val) {
112
-            // supports an indexed array format of simple field names
113
-            if (is_numeric($name)) {
114
-                $name = $val;
115
-                $val = true;
116
-            }
117
-
118
-            // supports a boolean value meaning "use the default setup"
119
-            $params = !is_array($val) ? array() : array_slice($val, 0);
120
-
121
-            // build a normalized structur
122
-            $def = array(
123
-                'field'     => isset($params['field']) ? $params['field'] : $name,
124
-                'type'      => isset($params['type']) ? $params['type'] : null,
125
-                'params'    => $params,
126
-            );
127
-
128
-            if (isset($def['params']['field'])) {
129
-                unset($def['params']['field']);
130
-            }
131
-            if (isset($def['params']['type'])) {
132
-                unset($def['params']['type']);
133
-            }
134
-
135
-            $out[$name] = $def;
136
-        }
137
-
138
-        return $out;
139
-    }
140
-
141
-
142
-    /**
143
-     * @return array
144
-     */
145
-    protected function getFulltextSpec()
146
-    {
147
-        $fields = Config::inst()->get('ShopSearch', 'solr_fulltext_fields');
148
-        if (empty($fields)) {
149
-            $fields = array('Title', 'Content');
150
-        }
151
-        return $this->scrubFieldList($fields);
152
-    }
153
-
154
-
155
-    /**
156
-     *
157
-     */
158
-    protected function getFilterSpec()
159
-    {
160
-        $fields = Config::inst()->get('ShopSearch', 'solr_filter_fields');
161
-        return $this->scrubFieldList($fields);
162
-    }
163
-
164
-
165
-    /**
166
-     * @return string
167
-     */
168
-    public function getFieldDefinitions()
169
-    {
170
-        $xml = parent::getFieldDefinitions();
92
+		// This will cause only live pages to be indexed. There are two ways to do
93
+		// this. See fulltextsearch/docs/en/index.md for more information.
94
+		// Not sure if this is really the way to go or not, but for now this is it.
95
+		$this->excludeVariantState(array('SearchVariantVersioned' => 'Stage'));
96
+	}
97
+
98
+
99
+	/**
100
+	 * Transforms different formats of field list into something we can pass to solr
101
+	 * @param array $in
102
+	 * @return array
103
+	 */
104
+	protected function scrubFieldList($in)
105
+	{
106
+		$out = array();
107
+		if (empty($in) || !is_array($in)) {
108
+			return $out;
109
+		}
110
+
111
+		foreach ($in as $name => $val) {
112
+			// supports an indexed array format of simple field names
113
+			if (is_numeric($name)) {
114
+				$name = $val;
115
+				$val = true;
116
+			}
117
+
118
+			// supports a boolean value meaning "use the default setup"
119
+			$params = !is_array($val) ? array() : array_slice($val, 0);
120
+
121
+			// build a normalized structur
122
+			$def = array(
123
+				'field'     => isset($params['field']) ? $params['field'] : $name,
124
+				'type'      => isset($params['type']) ? $params['type'] : null,
125
+				'params'    => $params,
126
+			);
127
+
128
+			if (isset($def['params']['field'])) {
129
+				unset($def['params']['field']);
130
+			}
131
+			if (isset($def['params']['type'])) {
132
+				unset($def['params']['type']);
133
+			}
134
+
135
+			$out[$name] = $def;
136
+		}
137
+
138
+		return $out;
139
+	}
140
+
141
+
142
+	/**
143
+	 * @return array
144
+	 */
145
+	protected function getFulltextSpec()
146
+	{
147
+		$fields = Config::inst()->get('ShopSearch', 'solr_fulltext_fields');
148
+		if (empty($fields)) {
149
+			$fields = array('Title', 'Content');
150
+		}
151
+		return $this->scrubFieldList($fields);
152
+	}
153
+
154
+
155
+	/**
156
+	 *
157
+	 */
158
+	protected function getFilterSpec()
159
+	{
160
+		$fields = Config::inst()->get('ShopSearch', 'solr_filter_fields');
161
+		return $this->scrubFieldList($fields);
162
+	}
163
+
164
+
165
+	/**
166
+	 * @return string
167
+	 */
168
+	public function getFieldDefinitions()
169
+	{
170
+		$xml = parent::getFieldDefinitions();
171 171
 //		$xml .= "\n\t\t<field name='_spellcheckContent' type='htmltext' indexed='true' stored='false' multiValued='true' />";
172 172
 
173
-        // create a sorting column
174
-        if (isset($this->fieldMap['Title']) || ShopSearch::config()->solr_title_sort_field) {
175
-            $f = empty(ShopSearch::config()->title_sort_field) ? '_titleSort' : ShopSearch::config()->solr_title_sort_field;
176
-            $xml .= "\n\t\t" . '<field name="' . $f . '" type="alphaOnlySort" indexed="true" stored="false" required="false" multiValued="false" />';
177
-            $xml .= "\n\t\t" . '<copyField source="SiteTree_Title" dest="' . $f . '"/>';
178
-        }
179
-
180
-        // create an autocomplete column
181
-        if (ShopSearch::config()->suggest_enabled) {
182
-            $xml .= "\n\t\t<field name='_autocomplete' type='autosuggest_text' indexed='true' stored='false' multiValued='true'/>";
183
-        }
184
-
185
-        return $xml;
186
-    }
187
-
188
-
189
-    /**
190
-     * @return string
191
-     */
192
-    public function getCopyFieldDefinitions()
193
-    {
194
-        $xml = parent::getCopyFieldDefinitions();
195
-
196
-        if (ShopSearch::config()->suggest_enabled) {
197
-            foreach ($this->fulltextFields as $name => $field) {
198
-                $xml .= "\n\t<copyField source='{$name}' dest='_autocomplete' />";
199
-                //$xml .= "\n\t<copyField source='{$name}' dest='_spellcheckContent' />";
200
-            }
201
-        }
202
-
203
-        return $xml;
204
-    }
205
-
206
-
207
-        /**
208
-     * Overrides the parent to add a field for autocomplete
209
-     * @return HTMLText
210
-     */
211
-    public function getTypes()
212
-    {
213
-        $val = parent::getTypes();
214
-        if (!$val || !is_object($val)) {
215
-            return $val;
216
-        }
217
-        $xml = $val->getValue();
218
-        $xml .= <<<XML
173
+		// create a sorting column
174
+		if (isset($this->fieldMap['Title']) || ShopSearch::config()->solr_title_sort_field) {
175
+			$f = empty(ShopSearch::config()->title_sort_field) ? '_titleSort' : ShopSearch::config()->solr_title_sort_field;
176
+			$xml .= "\n\t\t" . '<field name="' . $f . '" type="alphaOnlySort" indexed="true" stored="false" required="false" multiValued="false" />';
177
+			$xml .= "\n\t\t" . '<copyField source="SiteTree_Title" dest="' . $f . '"/>';
178
+		}
179
+
180
+		// create an autocomplete column
181
+		if (ShopSearch::config()->suggest_enabled) {
182
+			$xml .= "\n\t\t<field name='_autocomplete' type='autosuggest_text' indexed='true' stored='false' multiValued='true'/>";
183
+		}
184
+
185
+		return $xml;
186
+	}
187
+
188
+
189
+	/**
190
+	 * @return string
191
+	 */
192
+	public function getCopyFieldDefinitions()
193
+	{
194
+		$xml = parent::getCopyFieldDefinitions();
195
+
196
+		if (ShopSearch::config()->suggest_enabled) {
197
+			foreach ($this->fulltextFields as $name => $field) {
198
+				$xml .= "\n\t<copyField source='{$name}' dest='_autocomplete' />";
199
+				//$xml .= "\n\t<copyField source='{$name}' dest='_spellcheckContent' />";
200
+			}
201
+		}
202
+
203
+		return $xml;
204
+	}
205
+
206
+
207
+		/**
208
+		 * Overrides the parent to add a field for autocomplete
209
+		 * @return HTMLText
210
+		 */
211
+	public function getTypes()
212
+	{
213
+		$val = parent::getTypes();
214
+		if (!$val || !is_object($val)) {
215
+			return $val;
216
+		}
217
+		$xml = $val->getValue();
218
+		$xml .= <<<XML
219 219
 
220 220
 	        <fieldType name="autosuggest_text" class="solr.TextField"
221 221
 	                   positionIncrementGap="100">
@@ -234,89 +234,89 @@  discard block
 block discarded – undo
234 234
 	        </fieldType>
235 235
 
236 236
 XML;
237
-        $val->setValue($xml);
238
-        return $val;
239
-    }
240
-
241
-    /**
242
-     * This is an intermediary to bridge the search form input
243
-     * and the SearchQuery class. It allows us to have other
244
-     * drivers that may not use the FullTextSearch module.
245
-     *
246
-     * @param string $keywords
247
-     * @param array $filters [optional]
248
-     * @param array $facetSpec [optional]
249
-     * @param int $start [optional]
250
-     * @param int $limit [optional]
251
-     * @param string $sort [optional]
252
-     * @return ArrayData
253
-     */
254
-    public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='score desc')
255
-    {
256
-        $query = new SearchQuery();
257
-        $params = array(
258
-            'sort'  => $sort,
259
-        );
260
-
261
-        // swap out title search
262
-        if ($params['sort'] == 'SiteTree_Title') {
263
-            $params['sort'] = '_titleSort';
264
-        }
265
-
266
-        // search by keywords
267
-        $query->search(empty($keywords) ? '*:*' : $keywords);
268
-
269
-        // search by filter
270
-        foreach ($filters as $k => $v) {
271
-            if (isset($this->fieldMap[$k])) {
272
-                if (is_string($v) && preg_match('/^RANGE\~(.+)\~(.+)$/', $v, $m)) {
273
-                    // Is it a range value?
274
-                    $range = new SearchQuery_Range($m[1], $m[2]);
275
-                    $query->filter($this->fieldMap[$k], $range);
276
-                } else {
277
-                    // Or a normal scalar value
278
-                    $query->filter($this->fieldMap[$k], $v);
279
-                }
280
-            }
281
-        }
282
-
283
-        // add facets
284
-        $facetSpec = FacetHelper::inst()->expandFacetSpec($facetSpec);
285
-        $params += $this->buildFacetParams($facetSpec);
286
-
287
-        // TODO: add spellcheck
288
-
289
-        return $this->search($query, $start, $limit, $params, $facetSpec);
290
-    }
291
-
292
-
293
-    /**
294
-     * @param string $keywords
295
-     * @param array  $filters
296
-     * @return array
297
-     */
298
-    public function suggestWithResults($keywords, array $filters = array())
299
-    {
300
-        $limit      = (int)ShopSearch::config()->sayt_limit;
301
-
302
-        // process the keywords a bit
303
-        $terms      = preg_split('/\s+/', trim(strtolower($keywords)));
304
-        $lastTerm   = count($terms) > 0 ? array_pop($terms) : '';
305
-        $prefix     = count($terms) > 0 ? implode(' ', $terms) . ' ' : '';
306
-        //$terms[]    = $lastTerm;
307
-        $terms[]    = $lastTerm . '*'; // this allows for partial words to still match
308
-
309
-        // convert that to something solr adapater can handle
310
-        $query = new SearchQuery();
311
-        $query->search('+' . implode(' +', $terms));
312
-
313
-        $params = array(
314
-            'sort'          => 'score desc',
315
-            'facet'         => 'true',
316
-            'facet.field'   => '_autocomplete',
317
-            'facet.limit'   => ShopSearch::config()->suggest_limit,
318
-            'facet.prefix'  => $lastTerm,
319
-        );
237
+		$val->setValue($xml);
238
+		return $val;
239
+	}
240
+
241
+	/**
242
+	 * This is an intermediary to bridge the search form input
243
+	 * and the SearchQuery class. It allows us to have other
244
+	 * drivers that may not use the FullTextSearch module.
245
+	 *
246
+	 * @param string $keywords
247
+	 * @param array $filters [optional]
248
+	 * @param array $facetSpec [optional]
249
+	 * @param int $start [optional]
250
+	 * @param int $limit [optional]
251
+	 * @param string $sort [optional]
252
+	 * @return ArrayData
253
+	 */
254
+	public function searchFromVars($keywords, array $filters=array(), array $facetSpec=array(), $start=-1, $limit=-1, $sort='score desc')
255
+	{
256
+		$query = new SearchQuery();
257
+		$params = array(
258
+			'sort'  => $sort,
259
+		);
260
+
261
+		// swap out title search
262
+		if ($params['sort'] == 'SiteTree_Title') {
263
+			$params['sort'] = '_titleSort';
264
+		}
265
+
266
+		// search by keywords
267
+		$query->search(empty($keywords) ? '*:*' : $keywords);
268
+
269
+		// search by filter
270
+		foreach ($filters as $k => $v) {
271
+			if (isset($this->fieldMap[$k])) {
272
+				if (is_string($v) && preg_match('/^RANGE\~(.+)\~(.+)$/', $v, $m)) {
273
+					// Is it a range value?
274
+					$range = new SearchQuery_Range($m[1], $m[2]);
275
+					$query->filter($this->fieldMap[$k], $range);
276
+				} else {
277
+					// Or a normal scalar value
278
+					$query->filter($this->fieldMap[$k], $v);
279
+				}
280
+			}
281
+		}
282
+
283
+		// add facets
284
+		$facetSpec = FacetHelper::inst()->expandFacetSpec($facetSpec);
285
+		$params += $this->buildFacetParams($facetSpec);
286
+
287
+		// TODO: add spellcheck
288
+
289
+		return $this->search($query, $start, $limit, $params, $facetSpec);
290
+	}
291
+
292
+
293
+	/**
294
+	 * @param string $keywords
295
+	 * @param array  $filters
296
+	 * @return array
297
+	 */
298
+	public function suggestWithResults($keywords, array $filters = array())
299
+	{
300
+		$limit      = (int)ShopSearch::config()->sayt_limit;
301
+
302
+		// process the keywords a bit
303
+		$terms      = preg_split('/\s+/', trim(strtolower($keywords)));
304
+		$lastTerm   = count($terms) > 0 ? array_pop($terms) : '';
305
+		$prefix     = count($terms) > 0 ? implode(' ', $terms) . ' ' : '';
306
+		//$terms[]    = $lastTerm;
307
+		$terms[]    = $lastTerm . '*'; // this allows for partial words to still match
308
+
309
+		// convert that to something solr adapater can handle
310
+		$query = new SearchQuery();
311
+		$query->search('+' . implode(' +', $terms));
312
+
313
+		$params = array(
314
+			'sort'          => 'score desc',
315
+			'facet'         => 'true',
316
+			'facet.field'   => '_autocomplete',
317
+			'facet.limit'   => ShopSearch::config()->suggest_limit,
318
+			'facet.prefix'  => $lastTerm,
319
+		);
320 320
 
321 321
 //		$facetSpec = array(
322 322
 //			'_autocomplete' => array(
@@ -335,14 +335,14 @@  discard block
 block discarded – undo
335 335
 //		$suggestsion = array();
336 336
 ////		if ($)
337 337
 
338
-        $service = $this->getService();
338
+		$service = $this->getService();
339 339
 
340
-        SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
340
+		SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
341 341
 
342
-        $q = $terms;
343
-        $fq = array();
342
+		$q = $terms;
343
+		$fq = array();
344 344
 
345
-        // Build the search itself
345
+		// Build the search itself
346 346
 //		foreach ($query->search as $search) {
347 347
 //			$text = $search['text'];
348 348
 //			preg_match_all('/"[^"]*"|\S+/', $text, $parts);
@@ -366,408 +366,408 @@  discard block
 block discarded – undo
366 366
 //			}
367 367
 //		}
368 368
 
369
-        // Filter by class if requested
370
-        $classq = array();
371
-
372
-        foreach ($query->classes as $class) {
373
-            if (!empty($class['includeSubclasses'])) {
374
-                $classq[] = 'ClassHierarchy:'.$class['class'];
375
-            } else {
376
-                $classq[] = 'ClassName:'.$class['class'];
377
-            }
378
-        }
379
-
380
-        if ($classq) {
381
-            $fq[] = '+('.implode(' ', $classq).')';
382
-        }
383
-
384
-        // Filter by filters
385
-        foreach ($query->require as $field => $values) {
386
-            $requireq = array();
387
-
388
-            foreach ($values as $value) {
389
-                if ($value === SearchQuery::$missing) {
390
-                    $requireq[] = "(*:* -{$field}:[* TO *])";
391
-                } elseif ($value === SearchQuery::$present) {
392
-                    $requireq[] = "{$field}:[* TO *]";
393
-                } elseif ($value instanceof SearchQuery_Range) {
394
-                    $start = $value->start;
395
-                    if ($start === null) {
396
-                        $start = '*';
397
-                    }
398
-                    $end = $value->end;
399
-                    if ($end === null) {
400
-                        $end = '*';
401
-                    }
402
-                    $requireq[] = "$field:[$start TO $end]";
403
-                } else {
404
-                    $requireq[] = $field.':"'.$value.'"';
405
-                }
406
-            }
407
-
408
-            $fq[] = '+('.implode(' ', $requireq).')';
409
-        }
410
-
411
-        foreach ($query->exclude as $field => $values) {
412
-            $excludeq = array();
413
-            $missing = false;
414
-
415
-            foreach ($values as $value) {
416
-                if ($value === SearchQuery::$missing) {
417
-                    $missing = true;
418
-                } elseif ($value === SearchQuery::$present) {
419
-                    $excludeq[] = "{$field}:[* TO *]";
420
-                } elseif ($value instanceof SearchQuery_Range) {
421
-                    $start = $value->start;
422
-                    if ($start === null) {
423
-                        $start = '*';
424
-                    }
425
-                    $end = $value->end;
426
-                    if ($end === null) {
427
-                        $end = '*';
428
-                    }
429
-                    $excludeq[] = "$field:[$start TO $end]";
430
-                } else {
431
-                    $excludeq[] = $field.':"'.$value.'"';
432
-                }
433
-            }
434
-
435
-            $fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
436
-        }
369
+		// Filter by class if requested
370
+		$classq = array();
371
+
372
+		foreach ($query->classes as $class) {
373
+			if (!empty($class['includeSubclasses'])) {
374
+				$classq[] = 'ClassHierarchy:'.$class['class'];
375
+			} else {
376
+				$classq[] = 'ClassName:'.$class['class'];
377
+			}
378
+		}
379
+
380
+		if ($classq) {
381
+			$fq[] = '+('.implode(' ', $classq).')';
382
+		}
383
+
384
+		// Filter by filters
385
+		foreach ($query->require as $field => $values) {
386
+			$requireq = array();
387
+
388
+			foreach ($values as $value) {
389
+				if ($value === SearchQuery::$missing) {
390
+					$requireq[] = "(*:* -{$field}:[* TO *])";
391
+				} elseif ($value === SearchQuery::$present) {
392
+					$requireq[] = "{$field}:[* TO *]";
393
+				} elseif ($value instanceof SearchQuery_Range) {
394
+					$start = $value->start;
395
+					if ($start === null) {
396
+						$start = '*';
397
+					}
398
+					$end = $value->end;
399
+					if ($end === null) {
400
+						$end = '*';
401
+					}
402
+					$requireq[] = "$field:[$start TO $end]";
403
+				} else {
404
+					$requireq[] = $field.':"'.$value.'"';
405
+				}
406
+			}
407
+
408
+			$fq[] = '+('.implode(' ', $requireq).')';
409
+		}
410
+
411
+		foreach ($query->exclude as $field => $values) {
412
+			$excludeq = array();
413
+			$missing = false;
414
+
415
+			foreach ($values as $value) {
416
+				if ($value === SearchQuery::$missing) {
417
+					$missing = true;
418
+				} elseif ($value === SearchQuery::$present) {
419
+					$excludeq[] = "{$field}:[* TO *]";
420
+				} elseif ($value instanceof SearchQuery_Range) {
421
+					$start = $value->start;
422
+					if ($start === null) {
423
+						$start = '*';
424
+					}
425
+					$end = $value->end;
426
+					if ($end === null) {
427
+						$end = '*';
428
+					}
429
+					$excludeq[] = "$field:[$start TO $end]";
430
+				} else {
431
+					$excludeq[] = $field.':"'.$value.'"';
432
+				}
433
+			}
434
+
435
+			$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
436
+		}
437 437
 
438 438
 //		if(!headers_sent()) {
439 439
 //			if ($q) header('X-Query: '.implode(' ', $q));
440 440
 //			if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
441 441
 //		}
442 442
 
443
-        $params = array_merge($params, array('fq' => implode(' ', $fq)));
444
-
445
-        $res = $service->search(
446
-            implode(' ', $q),
447
-            0,
448
-            $limit,
449
-            $params,
450
-            Apache_Solr_Service::METHOD_POST
451
-        );
452
-
453
-        $results = new ArrayList();
454
-        if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
455
-            foreach ($res->response->docs as $doc) {
456
-                $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
457
-                if ($result) {
458
-                    $results->push($result);
459
-                }
460
-            }
461
-            $numFound = $res->response->numFound;
462
-        } else {
463
-            $numFound = 0;
464
-        }
465
-
466
-        $ret = array();
467
-        $ret['products'] = new PaginatedList($results);
468
-        $ret['products']->setLimitItems(false);
469
-        $ret['products']->setTotalItems($numFound);
470
-        $ret['products']->setPageStart(0);
471
-        $ret['products']->setPageLength($limit);
472
-
473
-        // Facets (this is how we're doing suggestions for now...
474
-        $ret['suggestions'] = array();
475
-        if (isset($res->facet_counts->facet_fields->_autocomplete)) {
476
-            foreach ($res->facet_counts->facet_fields->_autocomplete as $term => $count) {
477
-                $ret['suggestions'][] = $prefix . $term;
478
-            }
479
-        }
480
-
481
-        // Suggestions (requires custom setup, assumes spellcheck.collate=true)
443
+		$params = array_merge($params, array('fq' => implode(' ', $fq)));
444
+
445
+		$res = $service->search(
446
+			implode(' ', $q),
447
+			0,
448
+			$limit,
449
+			$params,
450
+			Apache_Solr_Service::METHOD_POST
451
+		);
452
+
453
+		$results = new ArrayList();
454
+		if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
455
+			foreach ($res->response->docs as $doc) {
456
+				$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
457
+				if ($result) {
458
+					$results->push($result);
459
+				}
460
+			}
461
+			$numFound = $res->response->numFound;
462
+		} else {
463
+			$numFound = 0;
464
+		}
465
+
466
+		$ret = array();
467
+		$ret['products'] = new PaginatedList($results);
468
+		$ret['products']->setLimitItems(false);
469
+		$ret['products']->setTotalItems($numFound);
470
+		$ret['products']->setPageStart(0);
471
+		$ret['products']->setPageLength($limit);
472
+
473
+		// Facets (this is how we're doing suggestions for now...
474
+		$ret['suggestions'] = array();
475
+		if (isset($res->facet_counts->facet_fields->_autocomplete)) {
476
+			foreach ($res->facet_counts->facet_fields->_autocomplete as $term => $count) {
477
+				$ret['suggestions'][] = $prefix . $term;
478
+			}
479
+		}
480
+
481
+		// Suggestions (requires custom setup, assumes spellcheck.collate=true)
482 482
 //		if(isset($res->spellcheck->suggestions->collation)) {
483 483
 //			$ret['Suggestion'] = $res->spellcheck->suggestions->collation;
484 484
 //		}
485 485
 
486
-        return $ret;
487
-    }
488
-
489
-    /**
490
-     * @param $facets
491
-     * @return array
492
-     */
493
-    protected function buildFacetParams(array $facets)
494
-    {
495
-        $params = array();
496
-
497
-        if (!empty($facets)) {
498
-            $params['facet'] = 'true';
499
-
500
-            foreach ($facets as $name => $spec) {
501
-                // With our current implementation, "range" facets aren't true facets in solr terms.
502
-                // They're just a type of filter which can be handled elsewhere.
503
-                // For the other types we just ignore the rest of the spec and let Solr do its thing
504
-                if ($spec['Type'] != ShopSearch::FACET_TYPE_RANGE && isset($this->fieldMap[$name])) {
505
-                    $params['facet.field'] = $this->fieldMap[$name];
506
-                }
507
-            }
508
-        }
509
-
510
-        return $params;
511
-    }
512
-
513
-
514
-    /**
515
-     * Fulltextsearch module doesn't yet support facets very well, so I've just copied this function here so
516
-     * we have access to the results. I'd prefer to modify it minimally so we can eventually get rid of it
517
-     * once they add faceting or hooks to get directly at the returned response.
518
-     *
519
-     * @param SearchQuery $query
520
-     * @param integer $offset
521
-     * @param integer $limit
522
-     * @param  Array $params Extra request parameters passed through to Solr
523
-     * @param array $facetSpec - Added for ShopSearch so we can process the facets
524
-     * @return ArrayData Map with the following keys:
525
-     *  - 'Matches': ArrayList of the matched object instances
526
-     */
527
-    public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array(), $facetSpec = array())
528
-    {
529
-        $service = $this->getService();
530
-
531
-        SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
532
-
533
-        $q = array();
534
-        $fq = array();
535
-
536
-        // Build the search itself
537
-
538
-        foreach ($query->search as $search) {
539
-            $text = $search['text'];
540
-            preg_match_all('/"[^"]*"|\S+/', $text, $parts);
541
-
542
-            $fuzzy = $search['fuzzy'] ? '~' : '';
543
-
544
-            foreach ($parts[0] as $part) {
545
-                $fields = (isset($search['fields'])) ? $search['fields'] : array();
546
-                if (isset($search['boost'])) {
547
-                    $fields = array_merge($fields, array_keys($search['boost']));
548
-                }
549
-                if ($fields) {
550
-                    $searchq = array();
551
-                    foreach ($fields as $field) {
552
-                        $boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
553
-                        $searchq[] = "{$field}:".$part.$fuzzy.$boost;
554
-                    }
555
-                    $q[] = '+('.implode(' OR ', $searchq).')';
556
-                } else {
557
-                    $q[] = '+'.$part.$fuzzy;
558
-                }
559
-            }
560
-        }
561
-
562
-        // Filter by class if requested
563
-
564
-        $classq = array();
565
-
566
-        foreach ($query->classes as $class) {
567
-            if (!empty($class['includeSubclasses'])) {
568
-                $classq[] = 'ClassHierarchy:'.$class['class'];
569
-            } else {
570
-                $classq[] = 'ClassName:'.$class['class'];
571
-            }
572
-        }
573
-
574
-        if ($classq) {
575
-            $fq[] = '+('.implode(' ', $classq).')';
576
-        }
577
-
578
-        // Filter by filters
579
-
580
-        foreach ($query->require as $field => $values) {
581
-            $requireq = array();
582
-
583
-            foreach ($values as $value) {
584
-                if ($value === SearchQuery::$missing) {
585
-                    $requireq[] = "(*:* -{$field}:[* TO *])";
586
-                } elseif ($value === SearchQuery::$present) {
587
-                    $requireq[] = "{$field}:[* TO *]";
588
-                } elseif ($value instanceof SearchQuery_Range) {
589
-                    $start = $value->start;
590
-                    if ($start === null) {
591
-                        $start = '*';
592
-                    }
593
-                    $end = $value->end;
594
-                    if ($end === null) {
595
-                        $end = '*';
596
-                    }
597
-                    $requireq[] = "$field:[$start TO $end]";
598
-                } else {
599
-                    $requireq[] = $field.':"'.$value.'"';
600
-                }
601
-            }
602
-
603
-            $fq[] = '+('.implode(' ', $requireq).')';
604
-        }
605
-
606
-        foreach ($query->exclude as $field => $values) {
607
-            $excludeq = array();
608
-            $missing = false;
609
-
610
-            foreach ($values as $value) {
611
-                if ($value === SearchQuery::$missing) {
612
-                    $missing = true;
613
-                } elseif ($value === SearchQuery::$present) {
614
-                    $excludeq[] = "{$field}:[* TO *]";
615
-                } elseif ($value instanceof SearchQuery_Range) {
616
-                    $start = $value->start;
617
-                    if ($start === null) {
618
-                        $start = '*';
619
-                    }
620
-                    $end = $value->end;
621
-                    if ($end === null) {
622
-                        $end = '*';
623
-                    }
624
-                    $excludeq[] = "$field:[$start TO $end]";
625
-                } else {
626
-                    $excludeq[] = $field.':"'.$value.'"';
627
-                }
628
-            }
629
-
630
-            $fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
631
-        }
486
+		return $ret;
487
+	}
488
+
489
+	/**
490
+	 * @param $facets
491
+	 * @return array
492
+	 */
493
+	protected function buildFacetParams(array $facets)
494
+	{
495
+		$params = array();
496
+
497
+		if (!empty($facets)) {
498
+			$params['facet'] = 'true';
499
+
500
+			foreach ($facets as $name => $spec) {
501
+				// With our current implementation, "range" facets aren't true facets in solr terms.
502
+				// They're just a type of filter which can be handled elsewhere.
503
+				// For the other types we just ignore the rest of the spec and let Solr do its thing
504
+				if ($spec['Type'] != ShopSearch::FACET_TYPE_RANGE && isset($this->fieldMap[$name])) {
505
+					$params['facet.field'] = $this->fieldMap[$name];
506
+				}
507
+			}
508
+		}
509
+
510
+		return $params;
511
+	}
512
+
513
+
514
+	/**
515
+	 * Fulltextsearch module doesn't yet support facets very well, so I've just copied this function here so
516
+	 * we have access to the results. I'd prefer to modify it minimally so we can eventually get rid of it
517
+	 * once they add faceting or hooks to get directly at the returned response.
518
+	 *
519
+	 * @param SearchQuery $query
520
+	 * @param integer $offset
521
+	 * @param integer $limit
522
+	 * @param  Array $params Extra request parameters passed through to Solr
523
+	 * @param array $facetSpec - Added for ShopSearch so we can process the facets
524
+	 * @return ArrayData Map with the following keys:
525
+	 *  - 'Matches': ArrayList of the matched object instances
526
+	 */
527
+	public function search(SearchQuery $query, $offset = -1, $limit = -1, $params = array(), $facetSpec = array())
528
+	{
529
+		$service = $this->getService();
530
+
531
+		SearchVariant::with(count($query->classes) == 1 ? $query->classes[0]['class'] : null)->call('alterQuery', $query, $this);
532
+
533
+		$q = array();
534
+		$fq = array();
535
+
536
+		// Build the search itself
537
+
538
+		foreach ($query->search as $search) {
539
+			$text = $search['text'];
540
+			preg_match_all('/"[^"]*"|\S+/', $text, $parts);
541
+
542
+			$fuzzy = $search['fuzzy'] ? '~' : '';
543
+
544
+			foreach ($parts[0] as $part) {
545
+				$fields = (isset($search['fields'])) ? $search['fields'] : array();
546
+				if (isset($search['boost'])) {
547
+					$fields = array_merge($fields, array_keys($search['boost']));
548
+				}
549
+				if ($fields) {
550
+					$searchq = array();
551
+					foreach ($fields as $field) {
552
+						$boost = (isset($search['boost'][$field])) ? '^' . $search['boost'][$field] : '';
553
+						$searchq[] = "{$field}:".$part.$fuzzy.$boost;
554
+					}
555
+					$q[] = '+('.implode(' OR ', $searchq).')';
556
+				} else {
557
+					$q[] = '+'.$part.$fuzzy;
558
+				}
559
+			}
560
+		}
561
+
562
+		// Filter by class if requested
563
+
564
+		$classq = array();
565
+
566
+		foreach ($query->classes as $class) {
567
+			if (!empty($class['includeSubclasses'])) {
568
+				$classq[] = 'ClassHierarchy:'.$class['class'];
569
+			} else {
570
+				$classq[] = 'ClassName:'.$class['class'];
571
+			}
572
+		}
573
+
574
+		if ($classq) {
575
+			$fq[] = '+('.implode(' ', $classq).')';
576
+		}
577
+
578
+		// Filter by filters
579
+
580
+		foreach ($query->require as $field => $values) {
581
+			$requireq = array();
582
+
583
+			foreach ($values as $value) {
584
+				if ($value === SearchQuery::$missing) {
585
+					$requireq[] = "(*:* -{$field}:[* TO *])";
586
+				} elseif ($value === SearchQuery::$present) {
587
+					$requireq[] = "{$field}:[* TO *]";
588
+				} elseif ($value instanceof SearchQuery_Range) {
589
+					$start = $value->start;
590
+					if ($start === null) {
591
+						$start = '*';
592
+					}
593
+					$end = $value->end;
594
+					if ($end === null) {
595
+						$end = '*';
596
+					}
597
+					$requireq[] = "$field:[$start TO $end]";
598
+				} else {
599
+					$requireq[] = $field.':"'.$value.'"';
600
+				}
601
+			}
602
+
603
+			$fq[] = '+('.implode(' ', $requireq).')';
604
+		}
605
+
606
+		foreach ($query->exclude as $field => $values) {
607
+			$excludeq = array();
608
+			$missing = false;
609
+
610
+			foreach ($values as $value) {
611
+				if ($value === SearchQuery::$missing) {
612
+					$missing = true;
613
+				} elseif ($value === SearchQuery::$present) {
614
+					$excludeq[] = "{$field}:[* TO *]";
615
+				} elseif ($value instanceof SearchQuery_Range) {
616
+					$start = $value->start;
617
+					if ($start === null) {
618
+						$start = '*';
619
+					}
620
+					$end = $value->end;
621
+					if ($end === null) {
622
+						$end = '*';
623
+					}
624
+					$excludeq[] = "$field:[$start TO $end]";
625
+				} else {
626
+					$excludeq[] = $field.':"'.$value.'"';
627
+				}
628
+			}
629
+
630
+			$fq[] = ($missing ? "+{$field}:[* TO *] " : '') . '-('.implode(' ', $excludeq).')';
631
+		}
632 632
 
633 633
 //		if(!headers_sent()) {
634 634
 //			if ($q) header('X-Query: '.implode(' ', $q));
635 635
 //			if ($fq) header('X-Filters: "'.implode('", "', $fq).'"');
636 636
 //		}
637 637
 
638
-        if ($offset == -1) {
639
-            $offset = $query->start;
640
-        }
641
-        if ($limit == -1) {
642
-            $limit = $query->limit;
643
-        }
644
-        if ($limit == -1) {
645
-            $limit = SearchQuery::$default_page_size;
646
-        }
647
-
648
-        $params = array_merge($params, array('fq' => implode(' ', $fq)));
649
-
650
-        $res = $service->search(
651
-            $q ? implode(' ', $q) : '*:*',
652
-            $offset,
653
-            $limit,
654
-            $params,
655
-            Apache_Solr_Service::METHOD_POST
656
-        );
657
-        //Debug::dump($res);
658
-
659
-        $results = new ArrayList();
660
-        if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
661
-            foreach ($res->response->docs as $doc) {
662
-                $result = DataObject::get_by_id($doc->ClassName, $doc->ID);
663
-                if ($result) {
664
-                    $results->push($result);
665
-
666
-                    // Add highlighting (optional)
667
-                    $docId = $doc->_documentid;
668
-                    if ($res->highlighting && $res->highlighting->$docId) {
669
-                        // TODO Create decorator class for search results rather than adding arbitrary object properties
670
-                        // TODO Allow specifying highlighted field, and lazy loading
671
-                        // in case the search API needs another query (similar to SphinxSearchable->buildExcerpt()).
672
-                        $combinedHighlights = array();
673
-                        foreach ($res->highlighting->$docId as $field => $highlights) {
674
-                            $combinedHighlights = array_merge($combinedHighlights, $highlights);
675
-                        }
676
-
677
-                        // Remove entity-encoded U+FFFD replacement character. It signifies non-displayable characters,
678
-                        // and shows up as an encoding error in browsers.
679
-                        $result->Excerpt = DBField::create_field(
680
-                            'HTMLText',
681
-                            str_replace(
682
-                                '&#65533;',
683
-                                '',
684
-                                implode(' ... ', $combinedHighlights)
685
-                            )
686
-                        );
687
-                    }
688
-                }
689
-            }
690
-            $numFound = $res->response->numFound;
691
-        } else {
692
-            $numFound = 0;
693
-        }
694
-
695
-        $ret = array();
696
-        $ret['Matches'] = new PaginatedList($results);
697
-        $ret['Matches']->setLimitItems(false);
698
-        // Tell PaginatedList how many results there are
699
-        $ret['Matches']->setTotalItems($numFound);
700
-        // Results for current page start at $offset
701
-        $ret['Matches']->setPageStart($offset);
702
-        // Results per page
703
-        $ret['Matches']->setPageLength($limit);
704
-
705
-        // Facets
706
-        //Debug::dump($res);
707
-        if (isset($res->facet_counts->facet_fields)) {
708
-            $ret['Facets'] = $this->buildFacetResults($res->facet_counts->facet_fields, $facetSpec);
709
-        }
710
-
711
-        // Suggestions (requires custom setup, assumes spellcheck.collate=true)
712
-        if (isset($res->spellcheck->suggestions->collation)) {
713
-            $ret['Suggestion'] = $res->spellcheck->suggestions->collation;
714
-        }
715
-
716
-        return new ArrayData($ret);
717
-    }
718
-
719
-
720
-    /**
721
-     * @param stdClass $facetFields
722
-     * @param array $facetSpec
723
-     * @return ArrayList
724
-     */
725
-    protected function buildFacetResults($facetFields, array $facetSpec)
726
-    {
727
-        $out = new ArrayList;
728
-
729
-        foreach ($facetSpec as $field => $facet) {
730
-            if ($facet['Type'] == ShopSearch::FACET_TYPE_RANGE) {
731
-                // If it's a range facet, set up the min/max
732
-                // TODO: we could probably get the real min and max with solr's range faceting if we tried
733
-                if (isset($facet['RangeMin'])) {
734
-                    $facet['MinValue'] = $facet['RangeMin'];
735
-                }
736
-                if (isset($facet['RangeMax'])) {
737
-                    $facet['MaxValue'] = $facet['RangeMax'];
738
-                }
739
-                $out->push(new ArrayData($facet));
740
-            } elseif (isset($this->fieldMap[$field])) {
741
-                // Otherwise, look through Solr's results
742
-                $mySolrName = $this->fieldMap[$field];
743
-                foreach ($facetFields as $solrName => $values) {
744
-                    if ($solrName == $mySolrName) {
745
-                        // we found a match, look through the values we were given
746
-                        foreach ($values as $val => $count) {
747
-                            if (!isset($facet['Values'][$val])) {
748
-                                // for link type facets we want to add anything
749
-                                // for checkboxes, if it's not in the provided list we leave it out
750
-                                if ($facet['Type'] != ShopSearch::FACET_TYPE_CHECKBOX && $count > 0) {
751
-                                    $facet['Values'][$val] = new ArrayData(array(
752
-                                        'Label'     => $val,
753
-                                        'Value'     => $val,
754
-                                        'Count'     => $count,
755
-                                    ));
756
-                                }
757
-                            } elseif ($facet['Values'][$val]) {
758
-                                $facet['Values'][$val]->Count = $count;
759
-                            }
760
-                        }
761
-                    }
762
-                }
763
-
764
-                // then add that to the stack
765
-                $facet['Values'] = new ArrayList($facet['Values']);
766
-                $out->push(new ArrayData($facet));
767
-            }
768
-        }
769
-
770
-        //Debug::dump($out);
771
-        return $out;
772
-    }
638
+		if ($offset == -1) {
639
+			$offset = $query->start;
640
+		}
641
+		if ($limit == -1) {
642
+			$limit = $query->limit;
643
+		}
644
+		if ($limit == -1) {
645
+			$limit = SearchQuery::$default_page_size;
646
+		}
647
+
648
+		$params = array_merge($params, array('fq' => implode(' ', $fq)));
649
+
650
+		$res = $service->search(
651
+			$q ? implode(' ', $q) : '*:*',
652
+			$offset,
653
+			$limit,
654
+			$params,
655
+			Apache_Solr_Service::METHOD_POST
656
+		);
657
+		//Debug::dump($res);
658
+
659
+		$results = new ArrayList();
660
+		if ($res->getHttpStatus() >= 200 && $res->getHttpStatus() < 300) {
661
+			foreach ($res->response->docs as $doc) {
662
+				$result = DataObject::get_by_id($doc->ClassName, $doc->ID);
663
+				if ($result) {
664
+					$results->push($result);
665
+
666
+					// Add highlighting (optional)
667
+					$docId = $doc->_documentid;
668
+					if ($res->highlighting && $res->highlighting->$docId) {
669
+						// TODO Create decorator class for search results rather than adding arbitrary object properties
670
+						// TODO Allow specifying highlighted field, and lazy loading
671
+						// in case the search API needs another query (similar to SphinxSearchable->buildExcerpt()).
672
+						$combinedHighlights = array();
673
+						foreach ($res->highlighting->$docId as $field => $highlights) {
674
+							$combinedHighlights = array_merge($combinedHighlights, $highlights);
675
+						}
676
+
677
+						// Remove entity-encoded U+FFFD replacement character. It signifies non-displayable characters,
678
+						// and shows up as an encoding error in browsers.
679
+						$result->Excerpt = DBField::create_field(
680
+							'HTMLText',
681
+							str_replace(
682
+								'&#65533;',
683
+								'',
684
+								implode(' ... ', $combinedHighlights)
685
+							)
686
+						);
687
+					}
688
+				}
689
+			}
690
+			$numFound = $res->response->numFound;
691
+		} else {
692
+			$numFound = 0;
693
+		}
694
+
695
+		$ret = array();
696
+		$ret['Matches'] = new PaginatedList($results);
697
+		$ret['Matches']->setLimitItems(false);
698
+		// Tell PaginatedList how many results there are
699
+		$ret['Matches']->setTotalItems($numFound);
700
+		// Results for current page start at $offset
701
+		$ret['Matches']->setPageStart($offset);
702
+		// Results per page
703
+		$ret['Matches']->setPageLength($limit);
704
+
705
+		// Facets
706
+		//Debug::dump($res);
707
+		if (isset($res->facet_counts->facet_fields)) {
708
+			$ret['Facets'] = $this->buildFacetResults($res->facet_counts->facet_fields, $facetSpec);
709
+		}
710
+
711
+		// Suggestions (requires custom setup, assumes spellcheck.collate=true)
712
+		if (isset($res->spellcheck->suggestions->collation)) {
713
+			$ret['Suggestion'] = $res->spellcheck->suggestions->collation;
714
+		}
715
+
716
+		return new ArrayData($ret);
717
+	}
718
+
719
+
720
+	/**
721
+	 * @param stdClass $facetFields
722
+	 * @param array $facetSpec
723
+	 * @return ArrayList
724
+	 */
725
+	protected function buildFacetResults($facetFields, array $facetSpec)
726
+	{
727
+		$out = new ArrayList;
728
+
729
+		foreach ($facetSpec as $field => $facet) {
730
+			if ($facet['Type'] == ShopSearch::FACET_TYPE_RANGE) {
731
+				// If it's a range facet, set up the min/max
732
+				// TODO: we could probably get the real min and max with solr's range faceting if we tried
733
+				if (isset($facet['RangeMin'])) {
734
+					$facet['MinValue'] = $facet['RangeMin'];
735
+				}
736
+				if (isset($facet['RangeMax'])) {
737
+					$facet['MaxValue'] = $facet['RangeMax'];
738
+				}
739
+				$out->push(new ArrayData($facet));
740
+			} elseif (isset($this->fieldMap[$field])) {
741
+				// Otherwise, look through Solr's results
742
+				$mySolrName = $this->fieldMap[$field];
743
+				foreach ($facetFields as $solrName => $values) {
744
+					if ($solrName == $mySolrName) {
745
+						// we found a match, look through the values we were given
746
+						foreach ($values as $val => $count) {
747
+							if (!isset($facet['Values'][$val])) {
748
+								// for link type facets we want to add anything
749
+								// for checkboxes, if it's not in the provided list we leave it out
750
+								if ($facet['Type'] != ShopSearch::FACET_TYPE_CHECKBOX && $count > 0) {
751
+									$facet['Values'][$val] = new ArrayData(array(
752
+										'Label'     => $val,
753
+										'Value'     => $val,
754
+										'Count'     => $count,
755
+									));
756
+								}
757
+							} elseif ($facet['Values'][$val]) {
758
+								$facet['Values'][$val]->Count = $count;
759
+							}
760
+						}
761
+					}
762
+				}
763
+
764
+				// then add that to the stack
765
+				$facet['Values'] = new ArrayList($facet['Values']);
766
+				$out->push(new ArrayData($facet));
767
+			}
768
+		}
769
+
770
+		//Debug::dump($out);
771
+		return $out;
772
+	}
773 773
 }
Please login to merge, or discard this patch.