Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / migrate / src / Plugin / migrate / destination / EntityContentBase.php
1 <?php
2
3 namespace Drupal\migrate\Plugin\migrate\destination;
4
5 use Drupal\Core\Entity\ContentEntityInterface;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\Entity\EntityManagerInterface;
8 use Drupal\Core\Entity\EntityStorageInterface;
9 use Drupal\Core\Field\FieldTypePluginManagerInterface;
10 use Drupal\Core\TypedData\TranslatableInterface;
11 use Drupal\Core\TypedData\TypedDataInterface;
12 use Drupal\migrate\Plugin\MigrationInterface;
13 use Drupal\migrate\MigrateException;
14 use Drupal\migrate\Plugin\MigrateIdMapInterface;
15 use Drupal\migrate\Row;
16 use Symfony\Component\DependencyInjection\ContainerInterface;
17
18 /**
19  * The destination class for all content entities lacking a specific class.
20  */
21 class EntityContentBase extends Entity {
22
23   /**
24    * Entity manager.
25    *
26    * @var \Drupal\Core\Entity\EntityManagerInterface
27    */
28   protected $entityManager;
29
30   /**
31    * Field type plugin manager.
32    *
33    * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
34    */
35   protected $fieldTypeManager;
36
37   /**
38    * Constructs a content entity.
39    *
40    * @param array $configuration
41    *   A configuration array containing information about the plugin instance.
42    * @param string $plugin_id
43    *   The plugin ID for the plugin instance.
44    * @param mixed $plugin_definition
45    *   The plugin implementation definition.
46    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
47    *   The migration entity.
48    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
49    *   The storage for this entity type.
50    * @param array $bundles
51    *   The list of bundles this entity type has.
52    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
53    *   The entity manager service.
54    * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
55    *   The field type plugin manager service.
56    */
57   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
58     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
59     $this->entityManager = $entity_manager;
60     $this->fieldTypeManager = $field_type_manager;
61   }
62
63   /**
64    * {@inheritdoc}
65    */
66   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
67     $entity_type = static::getEntityTypeId($plugin_id);
68     return new static(
69       $configuration,
70       $plugin_id,
71       $plugin_definition,
72       $migration,
73       $container->get('entity.manager')->getStorage($entity_type),
74       array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
75       $container->get('entity.manager'),
76       $container->get('plugin.manager.field.field_type')
77     );
78   }
79
80   /**
81    * {@inheritdoc}
82    */
83   public function import(Row $row, array $old_destination_id_values = []) {
84     $this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
85     $entity = $this->getEntity($row, $old_destination_id_values);
86     if (!$entity) {
87       throw new MigrateException('Unable to get entity');
88     }
89
90     $ids = $this->save($entity, $old_destination_id_values);
91     if (!empty($this->configuration['translations'])) {
92       $ids[] = $entity->language()->getId();
93     }
94     return $ids;
95   }
96
97   /**
98    * Saves the entity.
99    *
100    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
101    *   The content entity.
102    * @param array $old_destination_id_values
103    *   (optional) An array of destination ID values. Defaults to an empty array.
104    *
105    * @return array
106    *   An array containing the entity ID.
107    */
108   protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
109     $entity->save();
110     return [$entity->id()];
111   }
112
113   /**
114    * Get whether this destination is for translations.
115    *
116    * @return bool
117    *   Whether this destination is for translations.
118    */
119   protected function isTranslationDestination() {
120     return !empty($this->configuration['translations']);
121   }
122
123   /**
124    * {@inheritdoc}
125    */
126   public function getIds() {
127     $id_key = $this->getKey('id');
128     $ids[$id_key] = $this->getDefinitionFromEntity($id_key);
129
130     if ($this->isTranslationDestination()) {
131       if (!$langcode_key = $this->getKey('langcode')) {
132         throw new MigrateException('This entity type does not support translation.');
133       }
134       $ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
135     }
136
137     return $ids;
138   }
139
140   /**
141    * Updates an entity with the new values from row.
142    *
143    * @param \Drupal\Core\Entity\EntityInterface $entity
144    *   The entity to update.
145    * @param \Drupal\migrate\Row $row
146    *   The row object to update from.
147    *
148    * @return \Drupal\Core\Entity\EntityInterface|null
149    *   An updated entity, or NULL if it's the same as the one passed in.
150    */
151   protected function updateEntity(EntityInterface $entity, Row $row) {
152     $empty_destinations = $row->getEmptyDestinationProperties();
153     // By default, an update will be preserved.
154     $rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
155
156     // Make sure we have the right translation.
157     if ($this->isTranslationDestination()) {
158       $property = $this->storage->getEntityType()->getKey('langcode');
159       if ($row->hasDestinationProperty($property)) {
160         $language = $row->getDestinationProperty($property);
161         if (!$entity->hasTranslation($language)) {
162           $entity->addTranslation($language);
163
164           // We're adding a translation, so delete it on rollback.
165           $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
166         }
167         $entity = $entity->getTranslation($language);
168       }
169     }
170
171     // If the migration has specified a list of properties to be overwritten,
172     // clone the row with an empty set of destination values, and re-add only
173     // the specified properties.
174     if (isset($this->configuration['overwrite_properties'])) {
175       $empty_destinations = array_intersect($empty_destinations, $this->configuration['overwrite_properties']);
176       $clone = $row->cloneWithoutDestination();
177       foreach ($this->configuration['overwrite_properties'] as $property) {
178         $clone->setDestinationProperty($property, $row->getDestinationProperty($property));
179       }
180       $row = $clone;
181     }
182
183     foreach ($row->getDestination() as $field_name => $values) {
184       $field = $entity->$field_name;
185       if ($field instanceof TypedDataInterface) {
186         $field->setValue($values);
187       }
188     }
189     foreach ($empty_destinations as $field_name) {
190       $entity->$field_name = NULL;
191     }
192
193     $this->setRollbackAction($row->getIdMap(), $rollback_action);
194
195     // We might have a different (translated) entity, so return it.
196     return $entity;
197   }
198
199   /**
200    * Populates as much of the stub row as possible.
201    *
202    * @param \Drupal\migrate\Row $row
203    *   The row of data.
204    */
205   protected function processStubRow(Row $row) {
206     $bundle_key = $this->getKey('bundle');
207     if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
208       if (empty($this->bundles)) {
209         throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
210       }
211       $row->setDestinationProperty($bundle_key, reset($this->bundles));
212     }
213
214     // Populate any required fields not already populated.
215     $fields = $this->entityManager
216       ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
217     foreach ($fields as $field_name => $field_definition) {
218       if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
219         // Use the configured default value for this specific field, if any.
220         if ($default_value = $field_definition->getDefaultValueLiteral()) {
221           $values[] = $default_value;
222         }
223         else {
224           // Otherwise, ask the field type to generate a sample value.
225           $field_type = $field_definition->getType();
226           /** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
227           $field_type_class = $this->fieldTypeManager
228             ->getPluginClass($field_definition->getType());
229           $values = $field_type_class::generateSampleValue($field_definition);
230           if (is_null($values)) {
231             // Handle failure to generate a sample value.
232             throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
233           }
234         }
235
236         $row->setDestinationProperty($field_name, $values);
237       }
238     }
239   }
240
241   /**
242    * {@inheritdoc}
243    */
244   public function rollback(array $destination_identifier) {
245     if ($this->isTranslationDestination()) {
246       // Attempt to remove the translation.
247       $entity = $this->storage->load(reset($destination_identifier));
248       if ($entity && $entity instanceof TranslatableInterface) {
249         if ($key = $this->getKey('langcode')) {
250           if (isset($destination_identifier[$key])) {
251             $langcode = $destination_identifier[$key];
252             if ($entity->hasTranslation($langcode)) {
253               // Make sure we don't remove the default translation.
254               $translation = $entity->getTranslation($langcode);
255               if (!$translation->isDefaultTranslation()) {
256                 $entity->removeTranslation($langcode);
257                 $entity->save();
258               }
259             }
260           }
261         }
262       }
263     }
264     else {
265       parent::rollback($destination_identifier);
266     }
267   }
268
269   /**
270    * Gets the field definition from a specific entity base field.
271    *
272    * The method takes the field ID as an argument and returns the field storage
273    * definition to be used in getIds() by querying the destination entity base
274    * field definition.
275    *
276    * @param string $key
277    *   The field ID key.
278    *
279    * @return array
280    *   An associative array with a structure that contains the field type, keyed
281    *   as 'type', together with field storage settings as they are returned by
282    *   FieldStorageDefinitionInterface::getSettings().
283    *
284    * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
285    */
286   protected function getDefinitionFromEntity($key) {
287     $entity_type_id = static::getEntityTypeId($this->getPluginId());
288     /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
289     $definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
290     $field_definition = $definitions[$key];
291
292     return [
293       'type' => $field_definition->getType(),
294     ] + $field_definition->getSettings();
295   }
296
297 }