Completed
Push — master ( f511c0...29feb0 )
by Timo
04:42
created

SortableHeader::_getSortParts()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 5
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 6
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
        $newDirection = $this->_getNewDirection($oldDirection);
192 10
        $newSorting = $columnName . self::SORTING_SEPARATOR . $newDirection;
193 10
        if (!$this->_replaceOldSort($columnName, $parameters, $oldDirection, $newSorting))
194
        {
195 5
            $this->_addSortParameter($parameters, $newSorting);
196
        }
197
198 10
        return \http_build_query($parameters);
199
    }
200
201
    /**
202
     * @param string $columnName
203
     * @param string $queryString
204
     * @return null|string
205
     */
206 10
    private function _getDirectionFromQuery(string $columnName, string $queryString) : ?string
207
    {
208 10
        $column = $columnName . self::SORTING_SEPARATOR;
209 10
        $columnPos = \mb_strpos($queryString, $column);
210
211 10
        if ($columnPos === false)
212
        {
213 6
            return null;
214
        }
215
216 5
        $sortValue = \mb_substr($queryString, $columnPos + \mb_strlen($column));
217 5
        $dividerPos = \mb_strpos($sortValue, self::COLUMN_SEPARATOR);
218 5
        if ($dividerPos !== false)
219
        {
220 1
            $sortValue = \mb_substr($sortValue, 0, $dividerPos);
221
        }
222
223 5
        return $sortValue;
224
    }
225
226
    /**
227
     * Get the next sorting direction.
228
     *
229
     * @param string $oldDirection
230
     * @return string
231
     */
232 10
    private function _getNewDirection(string $oldDirection) : string
233
    {
234
        switch ($oldDirection)
235
        {
236 10
            case 'asc':
237 2
                $newDirection = 'desc';
238 2
                break;
239 9
            case 'desc':
240 4
                $newDirection = 'none';
241 4
                break;
242
            default:
243 6
                $newDirection = 'asc';
244
        }
245
246 10
        return $newDirection;
247
    }
248
249
    /**
250
     * @param string $columnName
251
     * @param array $parameters
252
     * @param string $oldDirection
253
     * @param string $newSorting
254
     * @return bool
255
     */
256 10
    private function _replaceOldSort(string $columnName, array &$parameters, string $oldDirection, string $newSorting) : bool
257
    {
258 10
        $replacedCount = 0;
259 10
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $columnName . '(' . self::SORTING_SEPARATOR . $oldDirection . '|)/';
260 10
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $parameters['sort'], 1, $replacedCount);
261 10
        if (!empty($parameters['sort']) && $parameters['sort'][0] === self::COLUMN_SEPARATOR)
262
        {
263 6
            $parameters['sort'] = \mb_substr($parameters['sort'], 1);
264
        }
265
266 10
        return $replacedCount > 0;
267
    }
268
269
    /**
270
     * @param array $parameters
271
     * @param string $newSorting
272
     */
273 5
    private function _addSortParameter(array &$parameters, string $newSorting)
274
    {
275 5
        if (!empty($parameters['sort']))
276
        {
277 1
            $newSorting = self::COLUMN_SEPARATOR . $newSorting;
278
        }
279 5
        $parameters['sort'] .= $newSorting;
280
    }
281
}