Issues (28)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Criteria.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * This software package is licensed under AGPL or Commercial license.
5
 *
6
 * @package   maslosoft/mangan
7
 * @licence   AGPL or Commercial
8
 * @copyright Copyright (c) Piotr MaseÅ‚kowski <[email protected]>
9
 * @copyright Copyright (c) Maslosoft
10
 * @copyright Copyright (c) Others as mentioned in code
11
 * @link      https://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan;
15
16
use Exception;
17
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
18
use Maslosoft\Mangan\Criteria\ConditionDecorator;
19
use Maslosoft\Mangan\Criteria\Conditions;
20
use Maslosoft\Mangan\Interfaces\Criteria\DecoratableInterface;
21
use Maslosoft\Mangan\Interfaces\Criteria\LimitableInterface;
22
use Maslosoft\Mangan\Interfaces\Criteria\MergeableInterface;
23
use Maslosoft\Mangan\Interfaces\Criteria\SelectableInterface;
24
use Maslosoft\Mangan\Interfaces\Criteria\SortableInterface;
25
use Maslosoft\Mangan\Interfaces\CriteriaInterface;
26
use Maslosoft\Mangan\Interfaces\ModelAwareInterface;
27
use Maslosoft\Mangan\Interfaces\SortInterface;
28
use Maslosoft\Mangan\Traits\Criteria\CursorAwareTrait;
29
use Maslosoft\Mangan\Traits\Criteria\DecoratableTrait;
30
use Maslosoft\Mangan\Traits\Criteria\LimitableTrait;
31
use Maslosoft\Mangan\Traits\Criteria\SelectableTrait;
32
use Maslosoft\Mangan\Traits\Criteria\SortableTrait;
33
use Maslosoft\Mangan\Traits\ModelAwareTrait;
34
use UnexpectedValueException;
35
36
/**
37
 * Criteria
38
 *
39
 * This class is a helper for building MongoDB query arrays, it support three syntaxes for adding conditions:
40
 *
41
 * 1. 'equals' syntax:
42
 *    $criteriaObject->fieldName = $value; // this will produce fieldName == value query
43
 * 2. fieldName call syntax
44
 *    $criteriaObject->fieldName($operator, $value); // this will produce fieldName <operator> value
45
 *    $criteriaObject->fieldName($value); // this will produce fieldName == value
46
 * 3. addCond method
47
 *    $criteriaObject->addCond($fieldName, $operator, $vale); // this will produce fieldName <operator> value
48
 *
49
 * For operators list {@see Criteria::$operators}
50
 *
51
 * @author    Ianaré Sévi
52
 * @author    Dariusz Górecki <[email protected]>
53
 * @author    Invenzzia Group, open-source division of CleverIT company http://www.invenzzia.org
54
 * @copyright 2011 CleverIT http://www.cleverit.com.pl
55
 * @license   New BSD license
56
 */
57
class Criteria implements CriteriaInterface,
58
	ModelAwareInterface
59
{
60
61
	use CursorAwareTrait,
62
		DecoratableTrait,
63
		LimitableTrait,
64
		ModelAwareTrait,
65
		SelectableTrait,
66
		SortableTrait;
67
68
	/**
69
	 * @since v1.0
70
	 * @var array $operators supported operators lists
71
	 */
72
	public static $operators = [
73
		// Comparison
74
		// Matches values that are equal to a specified value.
75
		'eq' => '$eq',
76
		'equals' => '$eq',
77
		'==' => '$eq',
78
		// Matches values that are greater than a specified value.
79
		'gt' => '$gt',
80
		'greater' => '$gt',
81
		'>' => '$gt',
82
		// Matches values that are greater than or equal to a specified value.
83
		'gte' => '$gte',
84
		'greatereq' => '$gte',
85
		'>=' => '$gte',
86
		// Matches values that are less than a specified value.
87
		'lt' => '$lt',
88
		'less' => '$lt',
89
		'<' => '$lt',
90
		// Matches values that are less than or equal to a specified value.
91
		'lte' => '$lte',
92
		'lesseq' => '$lte',
93
		'<=' => '$lte',
94
		// Matches all values that are not equal to a specified value.
95
		'ne' => '$ne',
96
		'noteq' => '$ne',
97
		'!=' => '$ne',
98
		'<>' => '$ne',
99
		// Matches any of the values specified in an array.
100
		'in' => '$in',
101
		// Matches none of the values specified in an array.
102
		'notin' => '$nin',
103
		// Logical
104
		// Joins query clauses with a logical OR returns all documents that match the conditions of either clause.
105
		'or' => '$or',
106
		// Joins query clauses with a logical AND returns all documents that match the conditions of both clauses.
107
		'and' => '$and',
108
		// Inverts the effect of a query expression and returns documents that do not match the query expression.
109
		'not' => '$not',
110
		// Joins query clauses with a logical NOR returns all documents that fail to match both clauses.
111
		'nor' => '$nor',
112
		// Element
113
		// Matches documents that have the specified field.
114
		'exists' => '$exists',
115
		'notexists' => '$exists',
116
		// Selects documents if a field is of the specified type.
117
		'type' => '$type',
118
		// Evaluation
119
		// Performs a modulo operation on the value of a field and selects documents with a specified result.
120
		'mod' => '$mod',
121
		'%' => '$mod',
122
		// Selects documents where values match a specified regular expression.
123
		'regex' => '$regex',
124
		// Performs text search.
125
		'text' => '$text',
126
		// Matches documents that satisfy a JavaScript expression.
127
		'where' => '$where',
128
		// Geospatial
129
		// Selects geometries within a bounding GeoJSON geometry. The `2dsphere` and `2d` indexes support $geoWithin.
130
		'geoWithin' => '$geoWithin',
131
		// Selects geometries that intersect with a GeoJSON geometry. The `2dsphere` index supports $geoIntersects.
132
		'geoIntersects' => '$geoIntersects',
133
		// Returns geospatial objects in proximity to a point. Requires a geospatial index. The `2dsphere` and `2d` indexes support $near.
134
		'near' => '$near',
135
		// Returns geospatial objects in proximity to a point on a sphere. Requires a geospatial index. The `2dsphere` and `2d` indexes support $nearSphere.
136
		'nearSphere' => '$nearSphere',
137
		// Array
138
		// Matches arrays that contain all elements specified in the query.
139
		'all' => '$all',
140
		// Selects documents if element in the array field matches all the specified $elemMatch conditions.
141
		'elemmatch' => '$elemMatch',
142
		// Selects documents if the array field is a specified size.
143
		'size' => '$size',
144
		// Comments
145
		'comment' => '$comment'
146
	];
147
148
	/**
149
	 * Sort Ascending
150
	 */
151
	const SortAsc = SortInterface::SortAsc;
152
153
	/**
154
	 * Sort Descending
155
	 */
156
	const SortDesc = SortInterface::SortDesc;
157
158
	/**
159
	 * Sort Ascending
160
	 * @deprecated since version 4.0.7
161
	 */
162
	const SORT_ASC = SortInterface::SortAsc;
163
164
	/**
165
	 * Sort Descending
166
	 * @deprecated since version 4.0.7
167
	 */
168
	const SORT_DESC = SortInterface::SortDesc;
169
170
	private $_conditions = [];
171
172
	/**
173
	 * Raw conditions array
174
	 * @var mixed[]
175
	 */
176
	private $_rawConds = [];
177
178
	/**
179
	 * Currently used fields list. This is
180
	 * used to allow chained criteria creation.
181
	 *
182
	 * Example:
183
	 *
184
	 * ```
185
	 * $criteria->address->city->street->number = 666
186
	 * ```
187
	 *
188
	 * Will result in conditions:
189
	 *
190
	 * ```
191
	 * [
192
	 *    'address.city.street.number' = 666
193
	 * ]
194
	 * ```
195
	 *
196
	 * @var array
197
	 */
198
	private $_workingFields = [];
199
200
	/**
201
	 * Constructor
202
	 * Example criteria:
203
	 *
204
	 * <pre>
205
	 * $criteria = new Criteria(
206
	 * [
207
	 *    'conditions'=> [
208
	 *        'fieldName1' => ['greater' => 0],
209
	 *        'fieldName2' => ['>=' => 10],
210
	 *        'fieldName3' => ['<' => 10],
211
	 *        'fieldName4' => ['lessEq' => 10],
212
	 *        'fieldName5' => ['notEq' => 10],
213
	 *        'fieldName6' => ['in' => [10, 9]],
214
	 *        'fieldName7' => ['notIn' => [10, 9]],
215
	 *        'fieldName8' => ['all' => [10, 9]],
216
	 *        'fieldName9' => ['size' => 10],
217
	 *        'fieldName10' => ['exists'],
218
	 *        'fieldName11' => ['notExists'],
219
	 *        'fieldName12' => ['mod' => [10, 9]],
220
	 *        'fieldName13' => ['==' => 1]
221
	 *    ],
222
	 *    'select' => [
223
	 *        'fieldName',
224
	 *        'fieldName2'
225
	 *    ],
226
	 *    'limit' => 10,
227
	 *    'offset' => 20,
228
	 *    'sort'=>[
229
	 *        'fieldName1' => Criteria::SortAsc,
230
	 *        'fieldName2' => Criteria::SortDesc,
231
	 *    ]
232
	 * ]
233
	 * );
234
	 * </pre>
235
	 * @param mixed|CriteriaInterface|Conditions $criteria
236
	 * @param AnnotatedInterface|null Model to use for criteria decoration
237
	 * @throws Exception
238
	 */
239 140
	public function __construct($criteria = null, AnnotatedInterface $model = null)
240
	{
241 140
		if (!empty($model))
242
		{
243 24
			$this->setModel($model);
244
		}
245 140
		$this->setCd(new ConditionDecorator($model));
246
247 140
		if (!is_null($criteria) && !is_array($criteria) && !is_object($criteria))
248
		{
249
			$msg = sprintf('Criteria require array or another Criteria object however was provided: %s', $criteria);
250
			throw new UnexpectedValueException($msg);
251
		}
252
253 140
		if (is_array($criteria))
254
		{
255 5
			$available = ['conditions', 'select', 'limit', 'offset', 'sort', 'useCursor'];
256
257 5
			$diff = array_diff_key($criteria, array_flip($available));
258 5
			if (!empty($diff))
259
			{
260
				$params = [
261
					'[' . implode(', ', $available) . ']',
262
					'[' . implode(', ', array_keys($criteria)) . ']'
263
				];
264
				$msg = vsprintf('Allowed criteria keys are: %s, however was provided: %s', $params);
265
				throw new UnexpectedValueException($msg);
266
			}
267
268 5
			if (isset($criteria['conditions']))
269
			{
270 5
				foreach ($criteria['conditions'] as $fieldName => $conditions)
271
				{
272 5
					assert(is_array($conditions), new UnexpectedValueException('Each condition must be array with operator as key and value, ie: ["_id" => ["==" => "123"]]'));
273 5
					foreach ($conditions as $operator => $value)
274
					{
275 5
						$operator = strtolower($operator);
276 5
						if (!isset(self::$operators[$operator]))
277
						{
278
							$params = [
279
								$operator,
280
								$fieldName
281
							];
282
							$msg = vsprintf('Unknown Criteria operator `%s` for `%s`', $params);
283
							throw new UnexpectedValueException($msg);
284
						}
285 5
						$this->addCond($fieldName, $operator, $value);
286
					}
287
				}
288
			}
289
290 5
			if (isset($criteria['select']))
291
			{
292
				$this->select($criteria['select']);
293
			}
294 5
			if (isset($criteria['limit']))
295
			{
296
				$this->limit($criteria['limit']);
297
			}
298 5
			if (isset($criteria['offset']))
299
			{
300
				$this->offset($criteria['offset']);
301
			}
302 5
			if (isset($criteria['sort']))
303
			{
304
				$this->setSort($criteria['sort']);
305
			}
306 5
			if (isset($criteria['useCursor']))
307
			{
308 5
				$this->setUseCursor($criteria['useCursor']);
309
			}
310
		}
311
		// NOTE:
312
		// Scrunitizer: $criteria is of type object<Maslosoft\Mangan\...ria\MergeableInterface>, but the function expects a array|object<Maslosoft\M...aces\CriteriaInterface>.
313
		// But for now it should be this way to easily distinguish from Conditions.
314
		// Future plan: Use CriteriaInterface here, and drop `$criteria instanceof Conditions` if clause. Conditions should implement CriteriaInterface too.
315 138
		elseif ($criteria instanceof MergeableInterface)
316
		{
317
			assert($criteria instanceof CriteriaInterface);
318
			$this->mergeWith($criteria);
319
		}
320 138
		elseif ($criteria instanceof Conditions)
321
		{
322
			$this->setConditions($criteria);
323
		}
324 140
	}
325
326
	/**
327
	 * Merge with other criteria
328
	 * - Field list operators will be merged
329
	 * - Limit and offset will be overridden
330
	 * - Select fields list will be merged
331
	 * - Sort fields list will be merged
332
	 * @param null|array|CriteriaInterface $criteria
333
	 * @return $this
334
	 * @throws Exception
335
	 */
336 135
	public function mergeWith($criteria)
337
	{
338 135
		if (is_array($criteria))
339
		{
340 1
			$criteria = new static($criteria, $this->getModel());
341
		}
342
		elseif (empty($criteria))
343
		{
344 130
			return $this;
345
		}
346
347
		// Set current criteria model if available
348 135
		$model = $this->getModel();
349
350
		// Fall back to merged criteria model
351 135
		if (empty($model))
352
		{
353 127
			$model = $criteria->getModel();
354 127
			if (!empty($model))
355
			{
356 4
				$this->setModel($model);
357
			}
358
		}
359
360
		// Use same model for decorating both criteria,
361
		// current one and merged one
362 135
		if (!empty($model))
363
		{
364 24
			if ($criteria instanceof DecoratableInterface)
365
			{
366 24
				$criteria->decorateWith($model);
367
			}
368
369 24
			if ($criteria instanceof ModelAwareInterface)
370
			{
371 24
				$criteria->setModel($model);
372
			}
373
374 24
			if ($this instanceof DecoratableInterface)
375
			{
376 24
				$this->decorateWith($model);
377
			}
378
379 24
			if ($this instanceof ModelAwareInterface)
380
			{
381 24
				$this->setModel($model);
382
			}
383
		}
384
385
386 135
		if ($this instanceof LimitableInterface && $criteria instanceof LimitableInterface && !empty($criteria->getLimit()))
387
		{
388
			$this->setLimit($criteria->getLimit());
389
		}
390 135
		if ($this instanceof LimitableInterface && $criteria instanceof LimitableInterface && !empty($criteria->getOffset()))
391
		{
392
			$this->setOffset($criteria->getOffset());
393
		}
394 135
		if ($this instanceof SortableInterface && $criteria instanceof SortableInterface && !empty($criteria->getSort()))
395
		{
396
			$this->setSort($criteria->getSort());
397
		}
398 135
		if ($this instanceof SelectableInterface && $criteria instanceof SelectableInterface && !empty($criteria->getSelect()))
399
		{
400
			$this->select($criteria->getSelect());
401
		}
402
403
404 135
		$this->_conditions = $this->_mergeConditions($this->_conditions, $criteria->getConditions());
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->_mergeConditions(...teria->getConditions()) of type * is incompatible with the declared type array of property $_conditions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
405 135
		return $this;
406
	}
407
408
	/**
409
	 * Internal method for merging `_conditions` with `getConditions` call result.
410
	 * @param $source
411
	 * @param $conditions
412
	 * @return mixed Merged conditions array
413
	 */
414 140
	private function _mergeConditions($source, $conditions)
415
	{
416 140
		$opTable = array_values(self::$operators);
417 140
		foreach ($conditions as $fieldName => $conds)
418
		{
419
			if (
420 130
				is_array($conds) &&
421 130
				count(array_diff(array_keys($conds), $opTable)) == 0
422
			)
423
			{
424 13
				if (isset($source[$fieldName]) && is_array($source[$fieldName]))
425
				{
426 2
					foreach ($source[$fieldName] as $operator => $value)
427
					{
428 2
						if (!in_array($operator, $opTable))
429
						{
430
							unset($source[$fieldName][$operator]);
431
						}
432
					}
433
				}
434
				else
435
				{
436 13
					$source[$fieldName] = [];
437
				}
438
439 13
				foreach ($conds as $operator => $value)
440
				{
441 13
					$source[$fieldName][$operator] = $value;
442
				}
443
			}
444
			else
445
			{
446 130
				$source[$fieldName] = $conds;
447
			}
448
		}
449 140
		return $source;
450
	}
451
452
	/**
453
	 * By-call-syntax criteria handler
454
	 *
455
	 * @param $fieldName
456
	 * @param mixed $parameters
457
	 * @return $this
458
	 */
459 1
	public function __call($fieldName, $parameters)
460
	{
461 1
		$operatorName = self::$operators['eq'];
462
463
		// Call with operator and value. Set
464
		// first param to be operator.
465 1
		if (array_key_exists(0, $parameters) && array_key_exists(1, $parameters))
466
		{
467 1
			$operatorName = strtolower($parameters[0]);
468
		}
469
470
		// Call without operator, use value only
471 1
		if (array_key_exists(0, $parameters) && !array_key_exists(1, $parameters))
472
		{
473
			$value = $parameters[0];
474
		}
475
476
		// Call with operator and value, use second param as value
477 1
		if (array_key_exists(1, $parameters))
478
		{
479 1
			$value = $parameters[1];
480
		}
481
482
		// ???
483 1
		if (is_numeric($operatorName))
484
		{
485
			$operatorName = strtolower(trim($value));
486
			$value = (strtolower(trim($value)) === 'exists') ? true : false;
487
		}
488
489 1
		if (!in_array($operatorName, array_keys(self::$operators)))
490
		{
491
			throw new UnexpectedValueException("Unknown operator: `$operatorName` on field `$fieldName`");
492
		}
493
494
		/**
495
		 * Support for syntax:
496
		 *
497
		 * ```
498
		 * $criteria->fieldOne->subField('op', 'value')
499
		 * ```
500
		 */
501
502 1
		array_push($this->_workingFields, $fieldName);
503 1
		$fieldName = implode('.', $this->_workingFields);
504 1
		$this->_workingFields = [];
505 1
		switch ($operatorName)
506
		{
507 1
			case 'exists':
508
				$this->addCond($fieldName, $operatorName, true);
509
				break;
510 1
			case 'notexists':
511
				$this->addCond($fieldName, $operatorName, false);
512
				break;
513
			default:
514 1
				$this->addCond($fieldName, $operatorName, $value);
515
		}
516
517 1
		return $this;
518
	}
519
520
	/**
521
	 * This is required for chained criteria creating, ie
522
	 *
523
	 * ```
524
	 * $criteria->fieldOne->fieldTwo = 123;
525
	 * ```
526
	 *
527
	 * @param string $name
528
	 * @return $this
529
	 */
530 2
	public function __get($name)
531
	{
532 2
		array_push($this->_workingFields, $name);
533 2
		return $this;
534
	}
535
536
	/**
537
	 * By-set-syntax handler.
538
	 *
539
	 * This allows adding *equal* conditions by
540
	 * using field.
541
	 *
542
	 * Example:
543
	 *
544
	 * ```
545
	 * $criteria->userId = 1;
546
	 * ```
547
	 *
548
	 * @param string $name
549
	 * @param mixed $value
550
	 */
551 11
	public function __set($name, $value)
552
	{
553 11
		array_push($this->_workingFields, $name);
554 11
		$fieldList = implode('.', $this->_workingFields);
555 11
		$this->_workingFields = [];
556 11
		$this->addCond($fieldList, '==', $value);
557 11
	}
558
559
	/**
560
	 * Return query array
561
	 * @return array Query array
562
	 */
563 140
	public function getConditions()
564
	{
565 140
		$conditions = [];
566 140
		foreach ($this->_rawConds as $c)
567
		{
568 130
			$conditions = $this->_makeCond($c[Conditions::FieldName], $c[Conditions::Operator], $c[Conditions::Value], $conditions);
569
		}
570 140
		return $this->_mergeConditions($this->_conditions, $conditions);
571
	}
572
573
	/**
574
	 * Set conditions
575
	 * @param array|Conditions $conditions
576
	 * @return Criteria
577
	 */
578
	public function setConditions($conditions)
579
	{
580
		if ($conditions instanceof Conditions)
581
		{
582
			$this->_conditions = $conditions->get();
583
			return $this;
584
		}
585
		$this->_conditions = $conditions;
586
		return $this;
587
	}
588
589
	/**
590
	 * Add condition
591
	 * If specified field already has a condition, values will be merged
592
	 * duplicates will be overriden by new values!
593
	 *
594
	 * NOTE: Should NOT be part of interface
595
	 *
596
	 * @param string $fieldName
597
	 * @param string $op operator
598
	 * @param mixed  $value
599
	 * @return $this
600
	 */
601 130
	public function addCond($fieldName, $op, $value)
602
	{
603 130
		$this->_rawConds[] = [
604 130
			Conditions::FieldName => $fieldName,
605 130
			Conditions::Operator => $op,
606 130
			Conditions::Value => $value
607
		];
608 130
		return $this;
609
	}
610
611
	/**
612
	 * Get condition
613
	 * If specified field already has a condition, values will be merged
614
	 * duplicates will be overridden by new values!
615
	 * @see   getConditions
616
	 * @param string $fieldName
617
	 * @param string $op operator
618
	 * @param mixed  $value
619
	 * @param array  $conditions
620
	 * @return array
621
	 */
622 130
	private function _makeCond($fieldName, $op, $value, $conditions = [])
623
	{
624
		// For array values
625
		$arrayOperators = [
626 130
			'or',
627
			'in',
628
			'notin'
629
		];
630 130
		if (in_array($op, $arrayOperators))
631
		{
632
			// Ensure array
633 12
			if (!is_array($value))
634
			{
635 1
				$value = [$value];
636
			}
637
638
			// Decorate each value
639 12
			$values = [];
640 12
			foreach ($value as $val)
641
			{
642 12
				$decorated = $this->getCd()->decorate($fieldName, $val);
643 12
				$fieldName = key($decorated);
644 12
				$values[] = current($decorated);
645
			}
646 12
			$value = $values;
647
		}
648
		else
649
		{
650 130
			$decorated = $this->getCd()->decorate($fieldName, $value);
651 130
			$fieldName = key($decorated);
652 130
			$value = current($decorated);
653
		}
654
655
		// Apply operators
656 130
		$op = self::$operators[$op];
657
658 130
		if ($op == self::$operators['or'])
659
		{
660 1
			if (!isset($conditions[$op]))
661
			{
662 1
				$conditions[$op] = [];
663
			}
664 1
			$conditions[$op][] = [$fieldName => $value];
665
		}
666
		else
667
		{
668 130
			if (!isset($conditions[$fieldName]) && $op != self::$operators['equals'])
669
			{
670 13
				$conditions[$fieldName] = [];
671
			}
672
673 130
			if ($op != self::$operators['equals'])
674
			{
675
				if (
676 13
					!is_array($conditions[$fieldName]) ||
677 13
					count(array_diff(array_keys($conditions[$fieldName]), array_values(self::$operators))) > 0
678
				)
679
				{
680
					$conditions[$fieldName] = [];
681
				}
682 13
				$conditions[$fieldName][$op] = $value;
683
			}
684
			else
685
			{
686 130
				$conditions[$fieldName] = $value;
687
			}
688
		}
689 130
		return $conditions;
690
	}
691
692
}
693