GridFieldSortableHeader::getFieldSorting()   A
last analyzed

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 0
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 implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider
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 = array();
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 sortings 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(array());
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 = $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...
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($columnField, '.') !== false) {
140
                // we have a relation column with dot notation
141
                // @see DataObject::relField for approximation
142
                $parts = explode('.', $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 (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') {
169
                    $dir = 'desc';
170
                }
171
172
                $field = GridField_FormAction::create(
173
                    $gridField,
174
                    'SetOrder' . $fieldName,
175
                    $title,
176
                    "sort$dir",
177
                    array('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\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader or SilverStripe\Forms\GridField\GridFieldPrintButton or SilverStripe\Forms\GridField\GridFieldFilterHeader 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 '
199
                                . 'grid-field__filter-open"></button>',
200
                                _t('SilverStripe\\Forms\\GridField\\GridField.OpenFilter', "Open search and filter"),
201
                                _t('SilverStripe\\Forms\\GridField\\GridField.OpenFilter', "Open search and filter")
202
                            )
203
                        );
204
                    } else {
205
                        $field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
206
                    }
207
                } else {
208
                    $field = new LiteralField($fieldName, '<span class="non-sortable">' . $title . '</span>');
209
                }
210
            }
211
            $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...
212
        }
213
214
        $template = SSViewer::get_templates_by_class($this, '_Row', __CLASS__);
215
        return array(
216
            'header' => $forTemplate->renderWith($template),
217
        );
218
    }
219
220
    /**
221
     *
222
     * @param GridField $gridField
223
     * @return array
224
     */
225
    public function getActions($gridField)
226
    {
227
        if (!$this->checkDataType($gridField->getList())) {
228
            return [];
229
        }
230
231
        return array('sortasc', 'sortdesc');
232
    }
233
234
    public function handleAction(GridField $gridField, $actionName, $arguments, $data)
235
    {
236
        if (!$this->checkDataType($gridField->getList())) {
237
            return;
238
        }
239
240
        $state = $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...
241
        switch ($actionName) {
242
            case 'sortasc':
243
                $state->SortColumn = $arguments['SortColumn'];
244
                $state->SortDirection = 'asc';
245
                break;
246
247
            case 'sortdesc':
248
                $state->SortColumn = $arguments['SortColumn'];
249
                $state->SortDirection = 'desc';
250
                break;
251
        }
252
    }
253
254
    /**
255
     * Returns the manipulated (sorted) DataList. Field names will simply add an
256
     * 'ORDER BY' clause, relation names will add appropriate joins to the
257
     * {@link DataQuery} first.
258
     *
259
     * @param GridField $gridField
260
     * @param SS_List $dataList
261
     * @return SS_List
262
     */
263
    public function getManipulatedData(GridField $gridField, SS_List $dataList)
264
    {
265
        if (!$this->checkDataType($dataList)) {
266
            return $dataList;
267
        }
268
269
        /** @var Sortable $dataList */
270
        $state = $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...
271
        if ($state->SortColumn == "") {
272
            return $dataList;
273
        }
274
275
        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

275
        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...
276
    }
277
}
278