Total Complexity | 58 |
Total Lines | 488 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like GridFieldFilterHeader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use GridFieldFilterHeader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
29 | class GridFieldFilterHeader implements |
||
30 | GridField_URLHandler, |
||
31 | GridField_HTMLProvider, |
||
32 | GridField_DataManipulator, |
||
33 | GridField_ActionProvider |
||
34 | { |
||
35 | /** |
||
36 | * See {@link setThrowExceptionOnBadDataType()} |
||
37 | * |
||
38 | * @var bool |
||
39 | */ |
||
40 | protected $throwExceptionOnBadDataType = true; |
||
41 | |||
42 | /** |
||
43 | * Indicates that this component should revert to displaying it's legacy |
||
44 | * table header style rather than the react driven search box |
||
45 | * |
||
46 | * @deprecated 4.3.0:5.0.0 Will be removed in 5.0 |
||
47 | * @var bool |
||
48 | */ |
||
49 | public $useLegacyFilterHeader = false; |
||
50 | |||
51 | /** |
||
52 | * Forces all filter components to revert to displaying the legacy |
||
53 | * table header style rather than the react driven search box |
||
54 | * |
||
55 | * @deprecated 4.3.0:5.0.0 Will be removed in 5.0 |
||
56 | * @config |
||
57 | * @var bool |
||
58 | */ |
||
59 | private static $force_legacy = false; |
||
60 | |||
61 | /** |
||
62 | * @var \SilverStripe\ORM\Search\SearchContext |
||
63 | */ |
||
64 | protected $searchContext = null; |
||
65 | |||
66 | /** |
||
67 | * @var Form |
||
68 | */ |
||
69 | protected $searchForm = null; |
||
70 | |||
71 | /** |
||
72 | * @var callable |
||
73 | * @deprecated 4.3.0:5.0.0 Will be removed in 5.0 |
||
74 | */ |
||
75 | protected $updateSearchContextCallback = null; |
||
76 | |||
77 | /** |
||
78 | * @var callable |
||
79 | * @deprecated 4.3.0:5.0.0 Will be removed in 5.0 |
||
80 | */ |
||
81 | protected $updateSearchFormCallback = null; |
||
82 | |||
83 | /** |
||
84 | * @inheritDoc |
||
85 | */ |
||
86 | public function getURLHandlers($gridField) |
||
87 | { |
||
88 | return [ |
||
89 | 'GET schema/SearchForm' => 'getSearchFormSchema' |
||
90 | ]; |
||
91 | } |
||
92 | |||
93 | /** |
||
94 | * @param bool $useLegacy This will be removed in 5.0 |
||
95 | * @param callable|null $updateSearchContext This will be removed in 5.0 |
||
96 | * @param callable|null $updateSearchForm This will be removed in 5.0 |
||
97 | */ |
||
98 | public function __construct( |
||
106 | } |
||
107 | |||
108 | /** |
||
109 | * Determine what happens when this component is used with a list that isn't {@link SS_Filterable}. |
||
110 | * |
||
111 | * - true: An exception is thrown |
||
112 | * - false: This component will be ignored - it won't make any changes to the GridField. |
||
113 | * |
||
114 | * By default, this is set to true so that it's clearer what's happening, but the predefined |
||
115 | * {@link GridFieldConfig} subclasses set this to false for flexibility. |
||
116 | * |
||
117 | * @param bool $throwExceptionOnBadDataType |
||
118 | */ |
||
119 | public function setThrowExceptionOnBadDataType($throwExceptionOnBadDataType) |
||
120 | { |
||
121 | $this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType; |
||
122 | } |
||
123 | |||
124 | /** |
||
125 | * See {@link setThrowExceptionOnBadDataType()} |
||
126 | */ |
||
127 | public function getThrowExceptionOnBadDataType() |
||
128 | { |
||
129 | return $this->throwExceptionOnBadDataType; |
||
130 | } |
||
131 | |||
132 | /** |
||
133 | * Check that this dataList is of the right data type. |
||
134 | * Returns false if it's a bad data type, and if appropriate, throws an exception. |
||
135 | * |
||
136 | * @param SS_List $dataList |
||
137 | * @return bool |
||
138 | */ |
||
139 | protected function checkDataType($dataList) |
||
140 | { |
||
141 | if ($dataList instanceof Filterable) { |
||
142 | return true; |
||
143 | } else { |
||
144 | if ($this->throwExceptionOnBadDataType) { |
||
145 | throw new LogicException( |
||
146 | static::class . " expects an SS_Filterable list to be passed to the GridField." |
||
147 | ); |
||
148 | } |
||
149 | return false; |
||
150 | } |
||
151 | } |
||
152 | |||
153 | /** |
||
154 | * If the GridField has a filterable datalist, return an array of actions |
||
155 | * |
||
156 | * @param GridField $gridField |
||
157 | * @return array |
||
158 | */ |
||
159 | public function getActions($gridField) |
||
160 | { |
||
161 | if (!$this->checkDataType($gridField->getList())) { |
||
162 | return []; |
||
163 | } |
||
164 | |||
165 | return ['filter', 'reset']; |
||
166 | } |
||
167 | |||
168 | /** |
||
169 | * If the GridField has a filterable datalist, return an array of actions |
||
170 | * |
||
171 | * @param GridField $gridField |
||
172 | * @return void |
||
173 | */ |
||
174 | public function handleAction(GridField $gridField, $actionName, $arguments, $data) |
||
175 | { |
||
176 | if (!$this->checkDataType($gridField->getList())) { |
||
177 | return; |
||
178 | } |
||
179 | |||
180 | $state = $gridField->State->GridFieldFilterHeader; |
||
181 | $state->Columns = null; |
||
182 | if ($actionName === 'filter') { |
||
183 | if (isset($data['filter'][$gridField->getName()])) { |
||
184 | foreach ($data['filter'][$gridField->getName()] as $key => $filter) { |
||
185 | $state->Columns->$key = $filter; |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | } |
||
190 | |||
191 | |||
192 | /** |
||
193 | * @inheritDoc |
||
194 | */ |
||
195 | public function getManipulatedData(GridField $gridField, SS_List $dataList) |
||
196 | { |
||
197 | if (!$this->checkDataType($dataList)) { |
||
198 | return $dataList; |
||
199 | } |
||
200 | |||
201 | /** @var Filterable $dataList */ |
||
202 | /** @var GridState_Data $columns */ |
||
203 | $columns = $gridField->State->GridFieldFilterHeader->Columns(null); |
||
204 | if (empty($columns)) { |
||
205 | return $dataList; |
||
206 | } |
||
207 | |||
208 | $filterArguments = $columns->toArray(); |
||
209 | $dataListClone = clone($dataList); |
||
210 | $results = $this->getSearchContext($gridField) |
||
211 | ->getQuery($filterArguments, false, false, $dataListClone); |
||
212 | |||
213 | return $results; |
||
214 | } |
||
215 | |||
216 | /** |
||
217 | * Returns whether this {@link GridField} has any columns to filter on at all |
||
218 | * |
||
219 | * @param GridField $gridField |
||
220 | * @return boolean |
||
221 | */ |
||
222 | public function canFilterAnyColumns($gridField) |
||
223 | { |
||
224 | $list = $gridField->getList(); |
||
225 | |||
226 | if (!$this->checkDataType($list)) { |
||
227 | return false; |
||
228 | } |
||
229 | |||
230 | $columns = $gridField->getColumns(); |
||
231 | foreach ($columns as $columnField) { |
||
232 | $metadata = $gridField->getColumnMetadata($columnField); |
||
233 | $title = $metadata['title']; |
||
234 | |||
235 | if ($title && $list->canFilterBy($columnField)) { |
||
236 | return true; |
||
237 | } |
||
238 | } |
||
239 | |||
240 | return false; |
||
241 | } |
||
242 | |||
243 | |||
244 | /** |
||
245 | * Generate a search context based on the model class of the of the GridField |
||
246 | * |
||
247 | * @param GridField $gridfield |
||
248 | * @return \SilverStripe\ORM\Search\SearchContext |
||
249 | */ |
||
250 | public function getSearchContext(GridField $gridField) |
||
261 | } |
||
262 | |||
263 | /** |
||
264 | * Returns the search field schema for the component |
||
265 | * |
||
266 | * @param GridField $gridfield |
||
267 | * @return string |
||
268 | */ |
||
269 | public function getSearchFieldSchema(GridField $gridField) |
||
311 | } |
||
312 | |||
313 | /** |
||
314 | * Returns the search form for the component |
||
315 | * |
||
316 | * @param GridField $gridField |
||
317 | * @return Form|null |
||
318 | */ |
||
319 | public function getSearchForm(GridField $gridField) |
||
320 | { |
||
321 | $searchContext = $this->getSearchContext($gridField); |
||
322 | $searchFields = $searchContext->getSearchFields(); |
||
323 | |||
324 | if ($searchFields->count() === 0) { |
||
325 | return null; |
||
326 | } |
||
327 | |||
328 | if ($this->searchForm) { |
||
329 | return $this->searchForm; |
||
330 | } |
||
331 | |||
332 | // Append a prefix to search field names to prevent conflicts with other fields in the search form |
||
333 | foreach ($searchFields as $field) { |
||
334 | $field->setName('Search__' . $field->getName()); |
||
335 | } |
||
336 | |||
337 | $columns = $gridField->getColumns(); |
||
338 | |||
339 | // Update field titles to match column titles |
||
340 | foreach ($columns as $columnField) { |
||
341 | $metadata = $gridField->getColumnMetadata($columnField); |
||
342 | // Get the field name, without any modifications |
||
343 | $name = explode('.', $columnField); |
||
344 | $title = $metadata['title']; |
||
345 | $field = $searchFields->fieldByName($name[0]); |
||
346 | |||
347 | if ($field) { |
||
348 | $field->setTitle($title); |
||
349 | } |
||
350 | } |
||
351 | |||
352 | foreach ($searchFields->getIterator() as $field) { |
||
353 | $field->addExtraClass('stacked'); |
||
354 | } |
||
355 | |||
356 | $name = $gridField->Title ?: singleton($gridField->getModelClass())->i18n_plural_name(); |
||
357 | |||
358 | $this->searchForm = $form = new Form( |
||
359 | $gridField, |
||
360 | $name . "SearchForm", |
||
361 | $searchFields, |
||
362 | new FieldList() |
||
363 | ); |
||
364 | |||
365 | $form->setFormMethod('get'); |
||
366 | $form->setFormAction($gridField->Link()); |
||
367 | $form->addExtraClass('cms-search-form form--no-dividers'); |
||
368 | $form->disableSecurityToken(); // This form is not tied to session so we disable this |
||
369 | $form->loadDataFrom($searchContext->getSearchParams()); |
||
370 | |||
371 | if ($this->updateSearchFormCallback) { |
||
372 | call_user_func($this->updateSearchFormCallback, $form); |
||
373 | } |
||
374 | |||
375 | return $this->searchForm; |
||
376 | } |
||
377 | |||
378 | /** |
||
379 | * Returns the search form schema for the component |
||
380 | * |
||
381 | * @param GridField $gridfield |
||
382 | * @return HTTPResponse |
||
383 | */ |
||
384 | public function getSearchFormSchema(GridField $gridField) |
||
385 | { |
||
386 | $form = $this->getSearchForm($gridField); |
||
387 | |||
388 | // If there are no filterable fields, return a 400 response |
||
389 | if (!$form) { |
||
390 | return new HTTPResponse(_t(__CLASS__ . '.SearchFormFaliure', 'No search form could be generated'), 400); |
||
391 | } |
||
392 | |||
393 | $parts = $gridField->getRequest()->getHeader(LeftAndMain::SCHEMA_HEADER); |
||
394 | $schemaID = $gridField->getRequest()->getURL(); |
||
395 | $data = FormSchema::singleton() |
||
396 | ->getMultipartSchema($parts, $schemaID, $form); |
||
397 | |||
398 | $response = new HTTPResponse(json_encode($data)); |
||
399 | $response->addHeader('Content-Type', 'application/json'); |
||
400 | return $response; |
||
401 | } |
||
402 | |||
403 | /** |
||
404 | * Generate fields for the legacy filter header row |
||
405 | * |
||
406 | * @deprecated 5.0 |
||
407 | * @param GridField $gridfield |
||
408 | * @return ArrayList|null |
||
409 | */ |
||
410 | public function getLegacyFilterHeader(GridField $gridField) |
||
411 | { |
||
412 | Deprecation::notice('5.0', 'Table row based filter header will be removed in favor of search field in 5.0'); |
||
413 | |||
414 | $list = $gridField->getList(); |
||
415 | if (!$this->checkDataType($list)) { |
||
416 | return null; |
||
417 | } |
||
418 | |||
419 | $columns = $gridField->getColumns(); |
||
420 | $filterArguments = $gridField->State->GridFieldFilterHeader->Columns->toArray(); |
||
421 | $currentColumn = 0; |
||
422 | $canFilter = false; |
||
423 | $fieldsList = new ArrayList(); |
||
424 | |||
425 | foreach ($columns as $columnField) { |
||
426 | $currentColumn++; |
||
427 | $metadata = $gridField->getColumnMetadata($columnField); |
||
428 | $title = $metadata['title']; |
||
429 | $fields = new FieldGroup(); |
||
430 | |||
431 | if ($title && $list->canFilterBy($columnField)) { |
||
432 | $canFilter = true; |
||
433 | |||
434 | $value = ''; |
||
435 | if (isset($filterArguments[$columnField])) { |
||
436 | $value = $filterArguments[$columnField]; |
||
437 | } |
||
438 | $field = new TextField('filter[' . $gridField->getName() . '][' . $columnField . ']', '', $value); |
||
439 | $field->addExtraClass('grid-field__sort-field'); |
||
440 | $field->addExtraClass('no-change-track'); |
||
441 | |||
442 | $field->setAttribute( |
||
443 | 'placeholder', |
||
444 | _t('SilverStripe\\Forms\\GridField\\GridField.FilterBy', "Filter by ") |
||
445 | . _t('SilverStripe\\Forms\\GridField\\GridField.' . $metadata['title'], $metadata['title']) |
||
446 | ); |
||
447 | |||
448 | $fields->push($field); |
||
449 | $fields->push( |
||
450 | GridField_FormAction::create($gridField, 'reset', false, 'reset', null) |
||
451 | ->addExtraClass('btn font-icon-cancel btn-secondary btn--no-text ss-gridfield-button-reset') |
||
452 | ->setAttribute('title', _t('SilverStripe\\Forms\\GridField\\GridField.ResetFilter', "Reset")) |
||
453 | ->setAttribute('id', 'action_reset_' . $gridField->getModelClass() . '_' . $columnField) |
||
454 | ); |
||
455 | } |
||
456 | |||
457 | if ($currentColumn == count($columns)) { |
||
458 | $fields->push( |
||
459 | GridField_FormAction::create($gridField, 'filter', false, 'filter', null) |
||
460 | ->addExtraClass( |
||
461 | 'btn font-icon-search btn--no-text btn--icon-large grid-field__filter-submit' |
||
462 | . ' ss-gridfield-button-filter' |
||
463 | ) |
||
464 | ->setAttribute('title', _t('SilverStripe\\Forms\\GridField\\GridField.Filter', 'Filter')) |
||
465 | ->setAttribute('id', 'action_filter_' . $gridField->getModelClass() . '_' . $columnField) |
||
466 | ); |
||
467 | $fields->push( |
||
468 | GridField_FormAction::create($gridField, 'reset', false, 'reset', null) |
||
469 | ->addExtraClass( |
||
470 | 'btn font-icon-cancel btn--no-text grid-field__filter-clear btn--icon-md' |
||
471 | . 'ss-gridfield-button-close' |
||
472 | ) |
||
473 | ->setAttribute('title', _t('SilverStripe\\Forms\\GridField\\GridField.ResetFilter', "Reset")) |
||
474 | ->setAttribute('id', 'action_reset_' . $gridField->getModelClass() . '_' . $columnField) |
||
475 | ); |
||
476 | $fields->addExtraClass('grid-field__filter-buttons'); |
||
477 | $fields->addExtraClass('no-change-track'); |
||
478 | } |
||
479 | |||
480 | $fieldsList->push($fields); |
||
481 | } |
||
482 | |||
483 | return $canFilter ? $fieldsList : null; |
||
484 | } |
||
485 | |||
486 | /** |
||
487 | * Either returns the legacy filter header or the search button and field |
||
488 | * |
||
489 | * @param GridField $gridField |
||
490 | * @return array|null |
||
491 | */ |
||
492 | public function getHTMLFragments($gridField) |
||
517 | ) |
||
518 | ]; |
||
519 | } |
||
520 | } |
||
522 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"]
, you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths