1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* This file is part of the Grido (http://grido.bugyik.cz) |
5
|
|
|
* |
6
|
|
|
* Copyright (c) 2011 Petr Bugyík (http://petr.bugyik.cz) |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view |
9
|
|
|
* the file LICENSE.md that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Grido\DataSources; |
13
|
|
|
|
14
|
|
|
use Grido\Exception; |
15
|
|
|
use Grido\Components\Filters\Condition; |
16
|
|
|
|
17
|
|
|
use Nette\Utils\Strings; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Array data source. |
21
|
|
|
* |
22
|
|
|
* @package Grido |
23
|
|
|
* @subpackage DataSources |
24
|
|
|
* @author Josef Kříž <[email protected]> |
25
|
|
|
* @author Petr Bugyík |
26
|
|
|
* |
27
|
|
|
* @property-read array $data |
28
|
|
|
* @property-read int $count |
29
|
|
|
*/ |
30
|
1 |
|
class ArraySource extends \Nette\Object implements IDataSource |
31
|
|
|
{ |
32
|
|
|
/** @var array */ |
33
|
|
|
protected $data; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @param array $data |
37
|
|
|
*/ |
38
|
|
|
public function __construct(array $data) |
39
|
|
|
{ |
40
|
1 |
|
$this->data = $data; |
41
|
1 |
|
} |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* This method needs tests! |
45
|
|
|
* @param Condition $condition |
46
|
|
|
* @param array $data |
47
|
|
|
* @return array |
48
|
|
|
*/ |
49
|
|
|
protected function makeWhere(Condition $condition, array $data = NULL) |
50
|
|
|
{ |
51
|
|
|
$data = $data === NULL |
52
|
1 |
|
? $this->data |
53
|
1 |
|
: $data; |
54
|
|
|
|
55
|
1 |
|
return array_filter($data, function ($row) use ($condition) { |
56
|
1 |
|
if ($condition->callback) { |
57
|
1 |
|
return call_user_func_array($condition->callback, [$condition->value, $row]); |
58
|
1 |
|
} |
59
|
|
|
|
60
|
1 |
|
$i = 0; |
61
|
1 |
|
$results = []; |
62
|
1 |
|
foreach ($condition->column as $column) { |
63
|
1 |
|
if (Condition::isOperator($column)) { |
64
|
1 |
|
$results[] = " $column "; |
65
|
|
|
|
66
|
1 |
|
} else { |
67
|
1 |
|
$i = count($condition->condition) > 1 ? $i : 0; |
68
|
1 |
|
$results[] = (int) $this->compare( |
69
|
1 |
|
$row[$column], |
70
|
1 |
|
$condition->condition[$i], |
71
|
1 |
|
isset($condition->value[$i]) ? $condition->value[$i] : NULL |
72
|
1 |
|
); |
73
|
|
|
|
74
|
1 |
|
$i++; |
75
|
|
|
} |
76
|
1 |
|
} |
77
|
|
|
|
78
|
1 |
|
$result = implode('', $results); |
79
|
1 |
|
return count($condition->column) === 1 |
80
|
1 |
|
? (bool) $result |
81
|
1 |
|
: eval("return $result;"); // QUESTION: How to remove this eval? hmmm? |
|
|
|
|
82
|
1 |
|
}); |
83
|
|
|
} |
84
|
|
|
|
85
|
|
|
/** |
86
|
|
|
* @param string $actual |
87
|
|
|
* @param string $condition |
88
|
|
|
* @param mixed $expected |
89
|
|
|
* @throws Exception |
90
|
|
|
* @return bool |
91
|
|
|
*/ |
92
|
|
|
public function compare($actual, $condition, $expected) |
93
|
|
|
{ |
94
|
1 |
|
$expected = (array) $expected; |
95
|
1 |
|
$expected = current($expected); |
96
|
1 |
|
$cond = str_replace(' ?', '', $condition); |
97
|
|
|
|
98
|
1 |
|
if ($cond === 'LIKE') { |
99
|
1 |
|
$actual = Strings::toAscii($actual); |
100
|
1 |
|
$expected = Strings::toAscii($expected); |
101
|
|
|
|
102
|
1 |
|
$pattern = str_replace('%', '(.|\s)*', preg_quote($expected, '/')); |
103
|
1 |
|
return (bool) preg_match("/^{$pattern}$/i", $actual); |
104
|
|
|
|
105
|
1 |
|
} elseif ($cond === '=') { |
106
|
1 |
|
return $actual == $expected; |
107
|
|
|
|
108
|
1 |
|
} elseif ($cond === '<>') { |
109
|
1 |
|
return $actual != $expected; |
110
|
|
|
|
111
|
1 |
|
} elseif ($cond === 'IS NULL') { |
112
|
1 |
|
return $actual === NULL; |
113
|
|
|
|
114
|
1 |
|
} elseif ($cond === 'IS NOT NULL') { |
115
|
1 |
|
return $actual !== NULL; |
116
|
|
|
|
117
|
1 |
|
} elseif ($cond === '<') { |
118
|
1 |
|
return (int) $actual < $expected; |
119
|
|
|
|
120
|
1 |
|
} elseif ($cond === '<=') { |
121
|
1 |
|
return (int) $actual <= $expected; |
122
|
|
|
|
123
|
1 |
|
} elseif ($cond === '>') { |
124
|
1 |
|
return (int) $actual > $expected; |
125
|
|
|
|
126
|
1 |
|
} elseif ($cond === '>=') { |
127
|
1 |
|
return (int) $actual >= $expected; |
128
|
|
|
|
129
|
|
|
} else { |
130
|
1 |
|
throw new Exception("Condition '$condition' not implemented yet."); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/*********************************** interface IDataSource ************************************/ |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* @return int |
138
|
|
|
*/ |
139
|
|
|
public function getCount() |
140
|
|
|
{ |
141
|
1 |
|
return count($this->data); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* @return array |
146
|
|
|
*/ |
147
|
|
|
public function getData() |
148
|
|
|
{ |
149
|
1 |
|
return $this->data; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @param array $conditions |
154
|
|
|
*/ |
155
|
|
|
public function filter(array $conditions) |
156
|
|
|
{ |
157
|
1 |
|
foreach ($conditions as $condition) { |
158
|
1 |
|
$this->data = $this->makeWhere($condition); |
159
|
1 |
|
} |
160
|
1 |
|
} |
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* @param int $offset |
164
|
|
|
* @param int $limit |
165
|
|
|
*/ |
166
|
|
|
public function limit($offset, $limit) |
167
|
|
|
{ |
168
|
1 |
|
$this->data = array_slice($this->data, $offset, $limit); |
169
|
1 |
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @param array $sorting |
173
|
|
|
* @throws Exception |
174
|
|
|
*/ |
175
|
|
|
public function sort(array $sorting) |
176
|
|
|
{ |
177
|
1 |
|
if (count($sorting) > 1) { |
178
|
|
|
throw new Exception('Multi-column sorting is not implemented yet.'); |
179
|
|
|
} |
180
|
|
|
|
181
|
1 |
|
foreach ($sorting as $column => $sort) { |
182
|
1 |
|
$data = []; |
183
|
1 |
|
foreach ($this->data as $item) { |
184
|
1 |
|
$sorter = (string) $item[$column]; |
185
|
1 |
|
$data[$sorter][] = $item; |
186
|
1 |
|
} |
187
|
|
|
|
188
|
1 |
|
if ($sort === 'ASC') { |
189
|
1 |
|
ksort($data); |
190
|
1 |
|
} else { |
191
|
1 |
|
krsort($data); |
192
|
|
|
} |
193
|
|
|
|
194
|
1 |
|
$this->data = []; |
195
|
1 |
|
foreach ($data as $i) { |
196
|
1 |
|
foreach ($i as $item) { |
197
|
1 |
|
$this->data[] = $item; |
198
|
1 |
|
} |
199
|
1 |
|
} |
200
|
1 |
|
} |
201
|
1 |
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* @param mixed $column |
205
|
|
|
* @param array $conditions |
206
|
|
|
* @param int $limit |
207
|
|
|
* @return array |
208
|
|
|
* @throws Exception |
209
|
|
|
*/ |
210
|
|
|
public function suggest($column, array $conditions, $limit) |
211
|
|
|
{ |
212
|
1 |
|
$data = $this->data; |
213
|
1 |
|
foreach ($conditions as $condition) { |
214
|
1 |
|
$data = $this->makeWhere($condition, $data); |
215
|
1 |
|
} |
216
|
|
|
|
217
|
1 |
|
array_slice($data, 1, $limit); |
218
|
|
|
|
219
|
1 |
|
$items = []; |
220
|
1 |
|
foreach ($data as $row) { |
221
|
1 |
|
if (is_string($column)) { |
222
|
1 |
|
$value = (string) $row[$column]; |
223
|
1 |
|
} elseif (is_callable($column)) { |
224
|
|
|
$value = (string) $column($row); |
225
|
|
|
} else { |
226
|
|
|
$type = gettype($column); |
227
|
|
|
throw new Exception("Column of suggestion must be string or callback, $type given."); |
228
|
|
|
} |
229
|
|
|
|
230
|
1 |
|
$items[$value] = \Latte\Runtime\Filters::escapeHtml($value); |
231
|
1 |
|
} |
232
|
|
|
|
233
|
1 |
|
sort($items); |
234
|
1 |
|
return array_values($items); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
On one hand,
eval
might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM,eval
prevents some optimization that they perform.