Version 1
[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     // By default, an update will be preserved.
153     $rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
154
155     // Make sure we have the right translation.
156     if ($this->isTranslationDestination()) {
157       $property = $this->storage->getEntityType()->getKey('langcode');
158       if ($row->hasDestinationProperty($property)) {
159         $language = $row->getDestinationProperty($property);
160         if (!$entity->hasTranslation($language)) {
161           $entity->addTranslation($language);
162
163           // We're adding a translation, so delete it on rollback.
164           $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
165         }
166         $entity = $entity->getTranslation($language);
167       }
168     }
169
170     // If the migration has specified a list of properties to be overwritten,
171     // clone the row with an empty set of destination values, and re-add only
172     // the specified properties.
173     if (isset($this->configuration['overwrite_properties'])) {
174       $clone = $row->cloneWithoutDestination();
175       foreach ($this->configuration['overwrite_properties'] as $property) {
176         $clone->setDestinationProperty($property, $row->getDestinationProperty($property));
177       }
178       $row = $clone;
179     }
180
181     foreach ($row->getDestination() as $field_name => $values) {
182       $field = $entity->$field_name;
183       if ($field instanceof TypedDataInterface) {
184         $field->setValue($values);
185       }
186     }
187
188     $this->setRollbackAction($row->getIdMap(), $rollback_action);
189
190     // We might have a different (translated) entity, so return it.
191     return $entity;
192   }
193
194   /**
195    * Populates as much of the stub row as possible.
196    *
197    * @param \Drupal\migrate\Row $row
198    *   The row of data.
199    */
200   protected function processStubRow(Row $row) {
201     $bundle_key = $this->getKey('bundle');
202     if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
203       if (empty($this->bundles)) {
204         throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
205       }
206       $row->setDestinationProperty($bundle_key, reset($this->bundles));
207     }
208
209     // Populate any required fields not already populated.
210     $fields = $this->entityManager
211       ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
212     foreach ($fields as $field_name => $field_definition) {
213       if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
214         // Use the configured default value for this specific field, if any.
215         if ($default_value = $field_definition->getDefaultValueLiteral()) {
216           $values[] = $default_value;
217         }
218         else {
219           // Otherwise, ask the field type to generate a sample value.
220           $field_type = $field_definition->getType();
221           /** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
222           $field_type_class = $this->fieldTypeManager
223             ->getPluginClass($field_definition->getType());
224           $values = $field_type_class::generateSampleValue($field_definition);
225           if (is_null($values)) {
226             // Handle failure to generate a sample value.
227             throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
228           }
229         }
230
231         $row->setDestinationProperty($field_name, $values);
232       }
233     }
234   }
235
236   /**
237    * {@inheritdoc}
238    */
239   public function rollback(array $destination_identifier) {
240     if ($this->isTranslationDestination()) {
241       // Attempt to remove the translation.
242       $entity = $this->storage->load(reset($destination_identifier));
243       if ($entity && $entity instanceof TranslatableInterface) {
244         if ($key = $this->getKey('langcode')) {
245           if (isset($destination_identifier[$key])) {
246             $langcode = $destination_identifier[$key];
247             if ($entity->hasTranslation($langcode)) {
248               // Make sure we don't remove the default translation.
249               $translation = $entity->getTranslation($langcode);
250               if (!$translation->isDefaultTranslation()) {
251                 $entity->removeTranslation($langcode);
252                 $entity->save();
253               }
254             }
255           }
256         }
257       }
258     }
259     else {
260       parent::rollback($destination_identifier);
261     }
262   }
263
264   /**
265    * Gets the field definition from a specific entity base field.
266    *
267    * The method takes the field ID as an argument and returns the field storage
268    * definition to be used in getIds() by querying the destination entity base
269    * field definition.
270    *
271    * @param string $key
272    *   The field ID key.
273    *
274    * @return array
275    *   An associative array with a structure that contains the field type, keyed
276    *   as 'type', together with field storage settings as they are returned by
277    *   FieldStorageDefinitionInterface::getSettings().
278    *
279    * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
280    */
281   protected function getDefinitionFromEntity($key) {
282     $entity_type_id = static::getEntityTypeId($this->getPluginId());
283     /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
284     $definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
285     $field_definition = $definitions[$key];
286
287     return [
288       'type' => $field_definition->getType(),
289     ] + $field_definition->getSettings();
290   }
291
292 }