Pull merge.
[yaffs-website] / web / core / lib / Drupal / Core / Database / Query / Condition.php
1 <?php
2
3 namespace Drupal\Core\Database\Query;
4
5 use Drupal\Core\Database\Connection;
6 use Drupal\Core\Database\InvalidQueryException;
7
8 /**
9  * Generic class for a series of conditions in a query.
10  */
11 class Condition implements ConditionInterface, \Countable {
12
13   /**
14    * Provides a map of condition operators to condition operator options.
15    */
16   protected static $conditionOperatorMap = [
17     'BETWEEN' => ['delimiter' => ' AND '],
18     'NOT BETWEEN' => ['delimiter' => ' AND '],
19     'IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
20     'NOT IN' => ['delimiter' => ', ', 'prefix' => '(', 'postfix' => ')'],
21     'IS NULL' => ['use_value' => FALSE],
22     'IS NOT NULL' => ['use_value' => FALSE],
23     // Use backslash for escaping wildcard characters.
24     'LIKE' => ['postfix' => " ESCAPE '\\\\'"],
25     'NOT LIKE' => ['postfix' => " ESCAPE '\\\\'"],
26     // Exists expects an already bracketed subquery as right hand part. Do
27     // not define additional brackets.
28     'EXISTS' => [],
29     'NOT EXISTS' => [],
30     // These ones are here for performance reasons.
31     '=' => [],
32     '<' => [],
33     '>' => [],
34     '>=' => [],
35     '<=' => [],
36   ];
37
38   /**
39    * Array of conditions.
40    *
41    * @var array
42    */
43   protected $conditions = [];
44
45   /**
46    * Array of arguments.
47    *
48    * @var array
49    */
50   protected $arguments = [];
51
52   /**
53    * Whether the conditions have been changed.
54    *
55    * TRUE if the condition has been changed since the last compile.
56    * FALSE if the condition has been compiled and not changed.
57    *
58    * @var bool
59    */
60   protected $changed = TRUE;
61
62   /**
63    * The identifier of the query placeholder this condition has been compiled against.
64    */
65   protected $queryPlaceholderIdentifier;
66
67   /**
68    * Contains the string version of the Condition.
69    *
70    * @var string
71    */
72   protected $stringVersion;
73
74   /**
75    * Constructs a Condition object.
76    *
77    * @param string $conjunction
78    *   The operator to use to combine conditions: 'AND' or 'OR'.
79    */
80   public function __construct($conjunction) {
81     $this->conditions['#conjunction'] = $conjunction;
82   }
83
84   /**
85    * Implements Countable::count().
86    *
87    * Returns the size of this conditional. The size of the conditional is the
88    * size of its conditional array minus one, because one element is the
89    * conjunction.
90    */
91   public function count() {
92     return count($this->conditions) - 1;
93   }
94
95   /**
96    * {@inheritdoc}
97    */
98   public function condition($field, $value = NULL, $operator = '=') {
99     if (empty($operator)) {
100       $operator = '=';
101     }
102     if (empty($value) && is_array($value)) {
103       throw new InvalidQueryException(sprintf("Query condition '%s %s ()' cannot be empty.", $field, $operator));
104     }
105
106     $this->conditions[] = [
107       'field' => $field,
108       'value' => $value,
109       'operator' => $operator,
110     ];
111
112     $this->changed = TRUE;
113
114     return $this;
115   }
116
117   /**
118    * {@inheritdoc}
119    */
120   public function where($snippet, $args = []) {
121     $this->conditions[] = [
122       'field' => $snippet,
123       'value' => $args,
124       'operator' => NULL,
125     ];
126     $this->changed = TRUE;
127
128     return $this;
129   }
130
131   /**
132    * {@inheritdoc}
133    */
134   public function isNull($field) {
135     return $this->condition($field, NULL, 'IS NULL');
136   }
137
138   /**
139    * {@inheritdoc}
140    */
141   public function isNotNull($field) {
142     return $this->condition($field, NULL, 'IS NOT NULL');
143   }
144
145   /**
146    * {@inheritdoc}
147    */
148   public function exists(SelectInterface $select) {
149     return $this->condition('', $select, 'EXISTS');
150   }
151
152   /**
153    * {@inheritdoc}
154    */
155   public function notExists(SelectInterface $select) {
156     return $this->condition('', $select, 'NOT EXISTS');
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public function &conditions() {
163     return $this->conditions;
164   }
165
166   /**
167    * {@inheritdoc}
168    */
169   public function arguments() {
170     // If the caller forgot to call compile() first, refuse to run.
171     if ($this->changed) {
172       return NULL;
173     }
174     return $this->arguments;
175   }
176
177   /**
178    * {@inheritdoc}
179    */
180   public function compile(Connection $connection, PlaceholderInterface $queryPlaceholder) {
181     // Re-compile if this condition changed or if we are compiled against a
182     // different query placeholder object.
183     if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) {
184       $this->queryPlaceholderIdentifier = $queryPlaceholder->uniqueIdentifier();
185
186       $condition_fragments = [];
187       $arguments = [];
188
189       $conditions = $this->conditions;
190       $conjunction = $conditions['#conjunction'];
191       unset($conditions['#conjunction']);
192       foreach ($conditions as $condition) {
193         // Process field.
194         if ($condition['field'] instanceof ConditionInterface) {
195           // Left hand part is a structured condition or a subquery. Compile,
196           // put brackets around it (if it is a query), and collect any
197           // arguments.
198           $condition['field']->compile($connection, $queryPlaceholder);
199           $field_fragment = (string) $condition['field'];
200           if ($condition['field'] instanceof SelectInterface) {
201             $field_fragment = '(' . $field_fragment . ')';
202           }
203           $arguments += $condition['field']->arguments();
204           // If the operator and value were not passed in to the
205           // @see ConditionInterface::condition() method (and thus have the
206           // default value as defined over there) it is assumed to be a valid
207           // condition on its own: ignore the operator and value parts.
208           $ignore_operator = $condition['operator'] === '=' && $condition['value'] === NULL;
209         }
210         elseif (!isset($condition['operator'])) {
211           // Left hand part is a literal string added with the
212           // @see ConditionInterface::where() method. Put brackets around
213           // the snippet and collect the arguments from the value part.
214           // Also ignore the operator and value parts.
215           $field_fragment = '(' . $condition['field'] . ')';
216           $arguments += $condition['value'];
217           $ignore_operator = TRUE;
218         }
219         else {
220           // Left hand part is a normal field. Add it as is.
221           $field_fragment = $connection->escapeField($condition['field']);
222           $ignore_operator = FALSE;
223         }
224
225         // Process operator.
226         if ($ignore_operator) {
227           $operator = ['operator' => '', 'use_value' => FALSE];
228         }
229         else {
230           // Remove potentially dangerous characters.
231           // If something passed in an invalid character stop early, so we
232           // don't rely on a broken SQL statement when we would just replace
233           // those characters.
234           if (stripos($condition['operator'], 'UNION') !== FALSE || strpbrk($condition['operator'], '[-\'"();') !== FALSE) {
235             $this->changed = TRUE;
236             $this->arguments = [];
237             // Provide a string which will result into an empty query result.
238             $this->stringVersion = '( AND 1 = 0 )';
239
240             // Conceptually throwing an exception caused by user input is bad
241             // as you result into a WSOD, which depending on your webserver
242             // configuration can result into the assumption that your site is
243             // broken.
244             // On top of that the database API relies on __toString() which
245             // does not allow to throw exceptions.
246             trigger_error('Invalid characters in query operator: ' . $condition['operator'], E_USER_ERROR);
247             return;
248           }
249
250           // For simplicity, we convert all operators to a data structure to
251           // allow to specify a prefix, a delimiter and such. Find the
252           // associated data structure by first doing a database specific
253           // lookup, followed by a specification according to the SQL standard.
254           $operator = $connection->mapConditionOperator($condition['operator']);
255           if (!isset($operator)) {
256             $operator = $this->mapConditionOperator($condition['operator']);
257           }
258           $operator += ['operator' => $condition['operator']];
259         }
260         // Add defaults.
261         $operator += [
262           'prefix' => '',
263           'postfix' => '',
264           'delimiter' => '',
265           'use_value' => TRUE,
266         ];
267         $operator_fragment = $operator['operator'];
268
269         // Process value.
270         $value_fragment = '';
271         if ($operator['use_value']) {
272           // For simplicity, we first convert to an array, so that we can handle
273           // the single and multi value cases the same.
274           if (!is_array($condition['value'])) {
275             if ($condition['value'] instanceof SelectInterface && ($operator['operator'] === 'IN' || $operator['operator'] === 'NOT IN')) {
276               // Special case: IN is followed by a single select query instead
277               // of a set of values: unset prefix and postfix to prevent double
278               // brackets.
279               $operator['prefix'] = '';
280               $operator['postfix'] = '';
281             }
282             $condition['value'] = [$condition['value']];
283           }
284           // Process all individual values.
285           $value_fragment = [];
286           foreach ($condition['value'] as $value) {
287             if ($value instanceof SelectInterface) {
288               // Right hand part is a subquery. Compile, put brackets around it
289               // and collect any arguments.
290               $value->compile($connection, $queryPlaceholder);
291               $value_fragment[] = '(' . (string) $value . ')';
292               $arguments += $value->arguments();
293             }
294             else {
295               // Right hand part is a normal value. Replace the value with a
296               // placeholder and add the value as an argument.
297               $placeholder = ':db_condition_placeholder_' . $queryPlaceholder->nextPlaceholder();
298               $value_fragment[] = $placeholder;
299               $arguments[$placeholder] = $value;
300             }
301           }
302           $value_fragment = $operator['prefix'] . implode($operator['delimiter'], $value_fragment) . $operator['postfix'];
303         }
304
305         // Concatenate the left hand part, operator and right hand part.
306         $condition_fragments[] = trim(implode(' ', [$field_fragment, $operator_fragment, $value_fragment]));
307       }
308
309       // Concatenate all conditions using the conjunction and brackets around
310       // the individual conditions to assure the proper evaluation order.
311       $this->stringVersion = count($condition_fragments) > 1 ? '(' . implode(") $conjunction (", $condition_fragments) . ')' : implode($condition_fragments);
312       $this->arguments = $arguments;
313       $this->changed = FALSE;
314     }
315   }
316
317   /**
318    * {@inheritdoc}
319    */
320   public function compiled() {
321     return !$this->changed;
322   }
323
324   /**
325    * Implements PHP magic __toString method to convert the conditions to string.
326    *
327    * @return string
328    *   A string version of the conditions.
329    */
330   public function __toString() {
331     // If the caller forgot to call compile() first, refuse to run.
332     if ($this->changed) {
333       return '';
334     }
335     return $this->stringVersion;
336   }
337
338   /**
339    * PHP magic __clone() method.
340    *
341    * Only copies fields that implement Drupal\Core\Database\Query\ConditionInterface. Also sets
342    * $this->changed to TRUE.
343    */
344   public function __clone() {
345     $this->changed = TRUE;
346     foreach ($this->conditions as $key => $condition) {
347       if ($key !== '#conjunction') {
348         if ($condition['field'] instanceof ConditionInterface) {
349           $this->conditions[$key]['field'] = clone($condition['field']);
350         }
351         if ($condition['value'] instanceof SelectInterface) {
352           $this->conditions[$key]['value'] = clone($condition['value']);
353         }
354       }
355     }
356   }
357
358   /**
359    * Gets any special processing requirements for the condition operator.
360    *
361    * Some condition types require special processing, such as IN, because
362    * the value data they pass in is not a simple value. This is a simple
363    * overridable lookup function.
364    *
365    * @param string $operator
366    *   The condition operator, such as "IN", "BETWEEN", etc. Case-sensitive.
367    *
368    * @return array
369    *   The extra handling directives for the specified operator or an empty
370    *   array if there are no extra handling directives.
371    */
372   protected function mapConditionOperator($operator) {
373     if (isset(static::$conditionOperatorMap[$operator])) {
374       $return = static::$conditionOperatorMap[$operator];
375     }
376     else {
377       // We need to upper case because PHP index matches are case sensitive but
378       // do not need the more expensive mb_strtoupper() because SQL statements
379       // are ASCII.
380       $operator = strtoupper($operator);
381       $return = isset(static::$conditionOperatorMap[$operator]) ? static::$conditionOperatorMap[$operator] : [];
382     }
383
384     $return += ['operator' => $operator];
385
386     return $return;
387   }
388
389   /**
390    * {@inheritdoc}
391    */
392   public function conditionGroupFactory($conjunction = 'AND') {
393     return new Condition($conjunction);
394   }
395
396   /**
397    * {@inheritdoc}
398    */
399   public function andConditionGroup() {
400     return $this->conditionGroupFactory('AND');
401   }
402
403   /**
404    * {@inheritdoc}
405    */
406   public function orConditionGroup() {
407     return $this->conditionGroupFactory('OR');
408   }
409
410 }