Completed
Push — master ( c40d9b...97e8fc )
by Timo
03:28
created

SortableHeader::_extractSortFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
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 array */
27
    private $_sortableHeaders;
28
29
    /** @var array */
30
    private $_dontSort;
31
32
    /** @var array */
33
    private $_sortingSymbols = ['asc' => '&#x25B2;', 'desc' => '&#x25BC;', 'none' => '⇵'];
34
35
    /**
36
     * SortableHeader constructor.
37
     *
38
     * @param array $sortableHeaders
39
     * @param array $dontSort
40
     */
41
    public function __construct(array $sortableHeaders = [], array $dontSort = [])
42
    {
43
        $this->_sortableHeaders = $sortableHeaders;
44
        $this->_dontSort = $dontSort;
45
46
    }
47
48
    /**
49
     * @param array $sortingSymbols
50
     * @return $this
51
     */
52
    public function sortingSymbols(array $sortingSymbols)
53
    {
54
        $this->_sortingSymbols = $sortingSymbols;
55
56
        return $this;
57
    }
58
59
    /**
60
     * @param array $array
61
     * @param string $key
62
     */
63
    private function _removeIndex(array $array, string $key)
64
    {
65
        $index = \array_search($key, $array, true);
66
        if ($index !== false)
67
        {
68
            unset($array[$index]);
69
        }
70
    }
71
72
    /**
73
     * Add a field to the sortable fields.
74
     *
75
     * @param string $header
76
     * @return $this
77
     */
78
    public function makeSortable(string $header)
79
    {
80
        $this->_sortableHeaders[] = $header;
81
        $this->_removeIndex($this->_dontSort, $header);
82
83
        return $this;
84
    }
85
86
    /**
87
     * Remove the ability to sort by this column/header.
88
     *
89
     * @param string $header
90
     * @return $this
91
     */
92
    public function dontSort(string $header)
93
    {
94
        $this->_dontSort[] = $header;
95
        $this->_removeIndex($this->_sortableHeaders, $header);
96
97
        return $this;
98
    }
99
100
    /**
101
     * @param Request $request
102
     * @return array
103
     */
104
    private function _getRememberedState(Request $request): array
105
    {
106
        return SessionHelper::getState($request, 'sort', []);
107
    }
108
109
    /**
110
     * Get the sorted fields from the request.
111
     *
112
     * @param Request $request
113
     * @return array
114
     */
115
    private function _getSortFields(Request $request): array
116
    {
117
        $sortFields = $request->get('sort');
118
        if ($sortFields === null)
119
        {
120
            return [];
121
        }
122
123
        $sorting = [];
124
        foreach (\explode(self::COLUMN_SEPARATOR, $sortFields) as $field)
125
        {
126
            $sortParts = \explode(self::SORTING_SEPARATOR, $field);
127
            if (\count($sortParts) === 1)
128
            {
129
                $sortParts[1] = 'asc';
130
            }
131
132
            $sorting[$sortParts[0]] = \mb_strtolower($sortParts[1]);
133
        }
134
135
        return $sorting;
136
    }
137
138
    /**
139
     * @param Request $request
140
     * @return array
141
     */
142
    private function _extractSortFields(Request $request)
143
    {
144
        return \array_diff($this->_sortableHeaders + $this->_getRememberedState($request) + $this->_getSortFields($request), $this->_dontSort);
145
    }
146
147
    /**
148
     * Get the next sorting direction.
149
     *
150
     * @param string $oldDirection
151
     * @return string
152
     */
153
    private function _getNewDirection(string $oldDirection): string
154
    {
155
        switch ($oldDirection)
156
        {
157
            case 'asc':
158
                $newDirection = 'desc';
159
                break;
160
            case 'desc':
161
                $newDirection = 'none';
162
                break;
163
            default:
164
                $newDirection = 'asc';
165
        }
166
167
        return $newDirection;
168
    }
169
170
    /**
171
     * @param Request $request
172
     * @param string $column
173
     * @param string $oldDirection
174
     * @return string
175
     * @throws \RuntimeException
176
     */
177
    private function _buildSortUrl(Request $request, string $column, string $oldDirection = 'asc')
178
    {
179
        $newDirection = $this->_getNewDirection($oldDirection);
180
181
        $newSorting = $column . self::SORTING_SEPARATOR . $newDirection;
182
        $parameters = UrlHelper::parameterizeQuery($request->getQueryString());
183
184
        if (!isset($parameters['sort']))
185
        {
186
            $parameters['sort'] = '';
187
        }
188
189
        $columnRegex = '/(^|\\' . self::COLUMN_SEPARATOR . ')' . $column . self::SORTING_SEPARATOR . $oldDirection . '/';
190
        $replacedCount = 0;
191
        $parameters['sort'] = \preg_replace($columnRegex, self::COLUMN_SEPARATOR . $newSorting, $parameters['sort'], 1, $replacedCount);
192
193
        if (!empty($parameters['sort']) && $parameters['sort'][0] === self::COLUMN_SEPARATOR)
194
        {
195
            $parameters['sort'] = \mb_substr($parameters['sort'], 1);
196
        }
197
198
        if ($replacedCount === 0)
199
        {
200
            $sorting = $newSorting;
201
            if (!empty($parameters['sort']))
202
            {
203
                $sorting = self::COLUMN_SEPARATOR . $sorting;
204
            }
205
            $parameters['sort'] .= $sorting;
206
        }
207
208
        return $request->url() . '?' . \http_build_query($parameters);
209
    }
210
211
    /**
212
     * @param $headerAttributeName
213
     * @return bool
214
     */
215
    private function _showSortLink(string $headerAttributeName): bool
216
    {
217
        return \count($this->_sortableHeaders + $this->_dontSort) === 0 ||
218
               (\in_array($headerAttributeName, $this->_sortableHeaders, true) && !\in_array($headerAttributeName, $this->_dontSort, true));
219
    }
220
221
    /**
222
     * Adds a link to sort by this header/column.
223
     * Also indicates how the columns are sorted (when sorted).
224
     *
225
     * @param Header $header
226
     * @param Request $request
227
     * @throws \RuntimeException
228
     */
229
    public function format(Header $header, Request $request)
230
    {
231
        $headerAttributeName = $header->getOriginalName();
232
233
        $sortFields = $this->_extractSortFields($request);
234
        $direction = $sortFields[$headerAttributeName] ?? 'none';
235
236
        if ($this->_showSortLink($headerAttributeName))
237
        {
238
            $header->name = '<a class="sortable-header" href="' . $this->_buildSortUrl($request, $headerAttributeName, $direction) . '">' . $header->name .
239
                            ' <span class="sort-symbol">' . ($this->_sortingSymbols[$direction] ?? '') . '</span></a>';
240
        }
241
    }
242
}