Completed
Push — master ( 29feb0...d9398e )
by Timo
07:22
created

SortableHeader::_getNewDirection()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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