|
1
|
|
|
<?php namespace PKleindienst\BlogSearch\Components; |
|
2
|
|
|
|
|
3
|
|
|
use Cms\Classes\ComponentBase; |
|
4
|
|
|
use Cms\Classes\Page; |
|
5
|
|
|
use Input; |
|
6
|
|
|
use RainLab\Blog\Models\Category as BlogCategory; |
|
7
|
|
|
use RainLab\Blog\Models\Post as BlogPost; |
|
8
|
|
|
use Redirect; |
|
9
|
|
|
// use System\Models\Parameters; |
|
10
|
|
|
|
|
11
|
|
|
/** |
|
12
|
|
|
* Search Result component |
|
13
|
|
|
* @see RainLab\Blog\Components\Posts |
|
14
|
|
|
* @package PKleindienst\BlogSearch\Components |
|
15
|
|
|
*/ |
|
16
|
|
|
class SearchResult extends ComponentBase |
|
17
|
|
|
{ |
|
18
|
|
|
/** |
|
19
|
|
|
* Parameter to use for the search |
|
20
|
|
|
* @var string |
|
21
|
|
|
*/ |
|
22
|
|
|
public $searchParam; |
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* The search term |
|
26
|
|
|
* @var string |
|
27
|
|
|
*/ |
|
28
|
|
|
public $searchTerm; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* A collection of posts to display |
|
32
|
|
|
* @var Collection |
|
33
|
|
|
*/ |
|
34
|
|
|
public $posts; |
|
35
|
|
|
|
|
36
|
|
|
/** |
|
37
|
|
|
* Parameter to use for the page number |
|
38
|
|
|
* @var string |
|
39
|
|
|
*/ |
|
40
|
|
|
public $pageParam; |
|
41
|
|
|
|
|
42
|
|
|
/** |
|
43
|
|
|
* Message to display when there are no messages. |
|
44
|
|
|
* @var string |
|
45
|
|
|
*/ |
|
46
|
|
|
public $noPostsMessage; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* Reference to the page name for linking to posts. |
|
50
|
|
|
* @var string |
|
51
|
|
|
*/ |
|
52
|
|
|
public $postPage; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* Reference to the page name for linking to categories. |
|
56
|
|
|
* @var string |
|
57
|
|
|
*/ |
|
58
|
|
|
public $categoryPage; |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* @return array |
|
62
|
|
|
*/ |
|
63
|
|
|
public function componentDetails() |
|
64
|
|
|
{ |
|
65
|
|
|
return [ |
|
66
|
|
|
'name' => 'Search Result', |
|
67
|
|
|
'description' => 'Displays a list of blog posts that match the search term on the page.' |
|
68
|
|
|
]; |
|
69
|
|
|
} |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* @see RainLab\Blog\Components\Posts::defineProperties() |
|
73
|
|
|
* @return array |
|
74
|
|
|
*/ |
|
75
|
|
|
public function defineProperties() |
|
76
|
|
|
{ |
|
77
|
|
|
// check build to add fallback to not supported inspector types if needed |
|
78
|
|
|
// $hasNewInspector = Parameters::get('system::core.build') >= 306; |
|
|
|
|
|
|
79
|
|
|
$categoryItems = BlogCategory::lists('name', 'id'); |
|
80
|
|
|
|
|
81
|
|
|
return [ |
|
82
|
|
|
'searchTerm' => [ |
|
83
|
|
|
'title' => 'Search Term', |
|
84
|
|
|
'description' => 'The value to determine what the user is searching for.', |
|
85
|
|
|
'type' => 'string', |
|
86
|
|
|
'default' => '{{ :search }}', |
|
87
|
|
|
], |
|
88
|
|
|
'pageNumber' => [ |
|
89
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_pagination', |
|
90
|
|
|
'description' => 'rainlab.blog::lang.settings.posts_pagination_description', |
|
91
|
|
|
'type' => 'string', |
|
92
|
|
|
'default' => '{{ :page }}', |
|
93
|
|
|
], |
|
94
|
|
|
'disableUrlMapping' => [ |
|
95
|
|
|
'title' => 'Disable URL Mapping', |
|
96
|
|
|
'description' => 'If the url Mapping is disabled the search form uses the default GET Parameter q ' |
|
97
|
|
|
. '(e.g. example.com/search?search=Foo instead of example.com/search/Foo)', |
|
98
|
|
|
'type' => 'checkbox', |
|
99
|
|
|
'default' => false, |
|
100
|
|
|
'showExternalParam' => false |
|
101
|
|
|
], |
|
102
|
|
|
'hightlight' => [ |
|
103
|
|
|
'title' => 'Hightlight Matches', |
|
104
|
|
|
'type' => 'checkbox', |
|
105
|
|
|
'default' => false, |
|
106
|
|
|
'showExternalParam' => false |
|
107
|
|
|
], |
|
108
|
|
|
'postsPerPage' => [ |
|
109
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_per_page', |
|
110
|
|
|
'type' => 'string', |
|
111
|
|
|
'validationPattern' => '^[0-9]+$', |
|
112
|
|
|
'validationMessage' => 'rainlab.blog::lang.settings.posts_per_page_validation', |
|
113
|
|
|
'default' => '10', |
|
114
|
|
|
], |
|
115
|
|
|
'noPostsMessage' => [ |
|
116
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_no_posts', |
|
117
|
|
|
'description' => 'rainlab.blog::lang.settings.posts_no_posts_description', |
|
118
|
|
|
'type' => 'string', |
|
119
|
|
|
'default' => 'No posts found', |
|
120
|
|
|
'showExternalParam' => false |
|
121
|
|
|
], |
|
122
|
|
|
'sortOrder' => [ |
|
123
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_order', |
|
124
|
|
|
'description' => 'rainlab.blog::lang.settings.posts_order_description', |
|
125
|
|
|
'type' => 'dropdown', |
|
126
|
|
|
'default' => 'published_at desc' |
|
127
|
|
|
], |
|
128
|
|
|
'includeCategories' => [ |
|
129
|
|
|
'title' => 'Include Categories', |
|
130
|
|
|
'description' => 'Only Posts with selected categories are included in the search result', |
|
131
|
|
|
// 'type' => $hasNewInspector ? 'set' : 'dropdown', |
|
|
|
|
|
|
132
|
|
|
'type' => 'set', |
|
133
|
|
|
'items' => $categoryItems, |
|
134
|
|
|
'group' => 'Categories' |
|
135
|
|
|
], |
|
136
|
|
|
'excludeCategories' => [ |
|
137
|
|
|
'title' => 'Exclude Categories', |
|
138
|
|
|
'description' => 'Posts with selected categories are excluded from the search result', |
|
139
|
|
|
// 'type' => $hasNewInspector ? 'set' : 'dropdown', |
|
|
|
|
|
|
140
|
|
|
'type' => 'set', |
|
141
|
|
|
'items' => $categoryItems, |
|
142
|
|
|
'group' => 'Categories' |
|
143
|
|
|
], |
|
144
|
|
|
'categoryPage' => [ |
|
145
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_category', |
|
146
|
|
|
'description' => 'rainlab.blog::lang.settings.posts_category_description', |
|
147
|
|
|
'type' => 'dropdown', |
|
148
|
|
|
'default' => 'blog/category', |
|
149
|
|
|
'group' => 'Links', |
|
150
|
|
|
], |
|
151
|
|
|
'postPage' => [ |
|
152
|
|
|
'title' => 'rainlab.blog::lang.settings.posts_post', |
|
153
|
|
|
'description' => 'rainlab.blog::lang.settings.posts_post_description', |
|
154
|
|
|
'type' => 'dropdown', |
|
155
|
|
|
'default' => 'blog/post', |
|
156
|
|
|
'group' => 'Links', |
|
157
|
|
|
], |
|
158
|
|
|
]; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* @return array |
|
163
|
|
|
*/ |
|
164
|
|
|
public function getIncludeCategoriesOptions() |
|
165
|
|
|
{ |
|
166
|
|
|
return BlogCategory::lists('name', 'id'); |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
/** |
|
170
|
|
|
* @return array |
|
171
|
|
|
*/ |
|
172
|
|
|
public function getExcludeCategoriesOptions() |
|
173
|
|
|
{ |
|
174
|
|
|
return BlogCategory::lists('name', 'id'); |
|
175
|
|
|
} |
|
176
|
|
|
|
|
177
|
|
|
/** |
|
178
|
|
|
* @see RainLab\Blog\Components\Posts::getCategoryPageOptions() |
|
179
|
|
|
* @return mixed |
|
180
|
|
|
*/ |
|
181
|
|
|
public function getCategoryPageOptions() |
|
182
|
|
|
{ |
|
183
|
|
|
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); |
|
184
|
|
|
} |
|
185
|
|
|
|
|
186
|
|
|
/** |
|
187
|
|
|
* @see RainLab\Blog\Components\Posts::getPostPageOptions() |
|
188
|
|
|
* @return mixed |
|
189
|
|
|
*/ |
|
190
|
|
|
public function getPostPageOptions() |
|
191
|
|
|
{ |
|
192
|
|
|
return Page::sortBy('baseFileName')->lists('baseFileName', 'baseFileName'); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
/** |
|
196
|
|
|
* @see RainLab\Blog\Components\Posts::getSortOrderOptions() |
|
197
|
|
|
* @return mixed |
|
198
|
|
|
*/ |
|
199
|
|
|
public function getSortOrderOptions() |
|
200
|
|
|
{ |
|
201
|
|
|
return BlogPost::$allowedSortingOptions; |
|
202
|
|
|
} |
|
203
|
|
|
|
|
204
|
|
|
/** |
|
205
|
|
|
* @see RainLab\Blog\Components\Posts::onRun() |
|
206
|
|
|
* @return mixed |
|
207
|
|
|
*/ |
|
208
|
|
|
public function onRun() |
|
209
|
|
|
{ |
|
210
|
|
|
$this->prepareVars(); |
|
211
|
|
|
|
|
212
|
|
|
// map get request to :search param |
|
213
|
|
|
$searchTerm = Input::get('search'); |
|
214
|
|
|
if (!$this->property('disableUrlMapping') && \Request::isMethod('get') && $searchTerm) { |
|
215
|
|
|
// add ?cats[] query string |
|
216
|
|
|
$cats = Input::get('cat'); |
|
217
|
|
|
$query = http_build_query(['cat' => $cats]); |
|
218
|
|
|
$query = preg_replace('/%5B[0-9]+%5D/simU', '%5B%5D', $query); |
|
219
|
|
|
$query = !empty($query) ? '?' . $query : ''; |
|
220
|
|
|
|
|
221
|
|
|
return Redirect::to( |
|
222
|
|
|
$this->currentPageUrl([ |
|
223
|
|
|
$this->searchParam => urlencode($searchTerm) |
|
224
|
|
|
]) |
|
225
|
|
|
. $query |
|
226
|
|
|
); |
|
227
|
|
|
} |
|
228
|
|
|
|
|
229
|
|
|
// load posts |
|
230
|
|
|
$this->posts = $this->page[ 'posts' ] = $this->listPosts(); |
|
231
|
|
|
|
|
232
|
|
|
/* |
|
233
|
|
|
* If the page number is not valid, redirect |
|
234
|
|
|
*/ |
|
235
|
|
|
if ($pageNumberParam = $this->paramName('pageNumber')) { |
|
236
|
|
|
$currentPage = $this->property('pageNumber'); |
|
237
|
|
|
|
|
238
|
|
|
if ($currentPage > ($lastPage = $this->posts->lastPage()) && $currentPage > 1) { |
|
239
|
|
|
return Redirect::to($this->currentPageUrl([$pageNumberParam => $lastPage])); |
|
240
|
|
|
} |
|
241
|
|
|
} |
|
242
|
|
|
} |
|
243
|
|
|
|
|
244
|
|
|
/** |
|
245
|
|
|
* @see RainLab\Blog\Components\Posts::prepareVars() |
|
246
|
|
|
*/ |
|
247
|
|
|
protected function prepareVars() |
|
248
|
|
|
{ |
|
249
|
|
|
$this->pageParam = $this->page[ 'pageParam' ] = $this->paramName('pageNumber'); |
|
250
|
|
|
$this->searchParam = $this->page[ 'searchParam' ] = $this->paramName('searchTerm'); |
|
251
|
|
|
$this->searchTerm = $this->page[ 'searchTerm' ] = urldecode($this->property('searchTerm')); |
|
252
|
|
|
$this->noPostsMessage = $this->page[ 'noPostsMessage' ] = $this->property('noPostsMessage'); |
|
253
|
|
|
|
|
254
|
|
|
if ($this->property('disableUrlMapping')) { |
|
255
|
|
|
$this->searchTerm = $this->page[ 'searchTerm' ] = urldecode(Input::get('search')); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
/* |
|
259
|
|
|
* Page links |
|
260
|
|
|
*/ |
|
261
|
|
|
$this->postPage = $this->page[ 'postPage' ] = $this->property('postPage'); |
|
262
|
|
|
$this->categoryPage = $this->page[ 'categoryPage' ] = $this->property('categoryPage'); |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
/** |
|
266
|
|
|
* @see RainLab\Blog\Components\Posts::prepareVars() |
|
267
|
|
|
* @return mixed |
|
268
|
|
|
*/ |
|
269
|
|
|
protected function listPosts() |
|
270
|
|
|
{ |
|
271
|
|
|
// Filter posts |
|
272
|
|
|
$posts = BlogPost::where(function ($q) { |
|
273
|
|
|
$q->where('title', 'LIKE', "%{$this->searchTerm}%") |
|
274
|
|
|
->orWhere('content', 'LIKE', "%{$this->searchTerm}%") |
|
275
|
|
|
->orWhere('excerpt', 'LIKE', "%{$this->searchTerm}%"); |
|
276
|
|
|
}); |
|
277
|
|
|
|
|
278
|
|
|
if (!is_null($this->property('includeCategories'))) { |
|
279
|
|
|
$posts = $posts->whereHas('categories', function ($q) { |
|
280
|
|
|
$q->whereIn('id', $this->property('includeCategories')); |
|
281
|
|
|
}); |
|
282
|
|
|
} |
|
283
|
|
|
|
|
284
|
|
|
if (!is_null($this->property('excludeCategories'))) { |
|
285
|
|
|
$posts = $posts->whereDoesntHave('categories', function ($q) { |
|
286
|
|
|
$q->whereIn('id', $this->property('excludeCategories')); |
|
287
|
|
|
}); |
|
288
|
|
|
} |
|
289
|
|
|
|
|
290
|
|
|
// filter categories |
|
291
|
|
|
$cat = Input::get('cat'); |
|
292
|
|
|
if ($cat) { |
|
293
|
|
|
$cat = is_array($cat) ? $cat : [$cat]; |
|
294
|
|
|
$posts->filterCategories($cat); |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
// List all the posts that match search terms, eager load their categories |
|
298
|
|
|
$posts = $posts->listFrontEnd([ |
|
299
|
|
|
'page' => $this->property('pageNumber'), |
|
300
|
|
|
'sort' => $this->property('sortOrder'), |
|
301
|
|
|
'perPage' => $this->property('postsPerPage'), |
|
302
|
|
|
]); |
|
303
|
|
|
|
|
304
|
|
|
/* |
|
305
|
|
|
* Add a "url" helper attribute for linking to each post and category |
|
306
|
|
|
*/ |
|
307
|
|
|
$posts->each(function ($post) { |
|
308
|
|
|
$post->setUrl($this->postPage, $this->controller); |
|
309
|
|
|
|
|
310
|
|
|
$post->categories->each(function ($category) { |
|
311
|
|
|
$category->setUrl($this->categoryPage, $this->controller); |
|
312
|
|
|
}); |
|
313
|
|
|
|
|
314
|
|
|
// apply highlight of search result |
|
315
|
|
|
$this->highlight($post); |
|
316
|
|
|
}); |
|
317
|
|
|
|
|
318
|
|
|
return $posts; |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
/** |
|
322
|
|
|
* @param \RainLab\Blog\Models\Post $post |
|
323
|
|
|
*/ |
|
324
|
|
|
protected function highlight(BlogPost $post) |
|
325
|
|
|
{ |
|
326
|
|
|
if ($this->property('hightlight')) { |
|
327
|
|
|
$searchTerm = preg_quote($this->searchTerm, '|'); |
|
328
|
|
|
|
|
329
|
|
|
// apply highlight |
|
330
|
|
|
$post->title = preg_replace('|(' . $searchTerm . ')|iu', '<mark>$1</mark>', $post->title); |
|
331
|
|
|
$post->excerpt = preg_replace('|(' . $searchTerm . ')|iu', '<mark>$1</mark>', $post->excerpt); |
|
332
|
|
|
|
|
333
|
|
|
$post->content_html = preg_replace( |
|
334
|
|
|
'~(?![^<>]*>)(' . $searchTerm . ')~ismu', |
|
335
|
|
|
'<mark>$1</mark>', |
|
336
|
|
|
$post->content_html |
|
337
|
|
|
); |
|
338
|
|
|
} |
|
339
|
|
|
} |
|
340
|
|
|
} |
|
341
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.