Passed
Pull Request — 4 (#10222)
by Steve
07:01
created

GridFieldSortableHeader::getState()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Forms\GridField;
4
5
use SilverStripe\Forms\LiteralField;
6
use SilverStripe\ORM\DataObjectSchema;
7
use SilverStripe\ORM\Sortable;
8
use SilverStripe\ORM\ArrayList;
9
use SilverStripe\ORM\SS_List;
10
use SilverStripe\ORM\DataObject;
11
use SilverStripe\View\ArrayData;
12
use SilverStripe\View\SSViewer;
13
use LogicException;
14
15
/**
16
 * GridFieldSortableHeader adds column headers to a {@link GridField} that can
17
 * also sort the columns.
18
 *
19
 * @see GridField
20
 */
21
class GridFieldSortableHeader extends AbstractGridFieldComponent implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider, GridField_StateProvider
22
{
23
24
    /**
25
     * See {@link setThrowExceptionOnBadDataType()}
26
     *
27
     * @var bool
28
     */
29
    protected $throwExceptionOnBadDataType = true;
30
31
    /**
32
     * @var array
33
     */
34
    public $fieldSorting = [];
35
36
    /**
37
     * Determine what happens when this component is used with a list that isn't {@link SS_Filterable}.
38
     *
39
     *  - true:  An exception is thrown
40
     *  - false: This component will be ignored - it won't make any changes to the GridField.
41
     *
42
     * By default, this is set to true so that it's clearer what's happening, but the predefined
43
     * {@link GridFieldConfig} subclasses set this to false for flexibility.
44
     *
45
     * @param bool $throwExceptionOnBadDataType
46
     * @return $this
47
     */
48
    public function setThrowExceptionOnBadDataType($throwExceptionOnBadDataType)
49
    {
50
        $this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType;
51
        return $this;
52
    }
53
54
    /**
55
     * See {@link setThrowExceptionOnBadDataType()}
56
     *
57
     * @return bool
58
     */
59
    public function getThrowExceptionOnBadDataType()
60
    {
61
        return $this->throwExceptionOnBadDataType;
62
    }
63
64
    /**
65
     * Check that this dataList is of the right data type.
66
     * Returns false if it's a bad data type, and if appropriate, throws an exception.
67
     *
68
     * @param SS_List $dataList
69
     * @return bool
70
     */
71
    protected function checkDataType($dataList)
72
    {
73
        if ($dataList instanceof Sortable) {
74
            return true;
75
        } else {
76
            if ($this->throwExceptionOnBadDataType) {
77
                throw new LogicException(
78
                    static::class . " expects an SS_Sortable list to be passed to the GridField."
79
                );
80
            }
81
            return false;
82
        }
83
    }
84
85
    /**
86
     * Specify sorting with fieldname as the key, and actual fieldname to sort as value.
87
     * Example: array("MyCustomTitle"=>"Title", "MyCustomBooleanField" => "ActualBooleanField")
88
     *
89
     * @param array $sorting
90
     * @return $this
91
     */
92
    public function setFieldSorting($sorting)
93
    {
94
        $this->fieldSorting = $sorting;
95
        return $this;
96
    }
97
98
    /**
99
     * @return array
100
     */
101
    public function getFieldSorting()
102
    {
103
        return $this->fieldSorting;
104
    }
105
106
    /**
107
     * Returns the header row providing titles with sort buttons
108
     *
109
     * @param GridField $gridField
110
     * @return array
111
     */
112
    public function getHTMLFragments($gridField)
113
    {
114
        $list = $gridField->getList();
115
        if (!$this->checkDataType($list)) {
116
            return null;
117
        }
118
        /** @var Sortable $list */
119
        $forTemplate = new ArrayData([]);
120
        $forTemplate->Fields = new ArrayList;
0 ignored issues
show
Bug Best Practice introduced by
The property Fields does not exist on SilverStripe\View\ArrayData. Since you implemented __set, consider adding a @property annotation.
Loading history...
121
122
        $state = $this->getState($gridField);
123
        $columns = $gridField->getColumns();
124
        $currentColumn = 0;
125
126
        $schema = DataObject::getSchema();
127
        foreach ($columns as $columnField) {
128
            $currentColumn++;
129
            $metadata = $gridField->getColumnMetadata($columnField);
130
            $fieldName = str_replace('.', '-', $columnField ?: '');
131
            $title = $metadata['title'];
132
133
            if (isset($this->fieldSorting[$columnField]) && $this->fieldSorting[$columnField]) {
134
                $columnField = $this->fieldSorting[$columnField];
135
            }
136
137
            $allowSort = ($title && $list->canSortBy($columnField));
138
139
            if (!$allowSort && strpos((string) $columnField, '.') !== false) {
140
                // we have a relation column with dot notation
141
                // @see DataObject::relField for approximation
142
                $parts = explode('.', (string) $columnField);
143
                $tmpItem = singleton($list->dataClass());
0 ignored issues
show
Bug introduced by
The method dataClass() does not exist on SilverStripe\ORM\Sortable. It seems like you code against a sub-type of said class. However, the method does not exist in SilverStripe\ORM\Relation. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

143
                $tmpItem = singleton($list->/** @scrutinizer ignore-call */ dataClass());
Loading history...
144
                for ($idx = 0; $idx < sizeof($parts ?: []); $idx++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function sizeof() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
145
                    $methodName = $parts[$idx];
146
                    if ($tmpItem instanceof SS_List) {
147
                        // It's impossible to sort on a HasManyList/ManyManyList
148
                        break;
149
                    } elseif ($tmpItem && method_exists($tmpItem, 'hasMethod') && $tmpItem->hasMethod($methodName)) {
150
                        // The part is a relation name, so get the object/list from it
151
                        $tmpItem = $tmpItem->$methodName();
152
                    } elseif ($tmpItem instanceof DataObject
153
                        && $schema->fieldSpec($tmpItem, $methodName, DataObjectSchema::DB_ONLY)
154
                    ) {
155
                        // Else, if we've found a database field at the end of the chain, we can sort on it.
156
                        // If a method is applied further to this field (E.g. 'Cost.Currency') then don't try to sort.
157
                        $allowSort = $idx === sizeof($parts ?: []) - 1;
158
                        break;
159
                    } else {
160
                        // If neither method nor field, then unable to sort
161
                        break;
162
                    }
163
                }
164
            }
165
166
            if ($allowSort) {
167
                $dir = 'asc';
168
                if ($state->SortColumn(null) == $columnField && $state->SortDirection('asc') == 'asc') {
0 ignored issues
show
Bug introduced by
The method SortColumn() does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
                if ($state->/** @scrutinizer ignore-call */ SortColumn(null) == $columnField && $state->SortDirection('asc') == 'asc') {
Loading history...
Bug introduced by
The method SortDirection() does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

168
                if ($state->SortColumn(null) == $columnField && $state->/** @scrutinizer ignore-call */ SortDirection('asc') == 'asc') {
Loading history...
169
                    $dir = 'desc';
170
                }
171
172
                $field = GridField_FormAction::create(
173
                    $gridField,
174
                    'SetOrder' . $fieldName,
175
                    $title,
176
                    "sort$dir",
177
                    ['SortColumn' => $columnField]
178
                )->addExtraClass('grid-field__sort');
179
180
                if ($state->SortColumn(null) == $columnField) {
181
                    $field->addExtraClass('ss-gridfield-sorted');
182
183
                    if ($state->SortDirection('asc') == 'asc') {
184
                        $field->addExtraClass('ss-gridfield-sorted-asc');
185
                    } else {
186
                        $field->addExtraClass('ss-gridfield-sorted-desc');
187
                    }
188
                }
189
            } else {
190
                if ($currentColumn == count($columns ?: [])) {
191
                    $filter = $gridField->getConfig()->getComponentByType(GridFieldFilterHeader::class);
192
193
                    if ($filter && $filter->useLegacyFilterHeader && $filter->canFilterAnyColumns($gridField)) {
0 ignored issues
show
Bug introduced by
Accessing useLegacyFilterHeader on the interface SilverStripe\Forms\GridField\GridFieldComponent suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
Bug introduced by
The method canFilterAnyColumns() does not exist on SilverStripe\Forms\GridField\GridFieldComponent. It seems like you code against a sub-type of SilverStripe\Forms\GridField\GridFieldComponent such as SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldDetailForm or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\Tests...ndlerTest\TestComponent or SilverStripe\Forms\GridField\GridFieldDetailForm. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

193
                    if ($filter && $filter->useLegacyFilterHeader && $filter->/** @scrutinizer ignore-call */ canFilterAnyColumns($gridField)) {
Loading history...
194
                        $field = new LiteralField(
195
                            $fieldName,
196
                            sprintf(
197
                                '<button type="button" name="showFilter" aria-label="%s" title="%s"' .
198
                                ' class="btn btn-secondary font-icon-search btn--no-text btn--icon-large grid-field__filter-open"></button>',
199
                                _t('SilverStripe\\Forms\\GridField\\GridField.OpenFilter', "Open search and filter"),
200
                                _t('SilverStripe\\Forms\\GridField\\GridField.OpenFilter', "Open search and filter")
201
                            )
202
                        );
203
                    } else {
204
                        $field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
205
                    }
206
                } else {
207
                    $field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
208
                }
209
            }
210
            $forTemplate->Fields->push($field);
0 ignored issues
show
Bug Best Practice introduced by
The property Fields does not exist on SilverStripe\View\ArrayData. Since you implemented __get, consider adding a @property annotation.
Loading history...
211
        }
212
213
        $template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
214
        return [
215
            'header' => $forTemplate->renderWith($template),
216
        ];
217
    }
218
219
    /**
220
     *
221
     * @param GridField $gridField
222
     * @return array
223
     */
224
    public function getActions($gridField)
225
    {
226
        if (!$this->checkDataType($gridField->getList())) {
227
            return [];
228
        }
229
230
        return ['sortasc', 'sortdesc'];
231
    }
232
233
    public function handleAction(GridField $gridField, $actionName, $arguments, $data)
234
    {
235
        if (!$this->checkDataType($gridField->getList())) {
236
            return;
237
        }
238
239
        $state = $this->getState($gridField);
240
        switch ($actionName) {
241
            case 'sortasc':
242
                $state->SortColumn = $arguments['SortColumn'];
0 ignored issues
show
Bug Best Practice introduced by
The property SortColumn does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __set, consider adding a @property annotation.
Loading history...
243
                $state->SortDirection = 'asc';
0 ignored issues
show
Bug Best Practice introduced by
The property SortDirection does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __set, consider adding a @property annotation.
Loading history...
244
                break;
245
246
            case 'sortdesc':
247
                $state->SortColumn = $arguments['SortColumn'];
248
                $state->SortDirection = 'desc';
249
                break;
250
        }
251
    }
252
253
    /**
254
     * Returns the manipulated (sorted) DataList. Field names will simply add an
255
     * 'ORDER BY' clause, relation names will add appropriate joins to the
256
     * {@link DataQuery} first.
257
     *
258
     * @param GridField $gridField
259
     * @param SS_List $dataList
260
     * @return SS_List
261
     */
262
    public function getManipulatedData(GridField $gridField, SS_List $dataList)
263
    {
264
        if (!$this->checkDataType($dataList)) {
265
            return $dataList;
266
        }
267
268
        /** @var Sortable $dataList */
269
        $state = $this->getState($gridField);
270
        if ($state->SortColumn == "") {
0 ignored issues
show
Bug Best Practice introduced by
The property SortColumn does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __get, consider adding a @property annotation.
Loading history...
271
            return $dataList;
272
        }
273
274
        return $dataList->sort($state->SortColumn, $state->SortDirection('asc'));
0 ignored issues
show
Unused Code introduced by
The call to SilverStripe\ORM\Sortable::sort() has too many arguments starting with $state->SortColumn. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

274
        return $dataList->/** @scrutinizer ignore-call */ sort($state->SortColumn, $state->SortDirection('asc'));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
275
    }
276
277
    /**
278
     * Extract state data from the parent gridfield
279
     * @param GridField $gridField
280
     * @return GridState_Data
281
     */
282
    private function getState(GridField $gridField): GridState_Data
283
    {
284
        return $gridField->State->GridFieldSortableHeader;
0 ignored issues
show
Bug Best Practice introduced by
The property GridFieldSortableHeader does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __get, consider adding a @property annotation.
Loading history...
285
    }
286
287
    public function initDefaultState(GridState_Data $data): void
288
    {
289
        $data->GridFieldSortableHeader->initDefaults(['SortColumn' => null, 'SortDirection' => 'asc']);
0 ignored issues
show
Bug Best Practice introduced by
The property GridFieldSortableHeader does not exist on SilverStripe\Forms\GridField\GridState_Data. Since you implemented __get, consider adding a @property annotation.
Loading history...
290
    }
291
}
292