Passed
Push — master ( c2d8e3...289151 )
by Jeroen
06:06
created

normalizeMetastringOptions()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 37
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
eloc 21
nc 4
nop 1
dl 0
loc 37
ccs 0
cts 0
cp 0
crap 20
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace Elgg\Database;
4
5
use DataFormatException;
6
use Elgg\Config;
7
use Elgg\Database\Clauses\AnnotationWhereClause;
8
use Elgg\Database\Clauses\AttributeWhereClause;
9
use Elgg\Database\Clauses\Clause;
10
use Elgg\Database\Clauses\EntitySortByClause;
11
use Elgg\Database\Clauses\GroupByClause;
12
use Elgg\Database\Clauses\HavingClause;
13
use Elgg\Database\Clauses\JoinClause;
14
use Elgg\Database\Clauses\MetadataWhereClause;
15
use Elgg\Database\Clauses\OrderByClause;
16
use Elgg\Database\Clauses\PrivateSettingWhereClause;
17
use Elgg\Database\Clauses\RelationshipWhereClause;
18
use Elgg\Database\Clauses\SelectClause;
19
use Elgg\Database\Clauses\WhereClause;
20
use ElggEntity;
21
22
/**
23
 * This trait serves as an adapter between legacy ege* options and new OO query builder
24
 */
25
trait LegacyQueryOptionsAdapter {
26
27
	/**
28
	 * Normalizes legacy query options
29
	 *
30
	 * @param array $options Legacy ege* options
31
	 *
32
	 * @return array
33
	 */
34 1301
	public function normalizeOptions(array $options = []) {
35
36 1301
		if (!isset($options['__original_options'])) {
37 1301
			$options['__original_options'] = $options;
38
		}
39
40 1301
		$options = array_merge($this->getDefaults(), $options);
41
42 1301
		_elgg_check_unsupported_site_guid($options);
43
44 1301
		$options = $this->normalizeGuidOptions($options);
45 1301
		$options = $this->normalizeTimeOptions($options);
46
47 1301
		$options = $this->normalizeAccessOptions($options);
48
49 1301
		$options = $this->normalizeTypeSubtypeOptions($options);
50
51 1301
		$options = $this->normalizePrivateSettingOptions($options);
52 1301
		$options = $this->normalizeRelationshipOptions($options);
53 1301
		$options = $this->normalizeAnnotationOptions($options);
54 1301
		$options = $this->normalizeMetadataOptions($options);
55
56 1301
		foreach (['selects', 'joins', 'wheres'] as $prop) {
57 1301
			if (empty($options[$prop])) {
58 1297
				$options[$prop] = [];
59
			}
60
61 1301
			if (!is_array($options[$prop])) {
62 215
				if ($options[$prop]) {
63 1301
					$options[$prop] = [$options[$prop]];
64
				}
65
			}
66
		}
67
68 1301
		$options = $this->normalizeSelectClauses($options);
69 1301
		$options = $this->normalizeWhereClauses($options);
70 1301
		$options = $this->normalizeJoinClauses($options);
71 1301
		$options = $this->normalizeOrderByClauses($options);
72 1301
		$options = $this->normalizeGroupByClauses($options);
73
74 1301
		return $options;
75
	}
76
77
	/**
78
	 * Returns defaults array
79
	 * @return array
80
	 */
81 1301
	protected function getDefaults() {
82
		return [
83 1301
			'types' => null,
84
			'subtypes' => null,
85
			'type_subtype_pairs' => null,
86
			'guids' => null,
87
			'owner_guids' => null,
88
			'container_guids' => null,
89
			'access_ids' => null,
90
91
			'created_after' => null,
92
			'created_before' => null,
93
			'updated_after' => null,
94
			'updated_before' => null,
95
			'last_action_after' => null,
96
			'last_action_before' => null,
97
98
			'sort_by' => [],
99
			'order_by' => null,
100
			'count' => false,
101 1301
			'limit' => elgg_get_config('default_limit'),
102 1301
			'offset' => 0,
103
104
			'selects' => [],
105
			'wheres' => [],
106
			'joins' => [],
107
			'group_by' => null,
108
109
			'metadata_name_value_pairs' => null,
110 1301
			'metadata_name_value_pairs_operator' => 'AND',
111
			'metadata_case_sensitive' => true,
112
			'order_by_metadata' => null,
113
			'metadata_ids' => null,
114
			'metadata_created_time_lower' => null,
115
			'metadata_created_time_upper' => null,
116
			'metadata_calculation' => null,
117
118
			'search_name_value_pairs' => null,
119
120
			'annotation_names' => null,
121
			'annotation_values' => null,
122
			'annotation_name_value_pairs' => null,
123 1301
			'annotation_name_value_pairs_operator' => 'AND',
124
			'annotation_case_sensitive' => true,
125
			'order_by_annotation' => null,
126
			'annotation_ids' => null,
127
			'annotation_created_time_lower' => null,
128
			'annotation_created_time_upper' => null,
129
			'annotation_owner_guids' => null,
130
			'annotation_calculation' => null,
131
132
			'relationship_pairs' => [],
133
134
			'relationship' => null,
135
			'relationship_guid' => null,
136
			'inverse_relationship' => false,
137 1301
			'relationship_join_on' => 'guid',
138
			'relationship_created_time_lower' => null,
139
			'relationship_created_time_upper' => null,
140
141
			'private_setting_names' => null,
142
			'private_setting_values' => null,
143
			'private_setting_name_value_pairs' => null,
144 1301
			'private_setting_name_value_pairs_operator' => 'AND',
145 1301
			'private_setting_name_prefix' => '',
146
			'private_setting_case_sensitive' => false,
147
148
			'preload_owners' => false,
149
			'preload_containers' => false,
150
			'callback' => null,
151
			'distinct' => true,
152
153
			'batch' => false,
154
			'batch_inc_offset' => true,
155 1301
			'batch_size' => 25,
156
157
			'__ElggBatch' => null,
158
		];
159
	}
160
161
	/**
162
	 * Normalize access options
163
	 *
164
	 * @param array $options Options
165
	 *
166
	 * @return array
167
	 */
168 1301
	protected function normalizeAccessOptions(array $options = []) {
169
170 1301
		$options = self::normalizePluralOptions($options, ['access_id']);
171
172 1301
		return $options;
173
	}
174
175
	/**
176
	 * Normalizes type/subtype options
177
	 *
178
	 * @param array $options Options
179
	 *
180
	 * @return array
181
	 * @throws DataFormatException
182
	 */
183 1301
	protected function normalizeTypeSubtypeOptions(array $options = []) {
184
185
		$singulars = [
186 1301
			'type',
187
			'subtype',
188
		];
189
190 1301
		$options = self::normalizePluralOptions($options, $singulars);
191
192
		// can't use helper function with type_subtype_pair because
193
		// it's already an array...just need to merge it
194 1301
		if (isset($options['type_subtype_pair']) && isset($options['type_subtype_pairs'])) {
195 1
			$options['type_subtype_pairs'] = array_merge((array) $options['type_subtype_pairs'], (array) $options['type_subtype_pair']);
196 1301
		} else if (isset($options['type_subtype_pair'])) {
197 2
			$options['type_subtype_pairs'] = (array) $options['type_subtype_pair'];
198 1301
		} else if (isset($options['type_subtype_pairs'])) {
199 74
			$options['type_subtype_pairs'] = (array) $options['type_subtype_pairs'];
200 1301
		} else if (isset($options['types'])) {
201 1161
			$options['type_subtype_pairs'] = [];
202 1161
			if ($options['types']) {
203 1161
				foreach ((array) $options['types'] as $type) {
204 1161
					$options['type_subtype_pairs'][$type] = isset($options['subtypes']) ? (array) $options['subtypes'] : null;
205
				}
206
			}
207
		}
208
209 1301
		if (is_array($options['type_subtype_pairs'])) {
210 1166
			foreach ($options['type_subtype_pairs'] as $type => $subtypes) {
211 1166
				if (!in_array($type, Config::getEntityTypes())) {
212 16
					elgg_log("'$type' is not a valid entity type", 'WARNING');
213
				}
214 1166
				if (!empty($subtypes) && !is_array($subtypes)) {
215 1166
					$options['type_subtype_pairs'][$type] = [$subtypes];
216
				}
217
			}
218
		}
219
220 1301
		unset($options['type_subtype_pair']);
221 1301
		unset($options['types']);
222 1301
		unset($options['subtypes']);
223
224 1301
		return $options;
225
	}
226
227
	/**
228
	 * Normalizes metadata options
229
	 *
230
	 * @param array $options Options
231
	 *
232
	 * @return array
233
	 */
234 1301
	protected function normalizeMetadataOptions(array $options = []) {
235
		$singulars = [
236 1301
			'metadata_id',
237
			'metadata_name',
238
			'metadata_value',
239
			'metadata_name_value_pair',
240
		];
241
242 1301
		$options = self::normalizePluralOptions($options, $singulars);
243
244 1301
		$options = $this->normalizePairedOptions('metadata', $options);
245 1301
		$options = $this->normalizePairedOptions('search', $options);
246
247 1301
		if (isset($options['order_by_metadata'])) {
248 7
			$name = elgg_extract('name', $options['order_by_metadata']);
249 7
			$direction = strtoupper(elgg_extract('direction', $options['order_by_metadata'], 'asc'));
250 7
			$as = elgg_extract('as', $options['order_by_metadata']);
251
252 7
			if ($name) {
253 7
				$options['sort_by'][] = [
254 7
					'property' => $name,
255 7
					'direction' => in_array($direction, ['ASC', 'DESC']) ? $direction : null,
256 7
					'signed' => $as === ELGG_VALUE_INTEGER,
257 7
					'property_type' => 'metadata',
258
				];
259
			}
260
261 7
			$options['order_by'] = null;
262 7
			$options['order_by_metadata'] = null;
263
		}
264
265
		$props = [
266 1301
			'metadata_ids',
267
			'metadata_created_after',
268
			'metadata_created_before',
269
		];
270
271 1301
		foreach ($props as $prop) {
272 1301
			if (isset($options[$prop]) && empty($options['metadata_name_value_pairs'])) {
273
				$options['metadata_name_value_pairs'][] = [
274 1301
					$prop => $options[$prop]
275
				];
276
			}
277
		}
278
279 1301
		foreach ($options['metadata_name_value_pairs'] as $key => $pair) {
280 1164
			if ($pair instanceof Clause) {
281 66
				continue;
282
			}
283
284 1164
			foreach ($props as $prop) {
285 1164
				if (!isset($pair[$prop])) {
286 1164
					$options['metadata_name_value_pairs'][$key][$prop] = elgg_extract($prop, $options);
287
				}
288
			}
289
290 1164
			$options['metadata_name_value_pairs'][$key]['entity_guids'] = $options['guids'];
291
		}
292
293 1301
		$options['metadata_name_value_pairs'] = $this->removeKeyPrefix('metadata_', $options['metadata_name_value_pairs']);
294
295
		$defaults = [
296 1301
			'name' => null,
297
			'value' => null,
298 1301
			'comparison' => '=',
299 1301
			'type' => ELGG_VALUE_STRING,
300
			'case_sensitive' => true,
301
			'entity_guids' => null,
302
			'ids' => null,
303
			'created_after' => null,
304
			'created_before' => null,
305
			'entity_guids' => null,
306
		];
307
308 1301
		foreach ($options['metadata_name_value_pairs'] as $key => $pair) {
309 1164
			if ($pair instanceof WhereClause) {
310 66
				continue;
311
			}
312
313 1164
			$pair = array_merge($defaults, $pair);
314
315 1164
			if (in_array(strtolower($pair['comparison']), ['in', 'eq', '=']) && is_string($pair['value'])) {
316
				// Apparently this madness is supported
317
				// \Elgg\Integration\ElggCoreGetEntitiesFromMetadataTest::testElggApiGettersEntityMetadataNVPValidNValidVOperandIn
318 999
				$pair['value'] = array_map(function ($e) {
319 999
					return trim($e, ' \"\'');
320 999
				}, explode(',', $pair['value']));
321
			}
322
323 1164
			if (in_array($pair['name'], ElggEntity::$primary_attr_names)) {
324 9
				$clause = new AttributeWhereClause();
325
			} else {
326 1164
				$clause = new MetadataWhereClause();
327 1164
				$clause->ids = (array) $pair['ids'];
328 1164
				$clause->entity_guids = (array) $pair['entity_guids'];
329 1164
				$clause->created_after = $pair['created_after'];
330 1164
				$clause->created_before = $pair['created_before'];
331
			}
332
333 1164
			$clause->names = (array) $pair['name'];
334 1164
			$clause->values = (array) $pair['value'];
335 1164
			$clause->comparison = $pair['comparison'];
336 1164
			$clause->value_type = $pair['type'];
337 1164
			$clause->case_sensitive = $pair['case_sensitive'];
338
339 1164
			$options['metadata_name_value_pairs'][$key] = $clause;
340
		}
341
342 1301
		return $options;
343
	}
344
345
	/**
346
	 * Normalizes annotation options
347
	 *
348
	 * @param array $options Options
349
	 *
350
	 * @return array
351
	 */
352 1301
	protected function normalizeAnnotationOptions(array $options = []) {
353
		$singulars = [
354 1301
			'annotation_id',
355
			'annotation_name',
356
			'annotation_value',
357
			'annotation_name_value_pair',
358
		];
359
360 1301
		$options = self::normalizePluralOptions($options, $singulars);
361
362 1301
		$options = $this->normalizePairedOptions('annotation', $options);
363
364 1301
		if (isset($options['order_by_annotation'])) {
365 1
			$name = elgg_extract('name', $options['order_by_annotation']);
366 1
			$direction = strtoupper(elgg_extract('direction', $options['order_by_annotation'], 'asc'));
367 1
			$as = elgg_extract('as', $options['order_by_annotation']);
368
369 1
			if ($name) {
370 1
				$options['sort_by'][] = [
371 1
					'property' => $name,
372 1
					'property_type' => 'annotation',
373 1
					'direction' => in_array($direction, ['ASC', 'DESC']) ? $direction : null,
374 1
					'signed' => $as === ELGG_VALUE_INTEGER,
375
				];
376
			}
377
378 1
			$options['order_by'] = null;
379 1
			$options['order_by_annotation'] = null;
380
		}
381
382
		$props = [
383 1301
			'annotation_ids',
384
			'annotation_owner_guids',
385
			'annotation_created_after',
386
			'annotation_created_before',
387
			'annotation_sort_by_calculation',
388
		];
389
390 1301
		foreach ($props as $prop) {
391 1301
			if (isset($options[$prop]) && empty($options['annotation_name_value_pairs'])) {
392 215
				$options['annotation_name_value_pairs'][] = [
393 1301
					$prop => $options[$prop]
394
				];
395
			}
396
		}
397
398 1301
		foreach ($options['annotation_name_value_pairs'] as $key => $pair) {
399 257
			if ($pair instanceof WhereClause) {
400 217
				continue;
401
			}
402
403 257
			foreach ($props as $prop) {
404 257
				if (!isset($pair[$prop])) {
405 257
					$options['annotation_name_value_pairs'][$key][$prop] = elgg_extract($prop, $options);
406
				}
407
			}
408
409 257
			$options['annotation_name_value_pairs'][$key]['entity_guids'] = $options['guids'];
410
		}
411
412 1301
		$options['annotation_name_value_pairs'] = $this->removeKeyPrefix('annotation_', $options['annotation_name_value_pairs']);
413
414
		$defaults = [
415 1301
			'name' => null,
416
			'value' => null,
417 1301
			'comparison' => '=',
418 1301
			'type' => ELGG_VALUE_STRING,
419
			'case_sensitive' => true,
420
			'entity_guids' => null,
421
			'owner_guids' => null,
422
			'ids' => null,
423
			'enabled' => null,
424
			'access_ids' => null,
425
			'created_after' => null,
426
			'created_before' => null,
427
			'sort_by_calculation' => null,
428
		];
429
430 1301
		foreach ($options['annotation_name_value_pairs'] as $key => $pair) {
431 257
			if ($pair instanceof WhereClause) {
432 217
				continue;
433
			}
434
435 257
			$pair = array_merge($defaults, $pair);
436
437 257
			$clause = new AnnotationWhereClause();
438 257
			$clause->ids = (array) $pair['ids'];
439 257
			$clause->entity_guids = (array) $pair['entity_guids'];
440 257
			$clause->owner_guids = (array) $pair['owner_guids'];
441 257
			$clause->created_after = $pair['created_after'];
442 257
			$clause->created_before = $pair['created_before'];
443 257
			$clause->names = (array) $pair['name'];
444 257
			$clause->values = (array) $pair['value'];
445 257
			$clause->comparison = $pair['comparison'];
446 257
			$clause->value_type = $pair['type'];
447 257
			$clause->case_sensitive = $pair['case_sensitive'];
448 257
			$clause->enabled = $pair['enabled'];
449 257
			$clause->access_ids = (array) $pair['access_ids'];
450 257
			$clause->sort_by_calculation = $pair['sort_by_calculation'];
451
452 257
			if ($clause->sort_by_calculation && empty($options['order_by'])) {
453 7
				$clause->sort_by_direction = 'desc';
454
			}
455
456 257
			$options['annotation_name_value_pairs'][$key] = $clause;
457
		}
458
459 1301
		return $options;
460
	}
461
462
	/**
463
	 * Normalizes private settings options
464
	 *
465
	 * @param array $options Options
466
	 *
467
	 * @return array
468
	 */
469 1301
	protected function normalizePrivateSettingOptions(array $options = []) {
470
471
		$singulars = [
472 1301
			'private_setting_name',
473
			'private_setting_value',
474
			'private_setting_name_value_pair',
475
		];
476
477 1301
		$options = self::normalizePluralOptions($options, $singulars);
478
479 1301
		if (isset($options['private_setting_name_prefix'])) {
480 1301
			$prefix = $options['private_setting_name_prefix'];
481 1301
			unset($options['private_setting_name_prefix']);
482
483 1301
			if (is_array($options['private_setting_names'])) {
484 78
				$options['private_setting_names'] = array_map(function ($el) use ($prefix) {
485 78
					return "$prefix$el";
486 78
				}, $options['private_setting_names']);
487
			}
488
		}
489
490 1301
		$options = $this->normalizePairedOptions('private_setting', $options);
491
492 1301
		foreach ($options['private_setting_name_value_pairs'] as $key => $pair) {
493 89
			if ($pair instanceof WhereClause) {
494 63
				continue;
495
			}
496
497 89
			$options['private_setting_name_value_pairs'][$key]['entity_guids'] = $options['guids'];
498
		}
499
500 1301
		$options['private_setting_name_value_pairs'] = $this->removeKeyPrefix('private_setting_', $options['private_setting_name_value_pairs']);
501
502
		$defaults = [
503 1301
			'name' => null,
504
			'value' => null,
505 1301
			'comparison' => '=',
506 1301
			'type' => ELGG_VALUE_STRING,
507
			'case_sensitive' => true,
508
			'entity_guids' => null,
509
			'ids' => null,
510
		];
511
512 1301
		foreach ($options['private_setting_name_value_pairs'] as $key => $pair) {
513 89
			if ($pair instanceof WhereClause) {
514 63
				continue;
515
			}
516
517 89
			$pair = array_merge($defaults, $pair);
518
519 89
			$clause = new PrivateSettingWhereClause();
520 89
			$clause->ids = (array) $pair['ids'];
521 89
			$clause->entity_guids = (array) $pair['entity_guids'];
522 89
			$clause->names = (array) $pair['name'];
523 89
			$clause->values = (array) $pair['value'];
524 89
			$clause->comparison = $pair['comparison'];
525 89
			$clause->value_type = $pair['type'];
526 89
			$clause->case_sensitive = $pair['case_sensitive'];
527
528 89
			$options['private_setting_name_value_pairs'][$key] = $clause;
529
		}
530
531 1301
		return $options;
532
	}
533
534
	/**
535
	 * Normalizes paired options
536
	 *
537
	 * @param string $type    Pair type
538
	 * @param array  $options Options
539
	 *
540
	 * @return array
541
	 */
542 1301
	protected function normalizePairedOptions($type = 'metadata', array $options = []) {
543 1301
		if (!is_array($options["{$type}_name_value_pairs"])) {
544 1301
			$options["{$type}_name_value_pairs"] = [];
545
		}
546
547 1301
		if (isset($options["{$type}_name_value_pairs"]['name'])) {
548 356
			$options["{$type}_name_value_pairs"][] = [
549 356
				'name' => $options["{$type}_name_value_pairs"]['name'],
550 356
				'value' => elgg_extract('value', $options["{$type}_name_value_pairs"]),
551 356
				'comparison' => elgg_extract('operand', $options["{$type}_name_value_pairs"], '='),
552 356
				'case_sensitive' => elgg_extract('case_sensitive', $options["{$type}_name_value_pairs"], true)
553
			];
554 356
			unset($options["{$type}_name_value_pairs"]['name']);
555 356
			unset($options["{$type}_name_value_pairs"]['value']);
556 356
			unset($options["{$type}_name_value_pairs"]['operand']);
557 356
			unset($options["{$type}_name_value_pairs"]['case_sensitive']);
558
		}
559
560 1301
		foreach ($options["{$type}_name_value_pairs"] as $index => $pair) {
561 1041
			if (is_array($pair)) {
562 815
				$keys = array_keys($pair);
563 815
				if (sizeof($keys) === 1 && is_string($keys[0])) {
0 ignored issues
show
Bug introduced by
The call to sizeof() has too few arguments starting with mode. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

563
				if (/** @scrutinizer ignore-call */ sizeof($keys) === 1 && is_string($keys[0])) {

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
564 2
					$options["{$type}_name_value_pairs"][$index] = [
565 2
						'name' => $keys[0],
566 2
						'value' => $pair[$keys[0]],
567 1041
						'comparison' => '=',
568
					];
569
				}
570
			}
571
		}
572
573 1301
		foreach ($options["{$type}_name_value_pairs"] as $index => $values) {
574 1041
			if ($values instanceof Clause) {
575 301
				continue;
576
			}
577
578 1029
			if (is_array($values)) {
579 815
				if (isset($values['name'])) {
580 811
					continue;
581
				}
582
			}
583 363
			$options["{$type}_name_value_pairs"][$index] = [
584 363
				'name' => $index,
585 363
				'value' => $values,
586 363
				'comparison' => '=',
587
			];
588
		}
589
590 1301
		if (isset($options["{$type}_names"]) || isset($options["{$type}_values"])) {
591 544
			$options["{$type}_name_value_pairs"][] = [
592 544
				'name' => isset($options["{$type}_names"]) ? (array) $options["{$type}_names"] : null,
593 544
				'value' => isset($options["{$type}_values"]) ? (array) $options["{$type}_values"] : null,
594 544
				'comparison' => '=',
595
			];
596
		}
597
598 1301
		foreach ($options["{$type}_name_value_pairs"] as $key => $value) {
599 1208
			if ($value instanceof Clause) {
600 301
				continue;
601
			}
602
603 1208
			if (!isset($value['case_sensitive'])) {
604 940
				$value['case_sensitive'] = elgg_extract("{$type}_case_sensitive", $options, true);
605
			}
606 1208
			if (!isset($value['type'])) {
607 1208
				if (is_bool($value['value'])) {
608 4
					$value['value'] = (int) $value['value'];
609
				}
610 1208
				if (is_int($value['value'])) {
611 84
					$value['type'] = ELGG_VALUE_INTEGER;
612
				} else {
613 1207
					$value['type'] = ELGG_VALUE_STRING;
614
				}
615
			}
616 1208
			if (!isset($value['comparison']) && isset($value['operand'])) {
617 5
				$value['comparison'] = $value['operand'];
618 1208
				unset($value['operand']);
619
			}
620
621
			$options["{$type}_name_value_pairs"][$key] = $value;
622 1301
		}
623 1301
624 1301
		unset($options["{$type}_names"]);
625
		unset($options["{$type}_values"]);
626 1301
		unset($options["{$type}_case_sensitive"]);
627
628
		return $options;
629
	}
630
631
	/**
632
	 * Normalizes relationship options
633
	 *
634
	 * @param array $options Options
635
	 *
636 1301
	 * @return array
637
	 */
638 1301
	protected function normalizeRelationshipOptions(array $options = []) {
639
640
		$pair = [];
641 1301
642
		$defaults = [
643
			'relationship_ids' => null,
644
			'relationship' => null,
645
			'relationship_guid' => null,
646
			'inverse_relationship' => false,
647
			'relationship_join_on' => 'guid',
648
			'relationship_created_after' => null,
649
			'relationship_created_before' => null,
650 1301
		];
651 1301
652 1301
		foreach (array_keys($defaults) as $prop) {
653
			if (isset($options[$prop])) {
654 1301
				$pair[$prop] = $options[$prop];
655
			}
656
			unset($options[$prop]);
657 1301
		}
658 1301
659
		$options['relationship_pairs'] = (array) $options['relationship_pairs'];
660 1301
		$options['relationship_pairs'][] = $pair;
661 1301
662 1
		foreach ($options['relationship_pairs'] as $index => $relationship_pair) {
663
			if ($relationship_pair instanceof WhereClause) {
664
				continue;
665 1301
			}
666
667
			$options['relationship_pairs'][$index] = array_merge($defaults, $relationship_pair);
668 1301
		}
669
670 1301
		$options['relationship_pairs'] = $this->removeKeyPrefix('relationship_', $options['relationship_pairs']);
671 1301
672 1
		foreach ($options['relationship_pairs'] as $key => $pair) {
673
			if ($pair instanceof WhereClause) {
674
				continue;
675 1301
			}
676
677 1301
			$pair = array_merge($defaults, $pair);
678 1297
679 1297
			if (!$pair['relationship'] && !$pair['relationship_guid']) {
680
				unset($options['relationship_pairs'][$key]);
681
				continue;
682 35
			}
683 35
684 35
			$clause = new RelationshipWhereClause();
685
			$clause->ids = (array) $pair['ids'];
686 35
			$clause->names = (array) $pair['relationship'];
687 35
688 35
			$clause->join_on = $pair['join_on'];
689 25
			$clause->inverse = $pair['inverse_relationship'];
690
			if ($clause->inverse) {
691 15
				$clause->object_guids = (array) $pair['guid'];
692
			} else {
693 35
				$clause->subject_guids = (array) $pair['guid'];
694 35
			}
695
			$clause->created_after = $pair['created_after'];
696
			$clause->created_before = $pair['created_before'];
697 35
698
699
			$options['relationship_pairs'][$key] = $clause;
700 1301
		}
701
702
		return $options;
703
	}
704
705
	/**
706
	 * Normalizes guid based options
707
	 *
708
	 * @param array $options Options
709
	 *
710 1301
	 * @return array
711
	 */
712
	protected function normalizeGuidOptions(array $options = []) {
713 1301
714
		$singulars = [
715
			'guid',
716
			'owner_guid',
717
			'container_guid',
718
			'annotation_owner_guid',
719 1301
		];
720
721
		$options = self::normalizePluralOptions($options, $singulars);
722 1301
723
		$names = [
724
			'guids',
725
			'owner_guids',
726
			'container_guids',
727
			'annotation_owner_guids',
728
			'relationship_guid',
729 1301
		];
730 1301
731
		foreach ($names as $name) {
732
			$options[$name] = !empty($options[$name]) ? $options[$name] : null;
733 1301
		}
734
735
		return $options;
736
	}
737
738
	/**
739
	 * Normalizes time based options
740
	 *
741
	 * @param array $options Options array
742
	 *
743 1301
	 * @return array
744
	 */
745
	protected function normalizeTimeOptions(array $options = []) {
746 1301
747
		$props = [
748
			'modified',
749
			'created',
750
			'updated',
751
			'metadata_created',
752
			'annotation_created',
753
			'relationship_created',
754
			'last_action',
755
			'posted',
756 1301
		];
757
758 1301
		$bounds = ['time_lower', 'time_upper', 'after', 'before'];
759 1301
760 1301
		foreach ($props as $prop) {
761
			foreach ($bounds as $bound) {
762 1301
				$prop_name = "{$prop}_{$bound}";
763
764 1301
				$time = elgg_extract($prop_name, $options);
0 ignored issues
show
Unused Code introduced by
The assignment to $time is dead and can be removed.
Loading history...
765 1301
766 1301
				$new_prop_name = $prop_name;
767 1301
				$new_prop_name = str_replace('modified', 'updated', $new_prop_name);
768 1301
				$new_prop_name = str_replace('posted', 'created', $new_prop_name);
769
				$new_prop_name = str_replace('time_lower', 'after', $new_prop_name);
770 1301
				$new_prop_name = str_replace('time_upper', 'before', $new_prop_name);
771 1301
772
				if (!isset($options[$new_prop_name])) {
773
					$options[$new_prop_name] = elgg_extract($prop_name, $options);
774
				}
775
			}
776 1301
		}
777
778
		return $options;
779
	}
780
781
	/**
782
	 * Remove $prefix from array keys
783
	 *
784
	 * @param string $prefix Prefix
785
	 * @param array  $array  Array
786
	 *
787 1301
	 * @return array
788 1301
	 */
789 1301
	protected function removeKeyPrefix($prefix, array $array = []) {
790 1301
		foreach ($array as $key => $value) {
791 1301
			$new_key = $key;
792
			if (strpos($key, $prefix) === 0) {
793 1301
				$new_key = substr($key, strlen($prefix));
794 1301
			}
795
			if (!isset($array[$new_key])) {
796 1301
				$array[$new_key] = $array[$key];
797 1301
			}
798
			if ($new_key !== $key) {
799
				unset($array[$key]);
800 1301
			}
801 1301
802
			if (is_array($array[$new_key])) {
803
				$array[$new_key] = $this->removeKeyPrefix($prefix, $array[$new_key]);
804
			}
805 1301
		}
806
807
		return $array;
808
	}
809
810
	/**
811
	 * Processes an array of 'select' clauses
812
	 *
813
	 * @param array $options Options
814
	 *
815 1301
	 * @return array
816
	 */
817 1301
	protected function normalizeSelectClauses(array $options = []) {
818
819 1301
		$options = self::normalizePluralOptions($options, ['select']);
820 28
821 1
		foreach ($options['selects'] as $key => $clause) {
822 1
			if (empty($clause)) {
823
				unset($options['selects'][$key]);
824
				continue;
825 28
			}
826 2
827
			if ($clause instanceof SelectClause) {
828
				continue;
829 27
			}
830
831
			$options['selects'][$key] = new SelectClause($clause);
832 1301
		}
833
834
		return $options;
835
	}
836
837
	/**
838
	 * Processes an array of 'where' clauses
839
	 *
840
	 * @param array $options Options
841
	 *
842 1301
	 * @return array
843
	 */
844 1301
	protected function normalizeWhereClauses(array $options = []) {
845
846 1301
		$options = self::normalizePluralOptions($options, ['where']);
847 481
848 1
		foreach ($options['wheres'] as $key => $clause) {
849 1
			if (empty($clause)) {
850
				unset($options['wheres'][$key]);
851
				continue;
852 481
			}
853 3
854
			if ($clause instanceof WhereClause) {
855
				continue;
856 481
			}
857 189
858
			if (is_string($clause)) {
859
				elgg_deprecated_notice("
860
					Using literal MySQL statements in 'wheres' options parameter is deprecated.
861
					Instead use a closure that receives an instanceof of QueryBuilder
862 189
					and returns a composite DBAL expression
863 189
					
864
					{{ $clause }}
865
				", '3.0');
866 481
			}
867
868
			$options['wheres'][$key] = new WhereClause($clause);
869 1301
		}
870
871
		return $options;
872
	}
873
874
	/**
875
	 * Processes an array of 'joins' clauses
876
	 *
877
	 * @param array $options Options
878
	 *
879
	 * @return array
880 1301
	 * @throws \InvalidParameterException
881
	 */
882 1301
	protected function normalizeJoinClauses(array $options = []) {
883
884 1301
		$options = self::normalizePluralOptions($options, ['join']);
885 7
886 1
		foreach ($options['joins'] as $key => $join) {
887 1
			if (empty($join)) {
888
				unset($options['joins'][$key]);
889
				continue;
890 7
			}
891 6
892
			if ($join instanceof JoinClause) {
893
				continue;
894 2
			}
895 2
896
			if (is_string($join)) {
897 2
				preg_match('/((LEFT|INNER|RIGHT)\s+)?JOIN\s+(.*?)\s+((as\s+)?(.*?)\s+)ON\s+(.*)$/im', $join, $parts);
898 2
899 2
				$type = !empty($parts[2]) ? strtolower($parts[2]) : 'inner';
900 2
				$table = $parts[3];
901
				$alias = $parts[6];
902 2
				$condition = preg_replace('/\r|\n/', '', $parts[7]);
903 2
904 2
				$dbprefix = elgg_get_config('dbprefix');
905
				if (strpos($table, $dbprefix) === 0) {
906
					$table = substr($table, strlen($dbprefix));
907 2
				}
908
909
				elgg_deprecated_notice("
910
					Using literal MySQL statements in 'joins' options parameter is deprecated.
911
					Instead use a closure that receives an instanceof of QueryBuilder and returns an instanceof of JoinClause,
912 2
					also consider using one of the built-in methods in QueryBuilder.
913 2
					
914
					{{ $join }}
915 2
				", '3.0');
916 2
917
				$clause = new JoinClause($table, $alias, $condition, $type);
918
				$options['joins'][$key] = $clause;
919
			}
920 1301
		}
921
922
		return $options;
923
	}
924
925
	/**
926
	 * Processes an array of 'joins' clauses
927
	 *
928
	 * @param array $options Options
929
	 *
930 1301
	 * @return array
931
	 */
932 1301
	protected function normalizeOrderByClauses(array $options = []) {
933 1301
934
		$order_by = $options['order_by'];
935 1301
		$options['order_by'] = [];
936 650
937 483
		if (!empty($order_by)) {
938
			if (is_string($order_by)) {
939
				elgg_deprecated_notice("
940
					Using literal MySQL statements in 'order_by' options parameter is deprecated.
941 483
					Instead use an OrderByClause or array of them.
942 483
					
943
					{{ $order_by }}
944 483
				", '3.0');
945 644
946 643
				$orders = explode(',', $order_by);
947
			} else if (is_array($order_by)) {
948 1
				$orders = $order_by;
949
			} else {
950
				$orders = [$order_by];
951 650
			}
952 650
953 644
			foreach ($orders as $order) {
954 644
				if ($order instanceof OrderByClause) {
955
					$options['order_by'][] = $order;
956
					continue;
957 484
				}
958 484
959 319
				$order = trim($order);
960 319
				if (preg_match('/(.*)(?=\s+(asc|desc))/i', $order, $parts)) {
961
					$column = $parts[1];
962 180
					$direction = $parts[2];
963 180
				} else {
964
					$column = $order;
965
					$direction = 'ASC';
966 484
				}
967 484
968
				$direction = in_array(strtoupper($direction), [
969 484
					'ASC',
970
					'DESC'
971 484
				]) ? strtoupper($direction) : 'ASC';
972
973
				$options['order_by'][] = new OrderByClause($column, $direction);
974
			}
975 1301
		}
976 8
977 8
		foreach ($options['sort_by'] as $key => $value) {
978 8
			$clause = new EntitySortByClause();
979 8
			$clause->property = elgg_extract('property', $value);
980 8
			$clause->property_type = elgg_extract('property_type', $value);
981 8
			$clause->join_type = elgg_extract('join_type', $value, 'inner');
982
			$clause->direction = elgg_extract('direction', $value);
983 8
			$clause->signed = elgg_extract('signed', $value);
984
985
			$options['order_by'][] = $clause;
986 1301
		}
987
988
		return $options;
989
	}
990
991
	/**
992
	 * Normalize 'group_by' statements
993
	 *
994
	 * @param array $options Options
995
	 *
996 1301
	 * @return array
997
	 */
998 1301
	protected function normalizeGroupByClauses(array $options = []) {
999 1297
1000
		if (!isset($options['having'])) {
1001 313
			$options['having'] = [];
1002 2
		} else {
1003
			if (!is_array($options['having'])) {
1004
				$options['having'] = [$options['having']];
1005 313
			}
1006 7
1007 1
			foreach ($options['having'] as $key => $expr) {
1008
				if ($expr instanceof HavingClause) {
1009
					continue;
1010 6
				}
1011
1012
				$options['having'][$key] = new HavingClause($expr);
1013
			}
1014 1301
		}
1015 1296
1016
		if (empty($options['group_by'])) {
1017
			$options['group_by'] = [];
1018 1301
		}
1019 13
1020
		if (is_string($options['group_by'])) {
1021 13
			$clause = $options['group_by'];
1022
1023 13
			$options['group_by'] = explode(',', $options['group_by']);
1024 5
1025
			if (count($options['group_by']) > 1) {
1026
				elgg_deprecated_notice("
1027
					Using literal MySQL statements in 'group_by' options parameter is deprecated.
1028
					Instead use a closure that receives an instanceof of QueryBuilder
1029 5
					and returns a prepared clause.
1030 5
					
1031
					{{ $clause }}
1032
				", '3.0');
1033
			}
1034 1301
		}
1035 19
1036 18
		$options['group_by'] = array_map(function ($e) {
1037
			if (!$e instanceof GroupByClause) {
1038
				$e = new GroupByClause(trim($e));
1039 19
			}
1040 1301
1041
			return $e;
1042
		}, $options['group_by']);
1043
1044
		return $options;
1045
	}
1046
1047
	/**
1048
	 * Normalizes metadata / annotation option names to their corresponding metastrings name.
1049
	 *
1050
	 * @param array $options An options array
1051
	 * @return array
1052
	 * @access private
1053
	 */
1054
	public static function normalizeMetastringOptions(array $options = []) {
1055
1056
		// support either metastrings_type or metastring_type
1057
		// because I've made this mistake many times and hunting it down is a pain...
1058
		$type = elgg_extract('metastring_type', $options, null);
1059
		$type = elgg_extract('metastrings_type', $options, $type);
1060
1061
		$options['metastring_type'] = $type;
1062
1063
		// support annotation_ and annotations_ because they're way too easy to confuse
1064
		$prefixes = ['metadata_', 'annotation_', 'annotations_'];
1065
1066
		// map the metadata_* options to metastring_* options
1067
		$map = [
1068
			'names'                 => 'metastring_names',
1069
			'values'                => 'metastring_values',
1070
			'case_sensitive'        => 'metastring_case_sensitive',
1071
			'owner_guids'           => 'metastring_owner_guids',
1072
			'created_time_lower'    => 'metastring_created_time_lower',
1073
			'created_time_upper'    => 'metastring_created_time_upper',
1074
			'calculation'           => 'metastring_calculation',
1075
			'ids'                   => 'metastring_ids',
1076
		];
1077
1078
		foreach ($prefixes as $prefix) {
1079
			$singulars = ["{$prefix}name", "{$prefix}value", "{$prefix}owner_guid", "{$prefix}id"];
1080
			$options = self::normalizePluralOptions($options, $singulars);
1081
1082
			foreach ($map as $specific => $normalized) {
1083
				$key = $prefix . $specific;
1084
				if (isset($options[$key])) {
1085
					$options[$normalized] = $options[$key];
1086
				}
1087
			}
1088
		}
1089
1090
		return $options;
1091
	}
1092
1093
	/**
1094
	 * Normalise the singular keys in an options array to plural keys.
1095
	 *
1096
	 * Used in elgg_get_entities*() functions to support shortcutting plural
1097
	 * names by singular names.
1098
	 *
1099
	 * @param array $options   The options array. $options['keys'] = 'values';
1100
	 * @param array $singulars A list of singular words to pluralize by adding 's'.
1101
	 *
1102
	 * @return array
1103
	 * @access private
1104
	 */
1105
	public static function normalizePluralOptions($options, $singulars) {
1106
		foreach ($singulars as $singular) {
1107
			$plural = $singular . 's';
1108
1109
			if (array_key_exists($singular, $options)) {
1110
				if ($options[$singular] === ELGG_ENTITIES_ANY_VALUE) {
1111
					$options[$plural] = $options[$singular];
1112
				} else {
1113
					// Test for array refs #2641
1114
					if (!is_array($options[$singular])) {
1115
						$options[$plural] = [$options[$singular]];
1116
					} else {
1117
						$options[$plural] = $options[$singular];
1118
					}
1119
				}
1120
			}
1121
1122
			unset($options[$singular]);
1123
		}
1124
1125
		return $options;
1126
	}
1127
}
1128