Completed
Push — master ( 86f929...a42e5b )
by Timo
04:48
created

SortableHeader::_removeIndex()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2
1
<?php
2
3
namespace hamburgscleanest\DataTables\Models\HeaderFormatters;
4
5
use hamburgscleanest\DataTables\Facades\SessionHelper;
6
use hamburgscleanest\DataTables\Facades\UrlHelper;
7
use hamburgscleanest\DataTables\Interfaces\HeaderFormatter;
8
use hamburgscleanest\DataTables\Models\Header;
9
use Illuminate\Http\Request;
10
11
/**
12
 * Class SortableHeader
13
 *
14
 * Either whitelist the headers you want to make sortable
15
 * or blacklist the headers you do not want to become sortable.
16
 *
17
 * If nothing is specified, all headers are sortable.
18
 *
19
 * @package hamburgscleanest\DataTables\Models\HeaderFormatters
20
 */
21
class SortableHeader implements HeaderFormatter {
22
23
    const SORTING_SEPARATOR = '~';
24
    const COLUMN_SEPARATOR  = '.';
25
26
    /** @var string */
27
    private $_defaultDirection = 'asc';
28
29
    /** @var array */
30
    private $_sortableHeaders;
31
32
    /** @var array */
33
    private $_dontSort;
34
35
    /** @var array */
36
    private $_sortingSymbols = ['asc' => '&#x25B2;', 'desc' => '&#x25BC;', 'none' => '⇵'];
37
38
    /**
39
     * SortableHeader constructor.
40
     *
41
     * @param array $sortableHeaders
42
     * @param array $dontSort
43
     */
44 7
    public function __construct(array $sortableHeaders = [], array $dontSort = [])
45
    {
46 7
        $this->_sortableHeaders = $sortableHeaders;
47 7
        $this->_dontSort = $dontSort;
48
49 7
    }
50
51
    /**
52
     * @param array $sortingSymbols
53
     * @return SortableHeader
54
     */
55 1
    public function sortingSymbols(array $sortingSymbols): SortableHeader
56
    {
57 1
        $this->_sortingSymbols = $sortingSymbols;
58
59 1
        return $this;
60
    }
61
62
    /**
63
     * Add a field to the sortable fields.
64
     *
65
     * @param string $header
66
     * @return SortableHeader
67
     */
68 1
    public function makeSortable(string $header): SortableHeader
69
    {
70 1
        $this->_sortableHeaders[] = $header;
71 1
        $this->_removeIndex($this->_dontSort, $header);
72
73 1
        return $this;
74
    }
75
76
    /**
77
     * @param array $array
78
     * @param string $key
79
     */
80 2
    private function _removeIndex(array $array, string $key)
81
    {
82 2
        $index = \array_search($key, $array, true);
83 2
        if ($index !== false)
84
        {
85 1
            unset($array[$index]);
86
        }
87 2
    }
88
89
    /**
90
     * Remove the ability to sort by this column/header.
91
     *
92
     * @param string $header
93
     * @return SortableHeader
94
     */
95 1
    public function dontSort(string $header): SortableHeader
96
    {
97 1
        $this->_dontSort[] = $header;
98 1
        $this->_removeIndex($this->_sortableHeaders, $header);
99
100 1
        return $this;
101
    }
102
103
    /**
104
     * Adds a link to sort by this header/column.
105
     * Also indicates how the columns are sorted (when sorted).
106
     *
107
     * @param Header $header
108
     * @param Request $request
109
     * @throws \RuntimeException
110
     */
111 7
    public function format(Header $header, Request $request)
112
    {
113 7
        $headerAttributeName = $header->getAttributeName();
114
115 7
        $sortFields = $this->_extractSortFields($request);
116 7
        $direction = $sortFields[$headerAttributeName] ?? 'none';
117
118 7
        if ($this->_showSortLink($headerAttributeName))
119
        {
120 6
            $header->name = '<a class="sortable-header" href="' . $this->_buildSortUrl($request, $headerAttributeName, $direction) . '">' . $header->name .
121 6
                            ' <span class="sort-symbol">' . ($this->_sortingSymbols[$direction] ?? '') . '</span></a>';
122
        }
123 7
    }
124
125
    /**
126
     * @param Request $request
127
     * @return array
128
     */
129 7
    private function _extractSortFields(Request $request): array
130
    {
131 7
        return \array_diff(
132 7
            $this->_getSortFields($request) + $this->_getRememberedState($request) + $this->_getDefaultSorting($this->_sortableHeaders),
133 7
            $this->_getDefaultSorting($this->_dontSort)
134
        );
135
    }
136
137
    /**
138
     * Get the sorted fields from the request.
139
     *
140
     * @param Request $request
141
     * @return array
142
     */
143 7
    private function _getSortFields(Request $request): array
144
    {
145 7
        $sortFields = $request->get('sort');
146 7
        if ($sortFields === null)
147
        {
148 5
            return [];
149
        }
150
151 2
        $sorting = [];
152 2
        foreach (\explode(self::COLUMN_SEPARATOR, $sortFields) as $field)
153
        {
154 2
            $sortParts = $this->_getSortParts($field);
155 2
            $sorting[$sortParts[0]] = \mb_strtolower($sortParts[1]);
156
        }
157
158 2
        return $sorting;
159
    }
160
161
    /**
162
     * Get the name of the field and the sorting direction (default: "asc").
163
     *
164
     * @param string $field
165
     * @return array
166
     */
167 2
    private function _getSortParts(string $field): array
168
    {
169 2
        $sortParts = \explode(self::SORTING_SEPARATOR, $field);
170 2
        if (\count($sortParts) === 1)
171
        {
172 1
            $sortParts[1] = $this->_defaultDirection;
173
        }
174
175 2
        return $sortParts;
176
    }
177
178
    /**
179
     * @param Request $request
180
     * @return array
181
     */
182 7
    private function _getRememberedState(Request $request): array
183
    {
184 7
        return SessionHelper::getState($request, 'sort', []);
185
    }
186
187
    /**
188
     * @param array $sortFields
189
     * @return array
190
     */
191 7
    private function _getDefaultSorting(array $sortFields): array
192
    {
193 7
        $sorting = [];
194 7
        foreach ($sortFields as $field)
195
        {
196 7
            $sorting[$field] = 'none';
197
        }
198
199 7
        return $sorting;
200
    }
201
202
    /**
203
     * @param $headerAttributeName
204
     * @return bool
205
     */
206 7
    private function _showSortLink(string $headerAttributeName): bool
207
    {
208 7
        return \count($this->_sortableHeaders + $this->_dontSort) === 0 ||
209 7
               (\in_array($headerAttributeName, $this->_sortableHeaders, true) && !\in_array($headerAttributeName, $this->_dontSort, true));
210
    }
211
212
    /**
213
     * @param Request $request
214
     * @param string $columnName
215
     * @param string $oldDirection
216
     * @return string
217
     * @throws \RuntimeException
218
     */
219 6
    private function _buildSortUrl(Request $request, string $columnName, string $oldDirection = 'asc')
220
    {
221 6
        $newDirection = $this->_getNewDirection($oldDirection);
222
223 6
        $newSorting = $columnName . self::SORTING_SEPARATOR . $newDirection;
224 6
        $parameters = UrlHelper::parameterizeQuery($request->getQueryString());
225
226 6
        if (!isset($parameters['sort']))
227
        {
228 6
            $parameters['sort'] = '';
229
        }
230
231 6
        if ($this->_replaceOldSort($columnName, $parameters['sort'], $oldDirection, $newSorting))
232
        {
233
            $this->_addSortParameter($parameters['sort'], $newSorting);
234
        }
235
236 6
        return $request->url() . '?' . \http_build_query($parameters);
237
    }
238
239
    /**
240
     * Get the next sorting direction.
241
     *
242
     * @param string $oldDirection
243
     * @return string
244
     */
245 6
    private function _getNewDirection(string $oldDirection): string
246
    {
247
        switch ($oldDirection)
248
        {
249 6
            case 'asc':
250 2
                $newDirection = 'desc';
251 2
                break;
252 4
            case 'desc':
253
                $newDirection = 'none';
254
                break;
255
            default:
256 4
                $newDirection = 'asc';
257
        }
258
259 6
        return $newDirection;
260
    }
261
262
    /**
263
     * @param string $columnName
264
     * @param string $sortParameter
265
     * @param string $oldDirection
266
     * @param string $newSorting
267
     * @return bool
268
     */
269 6
    private function _replaceOldSort(string $columnName, string &$sortParameter, string $oldDirection, string $newSorting): bool
270
    {
271 6
        $replacedCount = 0;
272 6
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $columnName . self::SORTING_SEPARATOR . $oldDirection . '/';
273 6
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $sortParameter, 1, $replacedCount);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$parameters was never initialized. Although not strictly required by PHP, it is generally a good practice to add $parameters = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
274
275 6
        if (!empty($sortParameter) && $sortParameter[0] === self::COLUMN_SEPARATOR)
276
        {
277
            $sortParameter = \mb_substr($sortParameter, 1);
278
        }
279
280 6
        return $replacedCount > 0;
281
    }
282
283
    /**
284
     * @param string $sortParameter
285
     * @param string $newSorting
286
     */
287
    private function _addSortParameter(string &$sortParameter, string $newSorting)
288
    {
289
        if (!empty($sortParameter))
290
        {
291
            $newSorting = self::COLUMN_SEPARATOR . $newSorting;
1 ignored issue
show
Coding Style introduced by
Consider using a different name than the parameter $newSorting. This often makes code more readable.
Loading history...
292
        }
293
        $sortParameter .= $newSorting;
294
    }
295
}