Completed
Push — master ( de6c7f...a35e5d )
by Timo
04:14
created

SortableHeader::_buildSortUrl()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 33
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 6.0585

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 15
cts 17
cp 0.8824
rs 8.439
c 0
b 0
f 0
cc 6
eloc 17
nc 12
nop 3
crap 6.0585
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 $this
54
     */
55 1
    public function sortingSymbols(array $sortingSymbols)
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 $this
67
     */
68 1
    public function makeSortable(string $header)
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 $this
94
     */
95 1
    public function dontSort(string $header)
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->getOriginalName();
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)
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 $column
215
     * @param string $oldDirection
216
     * @return string
217
     * @throws \RuntimeException
218
     */
219 6
    private function _buildSortUrl(Request $request, string $column, string $oldDirection = 'asc')
220
    {
221 6
        $newDirection = $this->_getNewDirection($oldDirection);
222
223 6
        $newSorting = $column . 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
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $column . self::SORTING_SEPARATOR . $oldDirection . '/';
232 6
        $replacedCount = 0;
233 6
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $parameters['sort'], 1, $replacedCount);
234
235 6
        if (!empty($parameters['sort']) && $parameters['sort'][0] === self::COLUMN_SEPARATOR)
236
        {
237
            $parameters['sort'] = \mb_substr($parameters['sort'], 1);
238
        }
239
240 6
        if ($replacedCount === 0)
241
        {
242 6
            $sorting = $newSorting;
243 6
            if (!empty($parameters['sort']))
244
            {
245
                $sorting = self::COLUMN_SEPARATOR . $sorting;
246
            }
247 6
            $parameters['sort'] .= $sorting;
248
        }
249
250 6
        return $request->url() . '?' . \http_build_query($parameters);
251
    }
252
253
    /**
254
     * Get the next sorting direction.
255
     *
256
     * @param string $oldDirection
257
     * @return string
258
     */
259 6
    private function _getNewDirection(string $oldDirection): string
260
    {
261
        switch ($oldDirection)
262
        {
263 6
            case 'asc':
264 2
                $newDirection = 'desc';
265 2
                break;
266 5
            case 'desc':
267 1
                $newDirection = 'none';
268 1
                break;
269
            default:
270 4
                $newDirection = 'asc';
271
        }
272
273 6
        return $newDirection;
274
    }
275
}