Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / web / core / lib / Drupal / Core / TypedData / Plugin / DataType / ItemList.php
1 <?php
2
3 namespace Drupal\Core\TypedData\Plugin\DataType;
4
5 use Drupal\Core\TypedData\ComplexDataInterface;
6 use Drupal\Core\TypedData\ListInterface;
7 use Drupal\Core\TypedData\TypedData;
8 use Drupal\Core\TypedData\TypedDataInterface;
9
10 /**
11  * A generic list class.
12  *
13  * This class can serve as list for any type of items and is used by default.
14  * Data types may specify the default list class in their definition, see
15  * Drupal\Core\TypedData\Annotation\DataType.
16  * Note: The class cannot be called "List" as list is a reserved PHP keyword.
17  *
18  * @ingroup typed_data
19  *
20  * @DataType(
21  *   id = "list",
22  *   label = @Translation("List of items"),
23  *   definition_class = "\Drupal\Core\TypedData\ListDataDefinition"
24  * )
25  */
26 class ItemList extends TypedData implements \IteratorAggregate, ListInterface {
27
28   /**
29    * Numerically indexed array of items.
30    *
31    * @var \Drupal\Core\TypedData\TypedDataInterface[]
32    */
33   protected $list = [];
34
35   /**
36    * {@inheritdoc}
37    */
38   public function getValue() {
39     $values = [];
40     foreach ($this->list as $delta => $item) {
41       $values[$delta] = $item->getValue();
42     }
43     return $values;
44   }
45
46   /**
47    * Overrides \Drupal\Core\TypedData\TypedData::setValue().
48    *
49    * @param array|null $values
50    *   An array of values of the field items, or NULL to unset the field.
51    */
52   public function setValue($values, $notify = TRUE) {
53     if (!isset($values) || $values === []) {
54       $this->list = [];
55     }
56     else {
57       // Only arrays with numeric keys are supported.
58       if (!is_array($values)) {
59         throw new \InvalidArgumentException('Cannot set a list with a non-array value.');
60       }
61       // Assign incoming values. Keys are renumbered to ensure 0-based
62       // sequential deltas. If possible, reuse existing items rather than
63       // creating new ones.
64       foreach (array_values($values) as $delta => $value) {
65         if (!isset($this->list[$delta])) {
66           $this->list[$delta] = $this->createItem($delta, $value);
67         }
68         else {
69           $this->list[$delta]->setValue($value, FALSE);
70         }
71       }
72       // Truncate extraneous pre-existing values.
73       $this->list = array_slice($this->list, 0, count($values));
74     }
75     // Notify the parent of any changes.
76     if ($notify && isset($this->parent)) {
77       $this->parent->onChange($this->name);
78     }
79   }
80
81   /**
82    * {@inheritdoc}
83    */
84   public function getString() {
85     $strings = [];
86     foreach ($this->list as $item) {
87       $strings[] = $item->getString();
88     }
89     // Remove any empty strings resulting from empty items.
90     return implode(', ', array_filter($strings, 'mb_strlen'));
91   }
92
93   /**
94    * {@inheritdoc}
95    */
96   public function get($index) {
97     if (!is_numeric($index)) {
98       throw new \InvalidArgumentException('Unable to get a value with a non-numeric delta in a list.');
99     }
100     // Automatically create the first item for computed fields.
101     // @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0.
102     // Use \Drupal\Core\TypedData\ComputedItemListTrait instead.
103     if ($index == 0 && !isset($this->list[0]) && $this->definition->isComputed()) {
104       @trigger_error('Automatically creating the first item for computed fields is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\TypedData\ComputedItemListTrait instead.', E_USER_DEPRECATED);
105       $this->list[0] = $this->createItem(0);
106     }
107     return isset($this->list[$index]) ? $this->list[$index] : NULL;
108   }
109
110   /**
111    * {@inheritdoc}
112    */
113   public function set($index, $value) {
114     if (!is_numeric($index)) {
115       throw new \InvalidArgumentException('Unable to set a value with a non-numeric delta in a list.');
116     }
117     // Ensure indexes stay sequential. We allow assigning an item at an existing
118     // index, or at the next index available.
119     if ($index < 0 || $index > count($this->list)) {
120       throw new \InvalidArgumentException('Unable to set a value to a non-subsequent delta in a list.');
121     }
122     // Support setting values via typed data objects.
123     if ($value instanceof TypedDataInterface) {
124       $value = $value->getValue();
125     }
126     // If needed, create the item at the next position.
127     $item = isset($this->list[$index]) ? $this->list[$index] : $this->appendItem();
128     $item->setValue($value);
129     return $this;
130   }
131
132   /**
133    * {@inheritdoc}
134    */
135   public function removeItem($index) {
136     if (isset($this->list) && array_key_exists($index, $this->list)) {
137       // Remove the item, and reassign deltas.
138       unset($this->list[$index]);
139       $this->rekey($index);
140     }
141     else {
142       throw new \InvalidArgumentException('Unable to remove item at non-existing index.');
143     }
144     return $this;
145   }
146
147   /**
148    * Renumbers the items in the list.
149    *
150    * @param int $from_index
151    *   Optionally, the index at which to start the renumbering, if it is known
152    *   that items before that can safely be skipped (for example, when removing
153    *   an item at a given index).
154    */
155   protected function rekey($from_index = 0) {
156     // Re-key the list to maintain consecutive indexes.
157     $this->list = array_values($this->list);
158     // Each item holds its own index as a "name", it needs to be updated
159     // according to the new list indexes.
160     for ($i = $from_index; $i < count($this->list); $i++) {
161       $this->list[$i]->setContext($i, $this);
162     }
163   }
164
165   /**
166    * {@inheritdoc}
167    */
168   public function first() {
169     return $this->get(0);
170   }
171
172   /**
173    * {@inheritdoc}
174    */
175   public function offsetExists($offset) {
176     // We do not want to throw exceptions here, so we do not use get().
177     return isset($this->list[$offset]);
178   }
179
180   /**
181    * {@inheritdoc}
182    */
183   public function offsetUnset($offset) {
184     $this->removeItem($offset);
185   }
186
187   /**
188    * {@inheritdoc}
189    */
190   public function offsetGet($offset) {
191     return $this->get($offset);
192   }
193
194   /**
195    * {@inheritdoc}
196    */
197   public function offsetSet($offset, $value) {
198     if (!isset($offset)) {
199       // The [] operator has been used.
200       $this->appendItem($value);
201     }
202     else {
203       $this->set($offset, $value);
204     }
205   }
206
207   /**
208    * {@inheritdoc}
209    */
210   public function appendItem($value = NULL) {
211     $offset = count($this->list);
212     $item = $this->createItem($offset, $value);
213     $this->list[$offset] = $item;
214     return $item;
215   }
216
217   /**
218    * Helper for creating a list item object.
219    *
220    * @return \Drupal\Core\TypedData\TypedDataInterface
221    */
222   protected function createItem($offset = 0, $value = NULL) {
223     return $this->getTypedDataManager()->getPropertyInstance($this, $offset, $value);
224   }
225
226   /**
227    * {@inheritdoc}
228    */
229   public function getItemDefinition() {
230     return $this->definition->getItemDefinition();
231   }
232
233   /**
234    * {@inheritdoc}
235    */
236   public function getIterator() {
237     return new \ArrayIterator($this->list);
238   }
239
240   /**
241    * {@inheritdoc}
242    */
243   public function count() {
244     return count($this->list);
245   }
246
247   /**
248    * {@inheritdoc}
249    */
250   public function isEmpty() {
251     foreach ($this->list as $item) {
252       if ($item instanceof ComplexDataInterface || $item instanceof ListInterface) {
253         if (!$item->isEmpty()) {
254           return FALSE;
255         }
256       }
257       // Other items are treated as empty if they have no value only.
258       elseif ($item->getValue() !== NULL) {
259         return FALSE;
260       }
261     }
262     return TRUE;
263   }
264
265   /**
266    * {@inheritdoc}
267    */
268   public function filter($callback) {
269     if (isset($this->list)) {
270       $removed = FALSE;
271       // Apply the filter, detecting if some items were actually removed.
272       $this->list = array_filter($this->list, function ($item) use ($callback, &$removed) {
273         if (call_user_func($callback, $item)) {
274           return TRUE;
275         }
276         else {
277           $removed = TRUE;
278         }
279       });
280       if ($removed) {
281         $this->rekey();
282       }
283     }
284     return $this;
285   }
286
287   /**
288    * {@inheritdoc}
289    */
290   public function onChange($delta) {
291     // Notify the parent of changes.
292     if (isset($this->parent)) {
293       $this->parent->onChange($this->name);
294     }
295   }
296
297   /**
298    * Magic method: Implements a deep clone.
299    */
300   public function __clone() {
301     foreach ($this->list as $delta => $item) {
302       $this->list[$delta] = clone $item;
303       $this->list[$delta]->setContext($delta, $this);
304     }
305   }
306
307 }