Security update for Core, with self-updated composer
[yaffs-website] / web / core / lib / Drupal / Core / Config / Entity / Query / Query.php
1 <?php
2
3 namespace Drupal\Core\Config\Entity\Query;
4
5 use Drupal\Core\Config\ConfigFactoryInterface;
6 use Drupal\Core\Entity\EntityTypeInterface;
7 use Drupal\Core\Entity\Query\QueryBase;
8 use Drupal\Core\Entity\Query\QueryInterface;
9 use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
10
11 /**
12  * Defines the entity query for configuration entities.
13  */
14 class Query extends QueryBase implements QueryInterface {
15
16   /**
17    * Information about the entity type.
18    *
19    * @var \Drupal\Core\Config\Entity\ConfigEntityTypeInterface
20    */
21   protected $entityType;
22
23   /**
24    * The config factory used by the config entity query.
25    *
26    * @var \Drupal\Core\Config\ConfigFactoryInterface
27    */
28   protected $configFactory;
29
30   /**
31    * The key value factory.
32    *
33    * @var \Drupal\Core\KeyValueStore\KeyValueFactoryInterface
34    */
35   protected $keyValueFactory;
36
37   /**
38    * Constructs a Query object.
39    *
40    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
41    *   The entity type definition.
42    * @param string $conjunction
43    *   - AND: all of the conditions on the query need to match.
44    *   - OR: at least one of the conditions on the query need to match.
45    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
46    *   The config factory.
47    * @param \Drupal\Core\KeyValueStore\KeyValueFactoryInterface $key_value_factory
48    *   The key value factory.
49    * @param array $namespaces
50    *   List of potential namespaces of the classes belonging to this query.
51    */
52   public function __construct(EntityTypeInterface $entity_type, $conjunction, ConfigFactoryInterface $config_factory, KeyValueFactoryInterface $key_value_factory, array $namespaces) {
53     parent::__construct($entity_type, $conjunction, $namespaces);
54     $this->configFactory = $config_factory;
55     $this->keyValueFactory = $key_value_factory;
56   }
57
58   /**
59    * Overrides \Drupal\Core\Entity\Query\QueryBase::condition().
60    *
61    * Additional to the syntax defined in the QueryInterface you can use
62    * placeholders (*) to match all keys of an subarray. Let's take the follow
63    * yaml file as example:
64    * @code
65    *  level1:
66    *    level2a:
67    *      level3: 1
68    *    level2b:
69    *      level3: 2
70    * @endcode
71    * Then you can filter out via $query->condition('level1.*.level3', 1).
72    */
73   public function condition($property, $value = NULL, $operator = NULL, $langcode = NULL) {
74     return parent::condition($property, $value, $operator, $langcode);
75   }
76
77   /**
78    * {@inheritdoc}
79    */
80   public function execute() {
81     // Load the relevant config records.
82     $configs = $this->loadRecords();
83
84     // Apply conditions.
85     $result = $this->condition->compile($configs);
86
87     // Apply sort settings.
88     foreach ($this->sort as $sort) {
89       $direction = $sort['direction'] == 'ASC' ? -1 : 1;
90       $field = $sort['field'];
91       uasort($result, function ($a, $b) use ($field, $direction) {
92         return ($a[$field] <= $b[$field]) ? $direction : -$direction;
93       });
94     }
95
96     // Let the pager do its work.
97     $this->initializePager();
98
99     if ($this->range) {
100       $result = array_slice($result, $this->range['start'], $this->range['length'], TRUE);
101     }
102     if ($this->count) {
103       return count($result);
104     }
105
106     // Create the expected structure of entity_id => entity_id. Config
107     // entities have string entity IDs.
108     foreach ($result as $key => &$value) {
109       $value = (string) $key;
110     }
111     return $result;
112   }
113
114   /**
115    * Loads the config records to examine for the query.
116    *
117    * @return array
118    *   Config records keyed by entity IDs.
119    */
120   protected function loadRecords() {
121     $prefix = $this->entityType->getConfigPrefix() . '.';
122     $prefix_length = strlen($prefix);
123
124     // Search the conditions for restrictions on configuration object names.
125     $names = FALSE;
126     $id_condition = NULL;
127     $id_key = $this->entityType->getKey('id');
128     if ($this->condition->getConjunction() == 'AND') {
129       $lookup_keys = $this->entityType->getLookupKeys();
130       $conditions = $this->condition->conditions();
131       foreach ($conditions as $condition_key => $condition) {
132         $operator = $condition['operator'] ?: (is_array($condition['value']) ? 'IN' : '=');
133         if (is_string($condition['field']) && ($operator == 'IN' || $operator == '=')) {
134           // Special case ID lookups.
135           if ($condition['field'] == $id_key) {
136             $ids = (array) $condition['value'];
137             $names = array_map(function ($id) use ($prefix) {
138               return $prefix . $id;
139             }, $ids);
140           }
141           elseif (in_array($condition['field'], $lookup_keys)) {
142             // If we don't find anything then there are no matches. No point in
143             // listing anything.
144             $names = [];
145             $keys = (array) $condition['value'];
146             $keys = array_map(function ($value) use ($condition) {
147               return $condition['field'] . ':' . $value;
148             }, $keys);
149             foreach ($this->getConfigKeyStore()->getMultiple($keys) as $list) {
150               $names = array_merge($names, $list);
151             }
152           }
153         }
154         // Save the first ID condition that is not an 'IN' or '=' for narrowing
155         // down later.
156         elseif (!$id_condition && $condition['field'] == $id_key) {
157           $id_condition = $condition;
158         }
159         // We stop at the first restricting condition on name. In the case where
160         // there are additional restricting conditions, results will be
161         // eliminated when the conditions are checked on the loaded records.
162         if ($names !== FALSE) {
163           // If the condition has been responsible for narrowing the list of
164           // configuration to check there is no point in checking it further.
165           unset($conditions[$condition_key]);
166           break;
167         }
168       }
169     }
170     // If no restrictions on IDs were found, we need to parse all records.
171     if ($names === FALSE) {
172       $names = $this->configFactory->listAll($prefix);
173     }
174     // In case we have an ID condition, try to narrow down the list of config
175     // objects to load.
176     if ($id_condition && !empty($names)) {
177       $value = $id_condition['value'];
178       $filter = NULL;
179       switch ($id_condition['operator']) {
180         case '<>':
181           $filter = function ($name) use ($value, $prefix_length) {
182             $id = substr($name, $prefix_length);
183             return $id !== $value;
184           };
185           break;
186         case 'STARTS_WITH':
187           $filter = function ($name) use ($value, $prefix_length) {
188             $id = substr($name, $prefix_length);
189             return strpos($id, $value) === 0;
190           };
191           break;
192         case 'CONTAINS':
193           $filter = function ($name) use ($value, $prefix_length) {
194             $id = substr($name, $prefix_length);
195             return strpos($id, $value) !== FALSE;
196           };
197           break;
198         case 'ENDS_WITH':
199           $filter = function ($name) use ($value, $prefix_length) {
200             $id = substr($name, $prefix_length);
201             return strrpos($id, $value) === strlen($id) - strlen($value);
202           };
203           break;
204       }
205       if ($filter) {
206         $names = array_filter($names, $filter);
207       }
208     }
209
210     // Load the corresponding records.
211     $records = [];
212     foreach ($this->configFactory->loadMultiple($names) as $config) {
213       $records[substr($config->getName(), $prefix_length)] = $config->get();
214     }
215     return $records;
216   }
217
218   /**
219    * Gets the key value store used to store fast lookups.
220    *
221    * @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
222    *   The key value store used to store fast lookups.
223    */
224   protected function getConfigKeyStore() {
225     return $this->keyValueFactory->get(QueryFactory::CONFIG_LOOKUP_PREFIX . $this->entityTypeId);
226   }
227
228 }