3 namespace Drupal\migrate\Plugin\migrate\destination;
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\Audit\HighestIdInterface;
13 use Drupal\migrate\Plugin\MigrationInterface;
14 use Drupal\migrate\MigrateException;
15 use Drupal\migrate\Plugin\MigrateIdMapInterface;
16 use Drupal\migrate\Row;
17 use Symfony\Component\DependencyInjection\ContainerInterface;
20 * Provides destination class for all content entities lacking a specific class.
22 * Available configuration keys:
23 * - translations: (optional) Boolean, indicates if the entity is translatable,
25 * - overwrite_properties: (optional) A list of properties that will be
26 * overwritten if an entity with the same ID already exists. Any properties
27 * that are not listed will not be overwritten.
31 * The example below will create a 'node' entity of content type 'article'.
33 * The language of the source will be used because the configuration
34 * 'translations: true' was set. Without this configuration option the site's
35 * default language would be used.
37 * The example content type has fields 'title', 'body' and 'field_example'.
38 * The text format of the body field is defaulted to 'basic_html'. The example
39 * uses the EmbeddedDataSource source plugin for the sake of simplicity.
41 * If the migration is executed again in an update mode, any updates done in the
42 * destination Drupal site to the 'title' and 'body' fields would be overwritten
43 * with the original source values. Updates done to 'field_example' would be
44 * preserved because 'field_example' is not included in 'overwrite_properties'
47 * id: custom_article_migration
48 * label: Custom article migration
50 * plugin: embedded_data
55 * title: 'Sivun otsikko'
56 * field_example: 'Huhuu'
57 * content: '<p>Hoi maailma</p>'
65 * field_example: field_example
66 * 'body/0/value': content
68 * plugin: default_value
69 * default_value: basic_html
72 * default_bundle: article
74 * overwrite_properties:
79 * @see \Drupal\migrate\Plugin\migrate\destination\EntityRevision
81 class EntityContentBase extends Entity implements HighestIdInterface {
86 * @var \Drupal\Core\Entity\EntityManagerInterface
88 protected $entityManager;
91 * Field type plugin manager.
93 * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
95 protected $fieldTypeManager;
98 * Constructs a content entity.
100 * @param array $configuration
101 * A configuration array containing information about the plugin instance.
102 * @param string $plugin_id
103 * The plugin ID for the plugin instance.
104 * @param mixed $plugin_definition
105 * The plugin implementation definition.
106 * @param \Drupal\migrate\Plugin\MigrationInterface $migration
107 * The migration entity.
108 * @param \Drupal\Core\Entity\EntityStorageInterface $storage
109 * The storage for this entity type.
110 * @param array $bundles
111 * The list of bundles this entity type has.
112 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
113 * The entity manager service.
114 * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
115 * The field type plugin manager service.
117 public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
118 parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
119 $this->entityManager = $entity_manager;
120 $this->fieldTypeManager = $field_type_manager;
126 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
127 $entity_type = static::getEntityTypeId($plugin_id);
133 $container->get('entity.manager')->getStorage($entity_type),
134 array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
135 $container->get('entity.manager'),
136 $container->get('plugin.manager.field.field_type')
143 public function import(Row $row, array $old_destination_id_values = []) {
144 $this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
145 $entity = $this->getEntity($row, $old_destination_id_values);
147 throw new MigrateException('Unable to get entity');
150 $ids = $this->save($entity, $old_destination_id_values);
151 if ($this->isTranslationDestination()) {
152 $ids[] = $entity->language()->getId();
160 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
161 * The content entity.
162 * @param array $old_destination_id_values
163 * (optional) An array of destination ID values. Defaults to an empty array.
166 * An array containing the entity ID.
168 protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
170 return [$entity->id()];
176 public function isTranslationDestination() {
177 return !empty($this->configuration['translations']);
183 public function getIds() {
186 $id_key = $this->getKey('id');
187 $ids[$id_key] = $this->getDefinitionFromEntity($id_key);
189 if ($this->isTranslationDestination()) {
190 $langcode_key = $this->getKey('langcode');
191 if (!$langcode_key) {
192 throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
194 $ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
201 * Updates an entity with the new values from row.
203 * @param \Drupal\Core\Entity\EntityInterface $entity
204 * The entity to update.
205 * @param \Drupal\migrate\Row $row
206 * The row object to update from.
208 * @return \Drupal\Core\Entity\EntityInterface
209 * An updated entity from row values.
211 protected function updateEntity(EntityInterface $entity, Row $row) {
212 $empty_destinations = $row->getEmptyDestinationProperties();
213 // By default, an update will be preserved.
214 $rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
216 // Make sure we have the right translation.
217 if ($this->isTranslationDestination()) {
218 $property = $this->storage->getEntityType()->getKey('langcode');
219 if ($row->hasDestinationProperty($property)) {
220 $language = $row->getDestinationProperty($property);
221 if (!$entity->hasTranslation($language)) {
222 $entity->addTranslation($language);
224 // We're adding a translation, so delete it on rollback.
225 $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
227 $entity = $entity->getTranslation($language);
231 // If the migration has specified a list of properties to be overwritten,
232 // clone the row with an empty set of destination values, and re-add only
233 // the specified properties.
234 if (isset($this->configuration['overwrite_properties'])) {
235 $empty_destinations = array_intersect($empty_destinations, $this->configuration['overwrite_properties']);
236 $clone = $row->cloneWithoutDestination();
237 foreach ($this->configuration['overwrite_properties'] as $property) {
238 $clone->setDestinationProperty($property, $row->getDestinationProperty($property));
243 foreach ($row->getDestination() as $field_name => $values) {
244 $field = $entity->$field_name;
245 if ($field instanceof TypedDataInterface) {
246 $field->setValue($values);
249 foreach ($empty_destinations as $field_name) {
250 $entity->$field_name = NULL;
253 $this->setRollbackAction($row->getIdMap(), $rollback_action);
255 // We might have a different (translated) entity, so return it.
260 * Populates as much of the stub row as possible.
262 * @param \Drupal\migrate\Row $row
265 protected function processStubRow(Row $row) {
266 $bundle_key = $this->getKey('bundle');
267 if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
268 if (empty($this->bundles)) {
269 throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
271 $row->setDestinationProperty($bundle_key, reset($this->bundles));
274 // Populate any required fields not already populated.
275 $fields = $this->entityManager
276 ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
277 foreach ($fields as $field_name => $field_definition) {
278 if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
279 // Use the configured default value for this specific field, if any.
280 if ($default_value = $field_definition->getDefaultValueLiteral()) {
281 $values = $default_value;
284 // Otherwise, ask the field type to generate a sample value.
285 $field_type = $field_definition->getType();
286 /** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
287 $field_type_class = $this->fieldTypeManager
288 ->getPluginClass($field_definition->getType());
289 $values = $field_type_class::generateSampleValue($field_definition);
290 if (is_null($values)) {
291 // Handle failure to generate a sample value.
292 throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
296 $row->setDestinationProperty($field_name, $values);
304 public function rollback(array $destination_identifier) {
305 if ($this->isTranslationDestination()) {
306 // Attempt to remove the translation.
307 $entity = $this->storage->load(reset($destination_identifier));
308 if ($entity && $entity instanceof TranslatableInterface) {
309 if ($key = $this->getKey('langcode')) {
310 if (isset($destination_identifier[$key])) {
311 $langcode = $destination_identifier[$key];
312 if ($entity->hasTranslation($langcode)) {
313 // Make sure we don't remove the default translation.
314 $translation = $entity->getTranslation($langcode);
315 if (!$translation->isDefaultTranslation()) {
316 $entity->removeTranslation($langcode);
325 parent::rollback($destination_identifier);
330 * Gets the field definition from a specific entity base field.
332 * The method takes the field ID as an argument and returns the field storage
333 * definition to be used in getIds() by querying the destination entity base
340 * An associative array with a structure that contains the field type, keyed
341 * as 'type', together with field storage settings as they are returned by
342 * FieldStorageDefinitionInterface::getSettings().
344 * @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
346 protected function getDefinitionFromEntity($key) {
347 $entity_type_id = static::getEntityTypeId($this->getPluginId());
348 /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
349 $definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
350 $field_definition = $definitions[$key];
353 'type' => $field_definition->getType(),
354 ] + $field_definition->getSettings();
360 public function getHighestId() {
361 $values = $this->storage->getQuery()
363 ->sort($this->getKey('id'), 'DESC')
366 return (int) current($values);