Condition.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <?php
  2. namespace Drupal\Core\Config\Entity\Query;
  3. use Drupal\Core\Entity\Query\ConditionBase;
  4. use Drupal\Core\Entity\Query\ConditionInterface;
  5. use Drupal\Core\Entity\Query\QueryException;
  6. /**
  7. * Defines the condition class for the config entity query.
  8. *
  9. * @see \Drupal\Core\Config\Entity\Query\Query
  10. */
  11. class Condition extends ConditionBase {
  12. /**
  13. * {@inheritdoc}
  14. */
  15. public function compile($configs) {
  16. $and = strtoupper($this->conjunction) == 'AND';
  17. $single_conditions = [];
  18. $condition_groups = [];
  19. foreach ($this->conditions as $condition) {
  20. if ($condition['field'] instanceof ConditionInterface) {
  21. $condition_groups[] = $condition;
  22. }
  23. else {
  24. if (!isset($condition['operator'])) {
  25. $condition['operator'] = is_array($condition['value']) ? 'IN' : '=';
  26. }
  27. // Lowercase condition value(s) for case-insensitive matches.
  28. if (is_array($condition['value'])) {
  29. $condition['value'] = array_map('mb_strtolower', $condition['value']);
  30. }
  31. elseif (!is_bool($condition['value'])) {
  32. $condition['value'] = mb_strtolower($condition['value']);
  33. }
  34. $single_conditions[] = $condition;
  35. }
  36. }
  37. $return = [];
  38. if ($single_conditions) {
  39. foreach ($configs as $config_name => $config) {
  40. foreach ($single_conditions as $condition) {
  41. $match = $this->matchArray($condition, $config, explode('.', $condition['field']));
  42. // If AND and it's not matching, then the rest of conditions do not
  43. // matter and this config object does not match.
  44. // If OR and it is matching, then the rest of conditions do not
  45. // matter and this config object does match.
  46. if ($and != $match) {
  47. break;
  48. }
  49. }
  50. if ($match) {
  51. $return[$config_name] = $config;
  52. }
  53. }
  54. }
  55. elseif (!$condition_groups || $and) {
  56. // If there were no single conditions then either:
  57. // - Complex conditions, OR: need to start from no entities.
  58. // - Complex conditions, AND: need to start from all entities.
  59. // - No complex conditions (AND/OR doesn't matter): need to return all
  60. // entities.
  61. $return = $configs;
  62. }
  63. foreach ($condition_groups as $condition) {
  64. $group_entities = $condition['field']->compile($configs);
  65. if ($and) {
  66. $return = array_intersect_key($return, $group_entities);
  67. }
  68. else {
  69. $return = $return + $group_entities;
  70. }
  71. }
  72. return $return;
  73. }
  74. /**
  75. * {@inheritdoc}
  76. */
  77. public function exists($field, $langcode = NULL) {
  78. return $this->condition($field, NULL, 'IS NOT NULL', $langcode);
  79. }
  80. /**
  81. * {@inheritdoc}
  82. */
  83. public function notExists($field, $langcode = NULL) {
  84. return $this->condition($field, NULL, 'IS NULL', $langcode);
  85. }
  86. /**
  87. * Matches for an array representing one or more config paths.
  88. *
  89. * @param array $condition
  90. * The condition array as created by the condition() method.
  91. * @param array $data
  92. * The config array or part of it.
  93. * @param array $needs_matching
  94. * The list of config array keys needing a match. Can contain config keys
  95. * and the * wildcard.
  96. * @param array $parents
  97. * The current list of parents.
  98. *
  99. * @return bool
  100. * TRUE when the condition matched to the data else FALSE.
  101. */
  102. protected function matchArray(array $condition, array $data, array $needs_matching, array $parents = []) {
  103. $parent = array_shift($needs_matching);
  104. if ($parent === '*') {
  105. $candidates = array_keys($data);
  106. }
  107. else {
  108. // Avoid a notice when calling match() later.
  109. if (!isset($data[$parent])) {
  110. $data[$parent] = NULL;
  111. }
  112. $candidates = [$parent];
  113. }
  114. foreach ($candidates as $key) {
  115. if ($needs_matching) {
  116. if (is_array($data[$key])) {
  117. $new_parents = $parents;
  118. $new_parents[] = $key;
  119. if ($this->matchArray($condition, $data[$key], $needs_matching, $new_parents)) {
  120. return TRUE;
  121. }
  122. }
  123. // If the parent does not exist, it's safe to say the actual property
  124. // we're checking for is also NULL.
  125. elseif ($condition['operator'] === 'IS NULL') {
  126. return TRUE;
  127. }
  128. }
  129. // Only try to match a scalar if there are no remaining keys in
  130. // $needs_matching as this indicates that we are looking for a specific
  131. // subkey and a scalar can never match that.
  132. elseif ($this->match($condition, $data[$key])) {
  133. return TRUE;
  134. }
  135. }
  136. return FALSE;
  137. }
  138. /**
  139. * Perform the actual matching.
  140. *
  141. * @param array $condition
  142. * The condition array as created by the condition() method.
  143. * @param string $value
  144. * The value to match against.
  145. *
  146. * @return bool
  147. * TRUE when matches else FALSE.
  148. */
  149. protected function match(array $condition, $value) {
  150. // "IS NULL" and "IS NOT NULL" conditions can also deal with array values,
  151. // so we return early for them to avoid problems.
  152. if (in_array($condition['operator'], ['IS NULL', 'IS NOT NULL'], TRUE)) {
  153. $should_be_set = $condition['operator'] === 'IS NOT NULL';
  154. return $should_be_set === isset($value);
  155. }
  156. if (isset($value)) {
  157. // We always want a case-insensitive match.
  158. if (!is_bool($value)) {
  159. $value = mb_strtolower($value);
  160. }
  161. switch ($condition['operator']) {
  162. case '=':
  163. return $value == $condition['value'];
  164. case '>':
  165. return $value > $condition['value'];
  166. case '<':
  167. return $value < $condition['value'];
  168. case '>=':
  169. return $value >= $condition['value'];
  170. case '<=':
  171. return $value <= $condition['value'];
  172. case '<>':
  173. return $value != $condition['value'];
  174. case 'IN':
  175. return array_search($value, $condition['value']) !== FALSE;
  176. case 'NOT IN':
  177. return array_search($value, $condition['value']) === FALSE;
  178. case 'STARTS_WITH':
  179. return strpos($value, $condition['value']) === 0;
  180. case 'CONTAINS':
  181. return strpos($value, $condition['value']) !== FALSE;
  182. case 'ENDS_WITH':
  183. return substr($value, -strlen($condition['value'])) === (string) $condition['value'];
  184. default:
  185. throw new QueryException('Invalid condition operator.');
  186. }
  187. }
  188. return FALSE;
  189. }
  190. }