1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @copyright Copyright (c) 2015 ublaboo <[email protected]> |
5
|
|
|
* @author Pavel Janda <[email protected]> |
6
|
|
|
* @package Ublaboo |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace Ublaboo\DataGrid\DataSource; |
10
|
|
|
|
11
|
|
|
use Nette\Utils\Strings; |
12
|
|
|
use Ublaboo\DataGrid\Exception\DataGridArrayDataSourceException; |
13
|
|
|
use Ublaboo\DataGrid\Exception\DataGridDateTimeHelperException; |
14
|
|
|
use Ublaboo\DataGrid\Filter\Filter; |
15
|
|
|
use Ublaboo\DataGrid\Filter\FilterDate; |
16
|
|
|
use Ublaboo\DataGrid\Filter\FilterDateRange; |
17
|
|
|
use Ublaboo\DataGrid\Filter\FilterMultiSelect; |
18
|
|
|
use Ublaboo\DataGrid\Filter\FilterRange; |
19
|
|
|
use Ublaboo\DataGrid\Filter\FilterText; |
20
|
|
|
use Ublaboo\DataGrid\Utils\DateTimeHelper; |
21
|
|
|
use Ublaboo\DataGrid\Utils\Sorting; |
22
|
|
|
|
23
|
1 |
|
class ArrayDataSource implements IDataSource |
24
|
|
|
{ |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @var array |
28
|
|
|
*/ |
29
|
|
|
protected $data = []; |
30
|
|
|
|
31
|
|
|
/** |
32
|
|
|
* @var int |
33
|
|
|
*/ |
34
|
|
|
protected $count = 0; |
35
|
|
|
|
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @param array $data_source |
39
|
|
|
*/ |
40
|
|
|
public function __construct(array $data_source) |
41
|
|
|
{ |
42
|
1 |
|
$this->setData($data_source); |
43
|
1 |
|
} |
44
|
|
|
|
45
|
|
|
|
46
|
|
|
/******************************************************************************** |
47
|
|
|
* IDataSource implementation * |
48
|
|
|
********************************************************************************/ |
49
|
|
|
|
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Get count of data |
53
|
|
|
* @return int |
54
|
|
|
*/ |
55
|
|
|
public function getCount() |
56
|
|
|
{ |
57
|
1 |
|
return sizeof($this->data); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* Get the data |
63
|
|
|
* @return array |
64
|
|
|
*/ |
65
|
|
|
public function getData() |
66
|
|
|
{ |
67
|
1 |
|
return $this->data; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
/** |
72
|
|
|
* Set the data |
73
|
|
|
* @param array $data_source |
74
|
|
|
* @return static |
75
|
|
|
*/ |
76
|
|
|
private function setData(array $data_source) |
77
|
|
|
{ |
78
|
1 |
|
$this->data = $data_source; |
79
|
|
|
|
80
|
1 |
|
return $this; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Filter data |
86
|
|
|
* @param Filter[] $filters |
87
|
|
|
* @return static |
88
|
|
|
*/ |
89
|
|
|
public function filter(array $filters) |
90
|
|
|
{ |
91
|
1 |
|
foreach ($filters as $filter) { |
92
|
1 |
|
if ($filter->isValueSet()) { |
93
|
1 |
|
if ($filter->hasConditionCallback()) { |
94
|
|
|
$data = (array) call_user_func_array( |
95
|
|
|
$filter->getConditionCallback(), |
96
|
|
|
[$this->data, $filter->getValue()] |
97
|
|
|
); |
98
|
|
|
$this->setData($data); |
99
|
|
|
} else { |
100
|
1 |
|
$data = array_filter($this->data, function ($row) use ($filter) { |
101
|
1 |
|
return $this->applyFilter($row, $filter); |
102
|
1 |
|
}); |
103
|
1 |
|
$this->setData($data); |
104
|
|
|
} |
105
|
|
|
} |
106
|
|
|
} |
107
|
|
|
|
108
|
1 |
|
return $this; |
109
|
|
|
} |
110
|
|
|
|
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* Filter data - get one row |
114
|
|
|
* @param array $condition |
115
|
|
|
* @return ArrayDataSource |
116
|
|
|
*/ |
117
|
|
|
public function filterOne(array $condition) |
118
|
|
|
{ |
119
|
1 |
|
foreach ($this->data as $item) { |
120
|
1 |
|
foreach ($condition as $key => $value) { |
121
|
1 |
|
if ($item[$key] == $value) { |
122
|
1 |
|
$this->setData([$item]); |
123
|
|
|
|
124
|
1 |
|
return $this; |
125
|
|
|
} |
126
|
|
|
} |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$this->setData([]); |
130
|
|
|
|
131
|
|
|
return $this; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Apply limit and offset on data |
137
|
|
|
* @param int $offset |
138
|
|
|
* @param int $limit |
139
|
|
|
* @return static |
140
|
|
|
*/ |
141
|
|
|
public function limit($offset, $limit) |
142
|
|
|
{ |
143
|
1 |
|
$data = array_slice($this->data, $offset, $limit); |
144
|
1 |
|
$this->setData($data); |
145
|
|
|
|
146
|
1 |
|
return $this; |
147
|
|
|
} |
148
|
|
|
|
149
|
|
|
|
150
|
|
|
/** |
151
|
|
|
* Apply fitler and tell whether row passes conditions or not |
152
|
|
|
* @param mixed $row |
153
|
|
|
* @param Filter $filter |
154
|
|
|
* @return mixed |
155
|
|
|
*/ |
156
|
|
|
protected function applyFilter($row, Filter $filter) |
157
|
|
|
{ |
158
|
1 |
|
if (is_array($row) || $row instanceof \Traversable) { |
159
|
1 |
|
if ($filter instanceof FilterDate) { |
160
|
|
|
return $this->applyFilterDate($row, $filter); |
161
|
1 |
|
} elseif ($filter instanceof FilterMultiSelect) { |
162
|
|
|
return $this->applyFilterMultiSelect($row, $filter); |
163
|
1 |
|
} elseif ($filter instanceof FilterDateRange) { |
164
|
|
|
return $this->applyFilterDateRange($row, $filter); |
165
|
1 |
|
} elseif ($filter instanceof FilterRange) { |
166
|
1 |
|
return $this->applyFilterRange($row, $filter); |
167
|
|
|
} |
168
|
|
|
|
169
|
1 |
|
$condition = $filter->getCondition(); |
170
|
|
|
|
171
|
1 |
|
foreach ($condition as $column => $value) { |
172
|
1 |
|
if ($filter instanceof FilterText && $filter->isExactSearch()) { |
173
|
1 |
|
return $row[$column] == $value; |
174
|
|
|
} |
175
|
|
|
|
176
|
1 |
View Code Duplication |
if ($filter instanceof FilterText && $filter->hasSplitWordsSearch() === false) { |
|
|
|
|
177
|
1 |
|
$words = [$value]; |
178
|
|
|
} else { |
179
|
1 |
|
$words = explode(' ', $value); |
180
|
|
|
} |
181
|
|
|
|
182
|
1 |
|
$row_value = strtolower(Strings::toAscii($row[$column])); |
183
|
|
|
|
184
|
1 |
|
foreach ($words as $word) { |
185
|
1 |
|
if (strpos($row_value, strtolower(Strings::toAscii($word))) !== false) { |
186
|
1 |
|
return $row; |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
1 |
|
return false; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
|
196
|
|
|
/** |
197
|
|
|
* Filter by multi select value |
198
|
|
|
* @param mixed $row |
199
|
|
|
* @param FilterMultiSelect $filter |
200
|
|
|
* @return void |
201
|
|
|
*/ |
202
|
|
|
public function applyFilterMultiSelect($row, FilterMultiSelect $filter) |
203
|
|
|
{ |
204
|
|
|
$condition = $filter->getCondition(); |
205
|
|
|
$values = $condition[$filter->getColumn()]; |
206
|
|
|
|
207
|
|
|
return in_array($row[$filter->getColumn()], $values, true); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
|
211
|
|
|
/** |
212
|
|
|
* @param mixed $row |
213
|
|
|
* @param FilterRange $filter |
214
|
|
|
* @return void |
215
|
|
|
*/ |
216
|
|
|
public function applyFilterRange($row, FilterRange $filter) |
217
|
|
|
{ |
218
|
1 |
|
$condition = $filter->getCondition(); |
219
|
1 |
|
$values = $condition[$filter->getColumn()]; |
220
|
|
|
|
221
|
1 |
View Code Duplication |
if ($values['from'] !== null && $values['from'] !== '') { |
|
|
|
|
222
|
1 |
|
if ($values['from'] > $row[$filter->getColumn()]) { |
223
|
1 |
|
return false; |
224
|
|
|
} |
225
|
|
|
} |
226
|
|
|
|
227
|
1 |
View Code Duplication |
if ($values['to'] !== null && $values['to'] !== '') { |
|
|
|
|
228
|
1 |
|
if ($values['to'] < $row[$filter->getColumn()]) { |
229
|
1 |
|
return false; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
233
|
1 |
|
return true; |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
|
237
|
|
|
/** |
238
|
|
|
* @param mixed $row |
239
|
|
|
* @param FilterDateRange $filter |
240
|
|
|
* @return void |
241
|
|
|
*/ |
242
|
|
|
public function applyFilterDateRange($row, FilterDateRange $filter) |
243
|
|
|
{ |
244
|
|
|
$format = $filter->getPhpFormat(); |
245
|
|
|
$condition = $filter->getCondition(); |
246
|
|
|
$values = $condition[$filter->getColumn()]; |
247
|
|
|
$row_value = $row[$filter->getColumn()]; |
248
|
|
|
|
249
|
|
View Code Duplication |
if ($values['from'] !== null && $values['from'] !== '') { |
|
|
|
|
250
|
|
|
$date_from = DateTimeHelper::tryConvertToDate($values['from'], [$format]); |
251
|
|
|
$date_from->setTime(0, 0, 0); |
252
|
|
|
|
253
|
|
|
if (!($row_value instanceof \DateTime)) { |
254
|
|
|
/** |
255
|
|
|
* Try to convert string to DateTime object |
256
|
|
|
*/ |
257
|
|
|
try { |
258
|
|
|
$row_value = DateTimeHelper::tryConvertToDate($row_value); |
259
|
|
|
} catch (DataGridDateTimeHelperException $e) { |
260
|
|
|
/** |
261
|
|
|
* Otherwise just return raw string |
262
|
|
|
*/ |
263
|
|
|
return false; |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
if ($row_value->getTimeStamp() < $date_from->getTimeStamp()) { |
268
|
|
|
return false; |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
272
|
|
View Code Duplication |
if ($values['to'] !== null && $values['to'] !== '') { |
|
|
|
|
273
|
|
|
$date_to = DateTimeHelper::tryConvertToDate($values['to'], [$format]); |
274
|
|
|
$date_to->setTime(23, 59, 59); |
275
|
|
|
|
276
|
|
|
if (!($row_value instanceof \DateTime)) { |
277
|
|
|
/** |
278
|
|
|
* Try to convert string to DateTime object |
279
|
|
|
*/ |
280
|
|
|
try { |
281
|
|
|
$row_value = DateTimeHelper::tryConvertToDate($row_value); |
282
|
|
|
} catch (DataGridDateTimeHelperException $e) { |
283
|
|
|
/** |
284
|
|
|
* Otherwise just return raw string |
285
|
|
|
*/ |
286
|
|
|
return false; |
287
|
|
|
} |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
if ($row_value->getTimeStamp() > $date_to->getTimeStamp()) { |
291
|
|
|
return false; |
292
|
|
|
} |
293
|
|
|
} |
294
|
|
|
|
295
|
|
|
return true; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
|
299
|
|
|
/** |
300
|
|
|
* Apply fitler date and tell whether row value matches or not |
301
|
|
|
* @param mixed $row |
302
|
|
|
* @param Filter $filter |
303
|
|
|
* @return mixed |
304
|
|
|
*/ |
305
|
|
|
protected function applyFilterDate($row, FilterDate $filter) |
306
|
|
|
{ |
307
|
|
|
$format = $filter->getPhpFormat(); |
308
|
|
|
$condition = $filter->getCondition(); |
309
|
|
|
|
310
|
|
|
foreach ($condition as $column => $value) { |
311
|
|
|
$row_value = $row[$column]; |
312
|
|
|
|
313
|
|
|
$date = DateTimeHelper::tryConvertToDateTime($value, [$format]); |
314
|
|
|
|
315
|
|
|
if (!($row_value instanceof \DateTime)) { |
316
|
|
|
/** |
317
|
|
|
* Try to convert string to DateTime object |
318
|
|
|
*/ |
319
|
|
|
try { |
320
|
|
|
$row_value = DateTimeHelper::tryConvertToDateTime($row_value); |
321
|
|
|
} catch (DataGridDateTimeHelperException $e) { |
322
|
|
|
/** |
323
|
|
|
* Otherwise just return raw string |
324
|
|
|
*/ |
325
|
|
|
return false; |
326
|
|
|
} |
327
|
|
|
} |
328
|
|
|
|
329
|
|
|
return $row_value->format($format) == $date->format($format); |
330
|
|
|
} |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
|
334
|
|
|
/** |
335
|
|
|
* Sort data |
336
|
|
|
* @param Sorting $sorting |
337
|
|
|
* @return static |
338
|
|
|
*/ |
339
|
|
|
public function sort(Sorting $sorting) |
340
|
|
|
{ |
341
|
1 |
|
if (is_callable($sorting->getSortCallback())) { |
342
|
|
|
$data = call_user_func( |
343
|
|
|
$sorting->getSortCallback(), |
344
|
|
|
$this->data, |
345
|
|
|
$sorting->getSort() |
346
|
|
|
); |
347
|
|
|
|
348
|
|
|
if (!is_array($data)) { |
349
|
|
|
throw new DataGridArrayDataSourceException('Sorting callback has to return array'); |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
$this->setData($data); |
353
|
|
|
|
354
|
|
|
return $this; |
355
|
|
|
} |
356
|
|
|
|
357
|
1 |
|
$sort = $sorting->getSort(); |
358
|
|
|
|
359
|
1 |
|
foreach ($sort as $column => $order) { |
360
|
1 |
|
$data = []; |
361
|
|
|
|
362
|
1 |
|
foreach ($this->data as $item) { |
363
|
1 |
|
if (is_object($item[$column]) && $item[$column] instanceof \DateTimeInterface) { |
364
|
|
|
$sort_by = $item[$column]->format('Y-m-d H:i:s'); |
365
|
|
|
} else { |
366
|
1 |
|
$sort_by = (string) $item[$column]; |
367
|
|
|
} |
368
|
1 |
|
$data[$sort_by][] = $item; |
369
|
|
|
} |
370
|
|
|
|
371
|
1 |
|
if ($order === 'ASC') { |
372
|
|
|
ksort($data); |
373
|
|
|
} else { |
374
|
1 |
|
krsort($data); |
375
|
|
|
} |
376
|
|
|
|
377
|
1 |
|
$data_source = []; |
378
|
|
|
|
379
|
1 |
|
foreach ($data as $i) { |
380
|
1 |
|
foreach ($i as $item) { |
381
|
1 |
|
$data_source[] = $item; |
382
|
|
|
} |
383
|
|
|
} |
384
|
|
|
|
385
|
1 |
|
$this->setData($data_source); |
386
|
|
|
} |
387
|
|
|
|
388
|
1 |
|
return $this; |
389
|
|
|
} |
390
|
|
|
} |
391
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.