Completed
Push — master ( ffa818...f511c0 )
by Timo
07:51
created

SortableHeader::_buildSortQuery()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 12
cts 12
cp 1
rs 8.6845
c 0
b 0
f 0
cc 4
eloc 12
nc 8
nop 2
crap 4
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 10
    public function __construct(array $sortableHeaders = [], array $dontSort = [])
45
    {
46 10
        $this->_sortableHeaders = $sortableHeaders;
47 10
        $this->_dontSort = $dontSort;
48
49 10
    }
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 10
    public function format(Header $header, Request $request)
112
    {
113 10
        $headerAttributeName = $header->getAttributeName();
114 10
        $sortFields = $this->_extractSortFields($request);
115 10
        $direction = $sortFields[$headerAttributeName] ?? 'none';
116
117 10
        if ($this->_showSortLink($headerAttributeName))
118
        {
119 9
            $header->name = '<a class="sortable-header" href="' . ($request->url() . '?' . $this->_buildSortQuery($headerAttributeName, $direction)) . '">' .
120 9
                            $header->name . ' <span class="sort-symbol">' . ($this->_sortingSymbols[$direction] ?? '') . '</span></a>';
121
        }
122 10
    }
123
124
    /**
125
     * @param Request $request
126
     * @return array
127
     */
128 10
    private function _extractSortFields(Request $request) : array
129
    {
130 10
        return \array_diff(
131 10
            $this->_getSortFields($request) + $this->_getRememberedState($request) + $this->_getDefaultSorting($this->_sortableHeaders),
132 10
            $this->_getDefaultSorting($this->_dontSort)
133
        );
134
    }
135
136
    /**
137
     * Get the sorted fields from the request.
138
     *
139
     * @param Request $request
140
     * @return array
141
     */
142 10
    private function _getSortFields(Request $request) : array
143
    {
144 10
        $sortFields = $request->get('sort');
145 10
        if ($sortFields === null)
146
        {
147 10
            return [];
148
        }
149
150
        $sorting = [];
151
        foreach (\explode(self::COLUMN_SEPARATOR, $sortFields) as $field)
152
        {
153
            $sortParts = $this->_getSortParts($field);
154
            $sorting[$sortParts[0]] = \mb_strtolower($sortParts[1]);
155
        }
156
157
        return $sorting;
158
    }
159
160
    /**
161
     * Get the name of the field and the sorting direction (default: "asc").
162
     *
163
     * @param string $field
164
     * @return array
165
     */
166
    private function _getSortParts(string $field) : array
167
    {
168
        $sortParts = \explode(self::SORTING_SEPARATOR, $field);
169
        if (\count($sortParts) === 1)
170
        {
171
            $sortParts[1] = $this->_defaultDirection;
172
        }
173
174
        return $sortParts;
175
    }
176
177
    /**
178
     * @param Request $request
179
     * @return array
180
     */
181 10
    private function _getRememberedState(Request $request) : array
182
    {
183 10
        return SessionHelper::getState($request, 'sort', []);
184
    }
185
186
    /**
187
     * @param array $sortFields
188
     * @return array
189
     */
190 10
    private function _getDefaultSorting(array $sortFields) : array
191
    {
192 10
        $sorting = [];
193 10
        foreach ($sortFields as $field)
194
        {
195 10
            $sorting[$field] = 'none';
196
        }
197
198 10
        return $sorting;
199
    }
200
201
    /**
202
     * @param $headerAttributeName
203
     * @return bool
204
     */
205 10
    private function _showSortLink(string $headerAttributeName) : bool
206
    {
207 10
        return \count($this->_sortableHeaders + $this->_dontSort) === 0 ||
208 10
               (\in_array($headerAttributeName, $this->_sortableHeaders, true) && !\in_array($headerAttributeName, $this->_dontSort, true));
209
    }
210
211
    /**
212
     * @param string $columnName
213
     * @param string $oldDirection
214
     * @return string
215
     * @throws \RuntimeException
216
     */
217 9
    private function _buildSortQuery(string $columnName, string &$oldDirection)
218
    {
219 9
        $parameters = UrlHelper::queryParameters();
220 9
        if (!isset($parameters['sort']))
221
        {
222 4
            $parameters['sort'] = '';
223
        }
224
225 9
        $queryDirection = $this->_getDirectionFromQuery($columnName, $parameters['sort']);
226 9
        if ($queryDirection !== null)
227
        {
228
            /** @var string $queryDirection */
229 4
            $oldDirection = $queryDirection;
230
        }
231
232 9
        $newDirection = $this->_getNewDirection($oldDirection);
233 9
        $newSorting = $columnName . self::SORTING_SEPARATOR . $newDirection;
234 9
        if (!$this->_replaceOldSort($columnName, $parameters, $oldDirection, $newSorting))
235
        {
236 4
            $this->_addSortParameter($parameters, $newSorting);
237
        }
238
239 9
        return \http_build_query($parameters);
240
    }
241
242
    /**
243
     * @param string $columnName
244
     * @param string $queryString
245
     * @return null|string
246
     */
247 9
    private function _getDirectionFromQuery(string $columnName, string $queryString) : ?string
248
    {
249 9
        $column = $columnName . self::SORTING_SEPARATOR;
250 9
        $columnPos = \mb_strpos($queryString, $column);
251
252 9
        if ($columnPos === false)
253
        {
254 5
            return null;
255
        }
256
257 4
        $sortValue = \mb_substr($queryString, $columnPos + \mb_strlen($column));
258 4
        $dividerPos = \mb_strpos($sortValue, self::COLUMN_SEPARATOR);
259 4
        if ($dividerPos !== false)
260
        {
261 1
            $sortValue = \mb_substr($sortValue, 0, $dividerPos);
262
        }
263
264 4
        return $sortValue;
265
    }
266
267
    /**
268
     * Get the next sorting direction.
269
     *
270
     * @param string $oldDirection
271
     * @return string
272
     */
273 9
    private function _getNewDirection(string $oldDirection) : string
274
    {
275
        switch ($oldDirection)
276
        {
277 9
            case 'asc':
278 2
                $newDirection = 'desc';
279 2
                break;
280 8
            case 'desc':
281 3
                $newDirection = 'none';
282 3
                break;
283
            default:
284 5
                $newDirection = 'asc';
285
        }
286
287 9
        return $newDirection;
288
    }
289
290
    /**
291
     * @param string $columnName
292
     * @param array $parameters
293
     * @param string $oldDirection
294
     * @param string $newSorting
295
     * @return bool
296
     */
297 9
    private function _replaceOldSort(string $columnName, array &$parameters, string $oldDirection, string $newSorting) : bool
298
    {
299 9
        $replacedCount = 0;
300 9
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $columnName . '(' . self::SORTING_SEPARATOR . $oldDirection . '|)/';
301 9
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $parameters['sort'], 1, $replacedCount);
302 9
        if (!empty($parameters['sort']) && $parameters['sort'][0] === self::COLUMN_SEPARATOR)
303
        {
304 5
            $parameters['sort'] = \mb_substr($parameters['sort'], 1);
305
        }
306
307 9
        return $replacedCount > 0;
308
    }
309
310
    /**
311
     * @param array $parameters
312
     * @param string $newSorting
313
     */
314 4
    private function _addSortParameter(array &$parameters, string $newSorting)
315
    {
316 4
        if (!empty($parameters['sort']))
317
        {
318
            $newSorting = self::COLUMN_SEPARATOR . $newSorting;
319
        }
320 4
        $parameters['sort'] .= $newSorting;
321
    }
322
}