SortableHeader::_replaceOldSort()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 3

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 6
c 2
b 0
f 0
nc 2
nop 4
dl 0
loc 11
ccs 7
cts 7
cp 1
crap 3
rs 10
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
10
/**
11
 * Class SortableHeader
12
 *
13
 * Either whitelist the headers you want to make sortable
14
 * or blacklist the headers you do not want to become sortable.
15
 *
16
 * If nothing is specified, all headers are sortable.
17
 *
18
 * @package hamburgscleanest\DataTables\Models\HeaderFormatters
19
 */
20
class SortableHeader implements HeaderFormatter {
21
22
    const SORTING_SEPARATOR = '~';
23
    const COLUMN_SEPARATOR  = '.';
24
25
    /** @var array */
26
    private $_sortableHeaders;
27
28
    /** @var array */
29
    private $_dontSort;
30
31
    /** @var array */
32
    private $_sortingSymbols = ['asc' => '&#x25B2;', 'desc' => '&#x25BC;', 'none' => '⇵'];
33
34
    /**
35
     * SortableHeader constructor.
36
     *
37
     * @param array $sortableHeaders
38
     * @param array $dontSort
39
     */
40 11
    public function __construct(array $sortableHeaders = [], array $dontSort = [])
41
    {
42 11
        $this->_sortableHeaders = $sortableHeaders;
43 11
        $this->_dontSort = $dontSort;
44
45 11
    }
46
47
    /**
48
     * @param array $sortingSymbols
49
     * @return SortableHeader
50
     */
51 1
    public function sortingSymbols(array $sortingSymbols) : SortableHeader
52
    {
53 1
        $this->_sortingSymbols = \array_merge($this->_sortingSymbols, $sortingSymbols);
54
55 1
        return $this;
56
    }
57
58
    /**
59
     * Add a field to the sortable fields.
60
     *
61
     * @param string $header
62
     * @return SortableHeader
63
     */
64 1
    public function makeSortable(string $header) : SortableHeader
65
    {
66 1
        $this->_sortableHeaders[] = $header;
67 1
        $this->_removeIndex($this->_dontSort, $header);
68
69 1
        return $this;
70
    }
71
72
    /**
73
     * @param array $array
74
     * @param string $key
75
     */
76 2
    private function _removeIndex(array $array, string $key) : void
77
    {
78 2
        $index = \array_search($key, $array, true);
79 2
        if ($index !== false)
80
        {
81 1
            unset($array[$index]);
82
        }
83 2
    }
84
85
    /**
86
     * Remove the ability to sort by this column/header.
87
     *
88
     * @param string $header
89
     * @return SortableHeader
90
     */
91 1
    public function dontSort(string $header) : SortableHeader
92
    {
93 1
        $this->_dontSort[] = $header;
94 1
        $this->_removeIndex($this->_sortableHeaders, $header);
95
96 1
        return $this;
97
    }
98
99
    /**
100
     * Adds a link to sort by this header/column.
101
     * Also indicates how the columns are sorted (when sorted).
102
     *
103
     * @param Header $header
104
     * @throws \RuntimeException
105
     */
106 11
    public function format(Header $header) : void
107
    {
108 11
        $headerAttributeName = $header->getAttributeName();
109 11
        $sortFields = $this->_extractSortFields();
110 11
        $direction = $sortFields[$headerAttributeName] ?? 'none';
111
112 11
        if ($this->_showSortLink($headerAttributeName))
113
        {
114 10
            $header->key = '<a class="sortable-header" href="' . (\request()->url() . '?' . $this->_buildSortQuery($headerAttributeName, $direction)) . '">' .
115 10
                           $header->key . ' <span class="sort-symbol">' . $this->_sortingSymbols[$direction] . '</span></a>';
116
        }
117 11
    }
118
119
    /**
120
     * @return array
121
     */
122 11
    private function _extractSortFields() : array
123
    {
124 11
        return \array_diff(
125 11
            $this->_getRememberedState() + $this->_getDefaultSorting($this->_sortableHeaders),
126 11
            $this->_getDefaultSorting($this->_dontSort)
127
        );
128
    }
129
130
    /**
131
     * @return array
132
     */
133 11
    private function _getRememberedState() : array
134
    {
135 11
        return (array) SessionHelper::getState('sort', []);
0 ignored issues
show
Bug introduced by
The method getState() does not exist on hamburgscleanest\DataTables\Facades\SessionHelper. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

135
        return (array) SessionHelper::/** @scrutinizer ignore-call */ getState('sort', []);
Loading history...
136
    }
137
138
    /**
139
     * @param array $sortFields
140
     * @return array
141
     */
142 11
    private function _getDefaultSorting(array $sortFields) : array
143
    {
144 11
        $sorting = [];
145 11
        foreach ($sortFields as $field)
146
        {
147 11
            $sorting[$field] = 'none';
148
        }
149
150 11
        return $sorting;
151
    }
152
153
    /**
154
     * @param $headerAttributeName
155
     * @return bool
156
     */
157 11
    private function _showSortLink(string $headerAttributeName) : bool
158
    {
159 11
        return \count($this->_sortableHeaders + $this->_dontSort) === 0 ||
160 11
               (\in_array($headerAttributeName, $this->_sortableHeaders, true) && !\in_array($headerAttributeName, $this->_dontSort, true));
161
    }
162
163
    /**
164
     * @param string $columnName
165
     * @param string $oldDirection
166
     * @return string
167
     * @throws \RuntimeException
168
     */
169 10
    private function _buildSortQuery(string $columnName, string & $oldDirection) : string
170
    {
171 10
        $parameters = UrlHelper::queryParameters();
0 ignored issues
show
Bug introduced by
The method queryParameters() does not exist on hamburgscleanest\DataTables\Facades\UrlHelper. Since you implemented __callStatic, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

171
        /** @scrutinizer ignore-call */ 
172
        $parameters = UrlHelper::queryParameters();
Loading history...
172 10
        if (!isset($parameters['sort']))
173
        {
174 4
            $parameters['sort'] = '';
175
        }
176
177 10
        $queryDirection = $this->_getDirectionFromQuery($columnName, $parameters['sort']);
178 10
        if ($queryDirection !== null)
179
        {
180
            /** @var string $queryDirection */
181 5
            $oldDirection = $queryDirection;
182
        }
183
184 10
        return \http_build_query($this->_getQueryParameters($parameters, $columnName, $oldDirection));
185
    }
186
187
    /**
188
     * @param string $columnName
189
     * @param string $queryString
190
     * @return null|string
191
     */
192 10
    private function _getDirectionFromQuery(string $columnName, string $queryString) : ? string
193
    {
194 10
        $column = $columnName . self::SORTING_SEPARATOR;
195 10
        $columnPos = \mb_strpos($queryString, $column);
196
197 10
        if ($columnPos === false)
198
        {
199 6
            return null;
200
        }
201
202 5
        $sortValue = \mb_substr($queryString, $columnPos + \mb_strlen($column));
203 5
        $dividerPos = \mb_strpos($sortValue, self::COLUMN_SEPARATOR);
204 5
        if ($dividerPos !== false)
205
        {
206 1
            $sortValue = \mb_substr($sortValue, 0, $dividerPos);
207
        }
208
209 5
        return $sortValue;
210
    }
211
212
    /**
213
     * @param array $parameters
214
     * @param string $columnName
215
     * @param string $oldDirection
216
     * @return array
217
     */
218 10
    private function _getQueryParameters(array $parameters, string $columnName, string $oldDirection) : array
219
    {
220 10
        $newSorting = $columnName . self::SORTING_SEPARATOR . $this->_getNewDirection($oldDirection);
221 10
        if (!$this->_replaceOldSort($columnName, $parameters, $oldDirection, $newSorting))
222
        {
223 5
            $this->_addSortParameter($parameters, $newSorting);
224
        }
225
226 10
        return $parameters;
227
    }
228
229
    /**
230
     * Get the next sorting direction.
231
     *
232
     * @param string $oldDirection
233
     * @return string
234
     */
235 10
    private function _getNewDirection(string $oldDirection) : string
236
    {
237
        switch ($oldDirection)
238
        {
239 10
            case 'asc':
240 2
                $newDirection = 'desc';
241 2
                break;
242 9
            case 'desc':
243 4
                $newDirection = 'none';
244 4
                break;
245
            default:
246 6
                $newDirection = 'asc';
247
        }
248
249 10
        return $newDirection;
250
    }
251
252
    /**
253
     * @param string $columnName
254
     * @param array $parameters
255
     * @param string $oldDirection
256
     * @param string $newSorting
257
     * @return bool
258
     */
259 10
    private function _replaceOldSort(string $columnName, array &$parameters, string $oldDirection, string $newSorting) : bool
260
    {
261 10
        $replacedCount = 0;
262 10
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $columnName . '(' . self::SORTING_SEPARATOR . $oldDirection . '|)/';
263 10
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $parameters['sort'], 1, $replacedCount);
264 10
        if (!empty($parameters['sort']) && $parameters['sort'][0] === self::COLUMN_SEPARATOR)
265
        {
266 6
            $parameters['sort'] = \mb_substr($parameters['sort'], 1);
267
        }
268
269 10
        return $replacedCount > 0;
270
    }
271
272
    /**
273
     * @param array $parameters
274
     * @param string $newSorting
275
     */
276 5
    private function _addSortParameter(array &$parameters, string $newSorting) : void
277
    {
278 5
        if (!empty($parameters['sort']))
279
        {
280 1
            $newSorting = self::COLUMN_SEPARATOR . $newSorting;
281
        }
282 5
        $parameters['sort'] .= $newSorting;
283
    }
284
}