Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

LegacyQueryOptionsAdapter   F

Complexity

Total Complexity 140

Size/Duplication

Total Lines 1018
Duplicated Lines 0 %

Test Coverage

Coverage 99.77%

Importance

Changes 0
Metric Value
dl 0
loc 1018
ccs 429
cts 430
cp 0.9977
rs 1.1494
c 0
b 0
f 0
wmc 140

17 Methods

Rating   Name   Duplication   Size   Complexity  
B normalizeGuidOptions() 0 24 3
C normalizeRelationshipOptions() 0 65 10
B normalizeWhereClauses() 0 28 5
B removeKeyPrefix() 0 19 6
B normalizeTimeOptions() 0 34 4
C normalizeJoinClauses() 0 41 7
A normalizeSelectClauses() 0 18 4
B getDefaults() 0 77 1
B normalizeOptions() 0 41 6
B normalizeOrderByClauses() 0 57 9
F normalizePairedOptions() 0 85 23
C normalizeGroupByClauses() 0 47 9
B normalizePrivateSettingOptions() 0 63 7
A normalizeAccessOptions() 0 5 1
C normalizeTypeSubtypeOptions() 0 42 14
F normalizeAnnotationOptions() 0 108 15
F normalizeMetadataOptions() 0 109 16

How to fix   Complexity   

Complex Class

Complex classes like LegacyQueryOptionsAdapter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LegacyQueryOptionsAdapter, and based on these observations, apply Extract Interface, too.

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 = _elgg_normalize_plural_options_array($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 = _elgg_normalize_plural_options_array($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 = _elgg_normalize_plural_options_array($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 = _elgg_normalize_plural_options_array($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 = _elgg_normalize_plural_options_array($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
				$options["{$type}_name_value_pairs"][$key]['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
					$options["{$type}_name_value_pairs"][$key]['type'] = ELGG_VALUE_INTEGER;
612
				} else {
613 1207
					$options["{$type}_name_value_pairs"][$key]['type'] = ELGG_VALUE_STRING;
614
				}
615
			}
616 1208
			if (!isset($value['comparison']) && isset($value['operand'])) {
617 5
				$options["{$type}_name_value_pairs"][$key]['comparison'] = $options["{$type}_name_value_pairs"][$key]['operand'];
618 1208
				unset($options["{$type}_name_value_pairs"][$key]['operand']);
619
			}
620
		}
621
622 1301
		unset($options["{$type}_names"]);
623 1301
		unset($options["{$type}_values"]);
624 1301
		unset($options["{$type}_case_sensitive"]);
625
626 1301
		return $options;
627
	}
628
629
	/**
630
	 * Normalizes relationship options
631
	 *
632
	 * @param array $options Options
633
	 *
634
	 * @return array
635
	 */
636 1301
	protected function normalizeRelationshipOptions(array $options = []) {
637
638 1301
		$pair = [];
639
640
		$defaults = [
641 1301
			'relationship_ids' => null,
642
			'relationship' => null,
643
			'relationship_guid' => null,
644
			'inverse_relationship' => false,
645
			'relationship_join_on' => 'guid',
646
			'relationship_created_after' => null,
647
			'relationship_created_before' => null,
648
		];
649
650 1301
		foreach (array_keys($defaults) as $prop) {
651 1301
			if (isset($options[$prop])) {
652 1301
				$pair[$prop] = $options[$prop];
653
			}
654 1301
			unset($options[$prop]);
655
		}
656
657 1301
		$options['relationship_pairs'] = (array) $options['relationship_pairs'];
658 1301
		$options['relationship_pairs'][] = $pair;
659
660 1301
		foreach ($options['relationship_pairs'] as $index => $relationship_pair) {
661 1301
			if ($relationship_pair instanceof WhereClause) {
662 1
				continue;
663
			}
664
665 1301
			$options['relationship_pairs'][$index] = array_merge($defaults, $relationship_pair);
666
		}
667
668 1301
		$options['relationship_pairs'] = $this->removeKeyPrefix('relationship_', $options['relationship_pairs']);
669
670 1301
		foreach ($options['relationship_pairs'] as $key => $pair) {
671 1301
			if ($pair instanceof WhereClause) {
672 1
				continue;
673
			}
674
675 1301
			$pair = array_merge($defaults, $pair);
676
677 1301
			if (!$pair['relationship'] && !$pair['relationship_guid']) {
678 1297
				unset($options['relationship_pairs'][$key]);
679 1297
				continue;
680
			}
681
682 35
			$clause = new RelationshipWhereClause();
683 35
			$clause->ids = (array) $pair['ids'];
684 35
			$clause->names = (array) $pair['relationship'];
685
686 35
			$clause->join_on = $pair['join_on'];
687 35
			$clause->inverse = $pair['inverse_relationship'];
688 35
			if ($clause->inverse) {
689 25
				$clause->object_guids = (array) $pair['guid'];
690
			} else {
691 15
				$clause->subject_guids = (array) $pair['guid'];
692
			}
693 35
			$clause->created_after = $pair['created_after'];
694 35
			$clause->created_before = $pair['created_before'];
695
696
697 35
			$options['relationship_pairs'][$key] = $clause;
698
		}
699
700 1301
		return $options;
701
	}
702
703
	/**
704
	 * Normalizes guid based options
705
	 *
706
	 * @param array $options Options
707
	 *
708
	 * @return array
709
	 */
710 1301
	protected function normalizeGuidOptions(array $options = []) {
711
712
		$singulars = [
713 1301
			'guid',
714
			'owner_guid',
715
			'container_guid',
716
			'annotation_owner_guid',
717
		];
718
719 1301
		$options = _elgg_normalize_plural_options_array($options, $singulars);
720
721
		$names = [
722 1301
			'guids',
723
			'owner_guids',
724
			'container_guids',
725
			'annotation_owner_guids',
726
			'relationship_guid',
727
		];
728
729 1301
		foreach ($names as $name) {
730 1301
			$options[$name] = !empty($options[$name]) ? $options[$name] : null;
731
		}
732
733 1301
		return $options;
734
	}
735
736
	/**
737
	 * Normalizes time based options
738
	 *
739
	 * @param array $options Options array
740
	 *
741
	 * @return array
742
	 */
743 1301
	protected function normalizeTimeOptions(array $options = []) {
744
745
		$props = [
746 1301
			'modified',
747
			'created',
748
			'updated',
749
			'metadata_created',
750
			'annotation_created',
751
			'relationship_created',
752
			'last_action',
753
			'posted',
754
		];
755
756 1301
		$bounds = ['time_lower', 'time_upper', 'after', 'before'];
757
758 1301
		foreach ($props as $prop) {
759 1301
			foreach ($bounds as $bound) {
760 1301
				$prop_name = "{$prop}_{$bound}";
761
762 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...
763
764 1301
				$new_prop_name = $prop_name;
765 1301
				$new_prop_name = str_replace('modified', 'updated', $new_prop_name);
766 1301
				$new_prop_name = str_replace('posted', 'created', $new_prop_name);
767 1301
				$new_prop_name = str_replace('time_lower', 'after', $new_prop_name);
768 1301
				$new_prop_name = str_replace('time_upper', 'before', $new_prop_name);
769
770 1301
				if (!isset($options[$new_prop_name])) {
771 1301
					$options[$new_prop_name] = elgg_extract($prop_name, $options);
772
				}
773
			}
774
		}
775
776 1301
		return $options;
777
	}
778
779
	/**
780
	 * Remove $prefix from array keys
781
	 *
782
	 * @param string $prefix Prefix
783
	 * @param array  $array  Array
784
	 *
785
	 * @return array
786
	 */
787 1301
	protected function removeKeyPrefix($prefix, array $array = []) {
788 1301
		foreach ($array as $key => $value) {
789 1301
			$new_key = $key;
790 1301
			if (strpos($key, $prefix) === 0) {
791 1301
				$new_key = substr($key, strlen($prefix));
792
			}
793 1301
			if (!isset($array[$new_key])) {
794 1301
				$array[$new_key] = $array[$key];
795
			}
796 1301
			if ($new_key !== $key) {
797 1301
				unset($array[$key]);
798
			}
799
800 1301
			if (is_array($array[$new_key])) {
801 1301
				$array[$new_key] = $this->removeKeyPrefix($prefix, $array[$new_key]);
802
			}
803
		}
804
805 1301
		return $array;
806
	}
807
808
	/**
809
	 * Processes an array of 'select' clauses
810
	 *
811
	 * @param array $options Options
812
	 *
813
	 * @return array
814
	 */
815 1301
	protected function normalizeSelectClauses(array $options = []) {
816
817 1301
		$options = _elgg_normalize_plural_options_array($options, ['select']);
818
819 1301
		foreach ($options['selects'] as $key => $clause) {
820 28
			if (empty($clause)) {
821 1
				unset($options['selects'][$key]);
822 1
				continue;
823
			}
824
825 28
			if ($clause instanceof SelectClause) {
826 2
				continue;
827
			}
828
829 27
			$options['selects'][$key] = new SelectClause($clause);
830
		}
831
832 1301
		return $options;
833
	}
834
835
	/**
836
	 * Processes an array of 'where' clauses
837
	 *
838
	 * @param array $options Options
839
	 *
840
	 * @return array
841
	 */
842 1301
	protected function normalizeWhereClauses(array $options = []) {
843
844 1301
		$options = _elgg_normalize_plural_options_array($options, ['where']);
845
846 1301
		foreach ($options['wheres'] as $key => $clause) {
847 481
			if (empty($clause)) {
848 1
				unset($options['wheres'][$key]);
849 1
				continue;
850
			}
851
852 481
			if ($clause instanceof WhereClause) {
853 3
				continue;
854
			}
855
856 481
			if (is_string($clause)) {
857 189
				elgg_deprecated_notice("
858
					Using literal MySQL statements in 'wheres' options parameter is deprecated.
859
					Instead use a closure that receives an instanceof of QueryBuilder
860
					and returns a composite DBAL expression
861
					
862 189
					{{ $clause }}
863 189
				", '3.0');
864
			}
865
866 481
			$options['wheres'][$key] = new WhereClause($clause);
867
		}
868
869 1301
		return $options;
870
	}
871
872
	/**
873
	 * Processes an array of 'joins' clauses
874
	 *
875
	 * @param array $options Options
876
	 *
877
	 * @return array
878
	 * @throws \InvalidParameterException
879
	 */
880 1301
	protected function normalizeJoinClauses(array $options = []) {
881
882 1301
		$options = _elgg_normalize_plural_options_array($options, ['join']);
883
884 1301
		foreach ($options['joins'] as $key => $join) {
885 7
			if (empty($join)) {
886 1
				unset($options['joins'][$key]);
887 1
				continue;
888
			}
889
890 7
			if ($join instanceof JoinClause) {
891 6
				continue;
892
			}
893
894 2
			if (is_string($join)) {
895 2
				preg_match('/((LEFT|INNER|RIGHT)\s+)?JOIN\s+(.*?)\s+((as\s+)?(.*?)\s+)ON\s+(.*)$/im', $join, $parts);
896
897 2
				$type = !empty($parts[2]) ? strtolower($parts[2]) : 'inner';
898 2
				$table = $parts[3];
899 2
				$alias = $parts[6];
900 2
				$condition = preg_replace('/\r|\n/', '', $parts[7]);
901
902 2
				$dbprefix = elgg_get_config('dbprefix');
903 2
				if (strpos($table, $dbprefix) === 0) {
904 2
					$table = substr($table, strlen($dbprefix));
905
				}
906
907 2
				elgg_deprecated_notice("
908
					Using literal MySQL statements in 'joins' options parameter is deprecated.
909
					Instead use a closure that receives an instanceof of QueryBuilder and returns an instanceof of JoinClause,
910
					also consider using one of the built-in methods in QueryBuilder.
911
					
912 2
					{{ $join }}
913 2
				", '3.0');
914
915 2
				$clause = new JoinClause($table, $alias, $condition, $type);
916 2
				$options['joins'][$key] = $clause;
917
			}
918
		}
919
920 1301
		return $options;
921
	}
922
923
	/**
924
	 * Processes an array of 'joins' clauses
925
	 *
926
	 * @param array $options Options
927
	 *
928
	 * @return array
929
	 */
930 1301
	protected function normalizeOrderByClauses(array $options = []) {
931
932 1301
		$order_by = $options['order_by'];
933 1301
		$options['order_by'] = [];
934
935 1301
		if (!empty($order_by)) {
936 650
			if (is_string($order_by)) {
937 483
				elgg_deprecated_notice("
938
					Using literal MySQL statements in 'order_by' options parameter is deprecated.
939
					Instead use an OrderByClause or array of them.
940
					
941 483
					{{ $order_by }}
942 483
				", '3.0');
943
944 483
				$orders = explode(',', $order_by);
945 644
			} else if (is_array($order_by)) {
946 643
				$orders = $order_by;
947
			} else {
948 1
				$orders = [$order_by];
949
			}
950
951 650
			foreach ($orders as $order) {
952 650
				if ($order instanceof OrderByClause) {
953 644
					$options['order_by'][] = $order;
954 644
					continue;
955
				}
956
957 484
				$order = trim($order);
958 484
				if (preg_match('/(.*)(?=\s+(asc|desc))/i', $order, $parts)) {
959 319
					$column = $parts[1];
960 319
					$direction = $parts[2];
961
				} else {
962 180
					$column = $order;
963 180
					$direction = 'ASC';
964
				}
965
966 484
				$direction = in_array(strtoupper($direction), [
967 484
					'ASC',
968
					'DESC'
969 484
				]) ? strtoupper($direction) : 'ASC';
970
971 484
				$options['order_by'][] = new OrderByClause($column, $direction);
972
			}
973
		}
974
975 1301
		foreach ($options['sort_by'] as $key => $value) {
976 8
			$clause = new EntitySortByClause();
977 8
			$clause->property = elgg_extract('property', $value);
978 8
			$clause->property_type = elgg_extract('property_type', $value);
979 8
			$clause->join_type = elgg_extract('join_type', $value, 'inner');
980 8
			$clause->direction = elgg_extract('direction', $value);
981 8
			$clause->signed = elgg_extract('signed', $value);
982
983 8
			$options['order_by'][] = $clause;
984
		}
985
986 1301
		return $options;
987
	}
988
989
	/**
990
	 * Normalize 'group_by' statements
991
	 *
992
	 * @param array $options Options
993
	 *
994
	 * @return array
995
	 */
996 1301
	protected function normalizeGroupByClauses(array $options = []) {
997
998 1301
		if (!isset($options['having'])) {
999 1297
			$options['having'] = [];
1000
		} else {
1001 313
			if (!is_array($options['having'])) {
1002 2
				$options['having'] = [$options['having']];
1003
			}
1004
1005 313
			foreach ($options['having'] as $key => $expr) {
1006 7
				if ($expr instanceof HavingClause) {
1007 1
					continue;
1008
				}
1009
1010 6
				$options['having'][$key] = new HavingClause($expr);
1011
			}
1012
		}
1013
1014 1301
		if (empty($options['group_by'])) {
1015 1296
			$options['group_by'] = [];
1016
		}
1017
1018 1301
		if (is_string($options['group_by'])) {
1019 13
			$clause = $options['group_by'];
1020
1021 13
			$options['group_by'] = explode(',', $options['group_by']);
1022
1023 13
			if (count($options['group_by']) > 1) {
1024 5
				elgg_deprecated_notice("
1025
					Using literal MySQL statements in 'group_by' options parameter is deprecated.
1026
					Instead use a closure that receives an instanceof of QueryBuilder
1027
					and returns a prepared clause.
1028
					
1029 5
					{{ $clause }}
1030 5
				", '3.0');
1031
			}
1032
		}
1033
1034 1301
		$options['group_by'] = array_map(function ($e) {
1035 19
			if (!$e instanceof GroupByClause) {
1036 18
				$e = new GroupByClause(trim($e));
1037
			}
1038
1039 19
			return $e;
1040 1301
		}, $options['group_by']);
1041
1042
		return $options;
1043
	}
1044
}
1045