1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* @copyright Copyright (c) Flipbox Digital Limited |
5
|
|
|
* @license https://github.com/flipboxfactory/craft-ember/blob/master/LICENSE |
6
|
|
|
* @link https://github.com/flipboxfactory/craft-ember/ |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace flipbox\craft\ember\helpers; |
10
|
|
|
|
11
|
|
|
use craft\helpers\StringHelper; |
12
|
|
|
use yii\db\Query; |
13
|
|
|
use yii\db\QueryInterface; |
14
|
|
|
|
15
|
|
|
/** |
16
|
|
|
* @author Flipbox Factory <[email protected]> |
17
|
|
|
* @since 2.0.0 |
18
|
|
|
*/ |
19
|
|
|
class QueryHelper |
20
|
|
|
{ |
21
|
|
|
/** |
22
|
|
|
* @var array |
23
|
|
|
*/ |
24
|
|
|
protected static $operators = ['not ', '!=', '<=', '>=', '<', '>', '=']; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @param $condition |
28
|
|
|
* @return array |
29
|
|
|
*/ |
30
|
|
|
public static function conditionToCriteria($condition) |
31
|
|
|
{ |
32
|
|
|
if (empty($condition)) { |
33
|
|
|
return $condition; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
// Assume it's an id |
37
|
|
|
if (!is_array($condition)) { |
38
|
|
|
$condition = [ |
39
|
|
|
'id' => $condition |
40
|
|
|
]; |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
return ['where' => ['and', $condition]]; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
/** |
47
|
|
|
* @param QueryInterface|Query $query |
48
|
|
|
* @param array $config |
49
|
|
|
* @return QueryInterface |
50
|
|
|
*/ |
51
|
|
|
public static function configure(QueryInterface $query, $config = []): QueryInterface |
52
|
|
|
{ |
53
|
|
|
// Halt |
54
|
|
|
if (empty($config)) { |
55
|
|
|
return $query; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
// Force array |
59
|
|
|
if (!is_array($config)) { |
60
|
|
|
$config = ArrayHelper::toArray($config, [], false); |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
// Populate query attributes |
64
|
|
|
foreach ($config as $name => $value) { |
65
|
|
|
if ($query->canSetProperty($name)) { |
66
|
|
|
$query->$name = $value; |
67
|
|
|
} |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
return $query; |
71
|
|
|
} |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Prepares a query params input value to be used as a condition. This will attempt to resolve an object |
75
|
|
|
* or look one up based on a 'handle' or other unique string identifier (via the lookup callable). |
76
|
|
|
* |
77
|
|
|
* ```php |
78
|
|
|
* |
79
|
|
|
* [ |
80
|
|
|
* 'and', |
81
|
|
|
* 'not' => [ |
82
|
|
|
* 4, elementFiveHandle, $someElementWithIdOfSix |
83
|
|
|
* ] |
84
|
|
|
* ] |
85
|
|
|
* ``` |
86
|
|
|
* Would result in a query condition as `NOT IN (4, 5, 6)`. |
87
|
|
|
* |
88
|
|
|
* @param $value |
89
|
|
|
* @param callable|null $lookup |
90
|
|
|
* @return array|string |
91
|
|
|
*/ |
92
|
|
|
public static function prepareParam($value, callable $lookup = null) |
93
|
|
|
{ |
94
|
|
|
if (!is_array($value)) { |
95
|
|
|
// An object (model, element, record) |
96
|
|
|
if (is_object($value)) { |
97
|
|
|
// Try to grab the Id from it |
98
|
|
|
try { |
99
|
|
|
return $value->id; |
100
|
|
|
} catch (\Exception $e) { |
|
|
|
|
101
|
|
|
// Carry on |
102
|
|
|
} |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
// alpha identifier (handle, etc) |
106
|
|
|
if (is_string($value) && |
107
|
|
|
!in_array($value, ['and', 'or'], true) && |
108
|
|
|
StringHelper::isAlpha($value) |
109
|
|
|
) { |
110
|
|
|
if (null !== $lookup) { |
111
|
|
|
if (null !== ($val = call_user_func($lookup, $value))) { |
112
|
|
|
return static::prepareParam($val, $lookup); |
113
|
|
|
} |
114
|
|
|
} |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
return $value; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
// Traverse |
121
|
|
|
$return = []; |
122
|
|
|
foreach ($value as $key => $val) { |
123
|
|
|
$return = ArrayHelper::merge( |
124
|
|
|
$return, |
125
|
|
|
static::prepareParamValue($key, $val, $lookup) |
126
|
|
|
); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return $return; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @param $key |
134
|
|
|
* @param $value |
135
|
|
|
* @param callable|null $lookup |
136
|
|
|
* @return array |
137
|
|
|
*/ |
138
|
|
|
protected static function prepareParamValue($key, $value, callable $lookup = null): array |
139
|
|
|
{ |
140
|
|
|
$value = static::prepareParam($value, $lookup); |
141
|
|
|
|
142
|
|
|
// Move arrays up one level |
143
|
|
|
if (is_array($value)) { |
144
|
|
|
$values = []; |
145
|
|
|
|
146
|
|
|
$firstVal = strtolower(reset($value)); |
147
|
|
|
|
148
|
|
|
foreach ($value as $k => $val) { |
149
|
|
|
$values = ArrayHelper::merge( |
150
|
|
|
$values, |
151
|
|
|
static::prepareParamValue( |
152
|
|
|
is_numeric($k) ? $key : $k, |
153
|
|
|
$val, |
154
|
|
|
$lookup |
155
|
|
|
) |
156
|
|
|
); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if (in_array($firstVal, ['and', 'or'], true)) { |
160
|
|
|
return [$values]; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return $values; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
if (!is_numeric($key)) { |
167
|
|
|
if (is_string($value) || is_numeric($value)) { |
168
|
|
|
$value = $key . ' ' . $value; |
169
|
|
|
} |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
return [$value]; |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Standard param parsing. |
178
|
|
|
* |
179
|
|
|
* @param $value |
180
|
|
|
* @param $join |
181
|
|
|
* @return bool |
182
|
|
|
* |
183
|
|
|
* @deprecated |
184
|
|
|
*/ |
185
|
|
|
public static function parseBaseParam(&$value, &$join): bool |
186
|
|
|
{ |
187
|
|
|
// Force array |
188
|
|
|
if (!is_array($value)) { |
189
|
|
|
$value = [$value]; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
// Get join type ('and' , 'or') |
193
|
|
|
$join = self::getJoinType($value, $join); |
|
|
|
|
194
|
|
|
|
195
|
|
|
// Check for object array (via 'id' key) |
196
|
|
|
if ($id = self::findIdFromObjectArray($value)) { |
|
|
|
|
197
|
|
|
$value = [$id]; |
198
|
|
|
return true; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
return false; |
202
|
|
|
} |
203
|
|
|
|
204
|
|
|
/** |
205
|
|
|
* Attempt to resolve a param value by the value. |
206
|
|
|
* Return false if a 'handle' or other string identifier is detected. |
207
|
|
|
* |
208
|
|
|
* @param $value |
209
|
|
|
* @param $operator |
210
|
|
|
* @return bool |
211
|
|
|
* |
212
|
|
|
* @deprecated |
213
|
|
|
*/ |
214
|
|
|
public static function findParamValue(&$value, &$operator): bool |
215
|
|
|
{ |
216
|
|
|
if (is_array($value) || is_object($value)) { |
217
|
|
|
$value = static::assembleParamValue($value, $operator); |
|
|
|
|
218
|
|
|
} else { |
219
|
|
|
self::normalizeEmptyValue($value); |
|
|
|
|
220
|
|
|
|
221
|
|
|
$operator = self::parseParamOperator($value); |
|
|
|
|
222
|
|
|
|
223
|
|
|
if (is_numeric($value)) { |
224
|
|
|
$value = self::prependOperator($value, $operator); |
|
|
|
|
225
|
|
|
} else { |
226
|
|
|
$value = StringHelper::toLowerCase($value); |
227
|
|
|
|
228
|
|
|
if ($value !== ':empty:' || $value !== 'not :empty:') { |
229
|
|
|
// Trim any whitespace from the value |
230
|
|
|
$value = StringHelper::trim($value); |
231
|
|
|
|
232
|
|
|
return false; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
return true; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* Format the param value so that we return a string w/ a prepended operator. |
242
|
|
|
* |
243
|
|
|
* @param $value |
244
|
|
|
* @param $operator |
245
|
|
|
* @param string|int|mixed $defaultValue |
246
|
|
|
* @return array|string |
247
|
|
|
* |
248
|
|
|
* @deprecated |
249
|
|
|
*/ |
250
|
|
|
public static function assembleParamValue($value, $operator, $defaultValue = ':default:') |
251
|
|
|
{ |
252
|
|
|
if (is_array($value) || is_object($value)) { |
253
|
|
|
$id = self::findIdFromObjectArray($value, $operator); |
|
|
|
|
254
|
|
|
|
255
|
|
|
if ($id !== null) { |
256
|
|
|
return self::prependOperator($id, $operator); |
|
|
|
|
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
if (is_object($value)) { |
260
|
|
|
return $defaultValue; |
261
|
|
|
} |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
return self::prependOperator($value, $operator); |
|
|
|
|
265
|
|
|
} |
266
|
|
|
|
267
|
|
|
/** |
268
|
|
|
* Attempt to resolve a param value by the value. |
269
|
|
|
* Return false if a 'handle' or other string identifier is detected. |
270
|
|
|
* |
271
|
|
|
* @param $value |
272
|
|
|
* @param $operator |
273
|
|
|
* @return bool |
274
|
|
|
* |
275
|
|
|
* @deprecated |
276
|
|
|
*/ |
277
|
|
|
public static function prepParamValue(&$value, &$operator): bool |
278
|
|
|
{ |
279
|
|
|
|
280
|
|
|
if (is_array($value)) { |
281
|
|
|
return true; |
282
|
|
|
} else { |
283
|
|
|
self::normalizeEmptyValue($value); |
|
|
|
|
284
|
|
|
$operator = self::parseParamOperator($value); |
|
|
|
|
285
|
|
|
|
286
|
|
|
if (is_numeric($value)) { |
287
|
|
|
return true; |
288
|
|
|
} else { |
289
|
|
|
$value = StringHelper::toLowerCase($value); |
290
|
|
|
|
291
|
|
|
if ($value !== ':empty:' || $value !== 'not :empty:') { |
292
|
|
|
// Trim any whitespace from the value |
293
|
|
|
$value = StringHelper::trim($value); |
294
|
|
|
|
295
|
|
|
return false; |
296
|
|
|
} |
297
|
|
|
} |
298
|
|
|
} |
299
|
|
|
|
300
|
|
|
return true; |
301
|
|
|
} |
302
|
|
|
|
303
|
|
|
/** |
304
|
|
|
* @param $value |
305
|
|
|
* @param string $default |
306
|
|
|
* @return mixed|string |
307
|
|
|
* |
308
|
|
|
* @deprecated |
309
|
|
|
*/ |
310
|
|
|
private static function getJoinType(&$value, $default = 'or') |
311
|
|
|
{ |
312
|
|
|
|
313
|
|
|
// Get first value in array |
314
|
|
|
$joinType = ArrayHelper::firstValue($value); |
315
|
|
|
|
316
|
|
|
// Make sure first value is a string |
317
|
|
|
$firstVal = is_string($joinType) ? StringHelper::toLowerCase($joinType) : ''; |
318
|
|
|
|
319
|
|
|
if ($firstVal == 'and' || $firstVal == 'or') { |
320
|
|
|
$join = array_shift($value); |
321
|
|
|
} else { |
322
|
|
|
$join = $default; |
323
|
|
|
} |
324
|
|
|
|
325
|
|
|
return $join; |
326
|
|
|
} |
327
|
|
|
|
328
|
|
|
/** |
329
|
|
|
* Attempt to get a numeric value from an object array. |
330
|
|
|
* @param $value |
331
|
|
|
* @param null $operator |
332
|
|
|
* @return mixed|string |
333
|
|
|
* |
334
|
|
|
* @deprecated |
335
|
|
|
*/ |
336
|
|
|
private static function findIdFromObjectArray($value, $operator = null) |
337
|
|
|
{ |
338
|
|
|
if ($id = ArrayHelper::getValue($value, 'id')) { |
339
|
|
|
return self::prependOperator($id, $operator); |
|
|
|
|
340
|
|
|
} |
341
|
|
|
|
342
|
|
|
return $id; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Prepend the operator to a value |
347
|
|
|
* |
348
|
|
|
* @param $value |
349
|
|
|
* @param null $operator |
350
|
|
|
* @return string|array |
351
|
|
|
* |
352
|
|
|
* @deprecated |
353
|
|
|
*/ |
354
|
|
|
private static function prependOperator($value, $operator = null) |
355
|
|
|
{ |
356
|
|
|
|
357
|
|
|
if ($operator) { |
358
|
|
|
$operator = StringHelper::toLowerCase($operator); |
359
|
|
|
|
360
|
|
|
if (in_array($operator, static::$operators) || $operator === 'not') { |
361
|
|
|
if (is_array($value)) { |
362
|
|
|
$values = []; |
363
|
|
|
|
364
|
|
|
foreach ($value as $v) { |
365
|
|
|
$values[] = $operator . ($operator === 'not' ? ' ' : '') . $v; |
366
|
|
|
} |
367
|
|
|
|
368
|
|
|
return $values; |
369
|
|
|
} |
370
|
|
|
|
371
|
|
|
return $operator . ($operator === 'not' ? ' ' : '') . $value; |
372
|
|
|
} |
373
|
|
|
} |
374
|
|
|
|
375
|
|
|
return $value; |
376
|
|
|
} |
377
|
|
|
|
378
|
|
|
/** |
379
|
|
|
* Normalizes “empty” values. |
380
|
|
|
* |
381
|
|
|
* @param string &$value The param value. |
382
|
|
|
* |
383
|
|
|
* @deprecated |
384
|
|
|
*/ |
385
|
|
|
private static function normalizeEmptyValue(&$value) |
386
|
|
|
{ |
387
|
|
|
if ($value === null) { |
388
|
|
|
$value = ':empty:'; |
389
|
|
|
} else { |
390
|
|
|
if (StringHelper::toLowerCase($value) == ':notempty:') { |
391
|
|
|
$value = 'not :empty:'; |
392
|
|
|
} |
393
|
|
|
} |
394
|
|
|
} |
395
|
|
|
|
396
|
|
|
/** |
397
|
|
|
* Extracts the operator from a DB param and returns it. |
398
|
|
|
* |
399
|
|
|
* @param string &$value Te param value. |
400
|
|
|
* |
401
|
|
|
* @return string The operator. |
402
|
|
|
* |
403
|
|
|
* @deprecated |
404
|
|
|
*/ |
405
|
|
|
private static function parseParamOperator(&$value) |
406
|
|
|
{ |
407
|
|
|
foreach (static::$operators as $testOperator) { |
408
|
|
|
// Does the value start with this operator? |
409
|
|
|
$operatorLength = strlen($testOperator); |
410
|
|
|
|
411
|
|
|
if (strncmp( |
412
|
|
|
StringHelper::toLowerCase($value), |
413
|
|
|
$testOperator, |
414
|
|
|
$operatorLength |
415
|
|
|
) == 0 |
416
|
|
|
) { |
417
|
|
|
$value = mb_substr($value, $operatorLength); |
418
|
|
|
|
419
|
|
|
if ($testOperator == 'not ') { |
420
|
|
|
return 'not'; |
421
|
|
|
} else { |
422
|
|
|
return $testOperator; |
423
|
|
|
} |
424
|
|
|
} |
425
|
|
|
} |
426
|
|
|
|
427
|
|
|
return ''; |
428
|
|
|
} |
429
|
|
|
} |
430
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return
,die
orexit
statements that have been added for debug purposes.In the above example, the last
return false
will never be executed, because a return statement has already been met in every possible execution path.