"Fossies" - the Fresh Open Source Software Archive

Member "drupal-8.9.10/core/modules/migrate/src/Plugin/migrate/destination/EntityContentBase.php" (26 Nov 2020, 13647 Bytes) of package /linux/www/drupal-8.9.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "EntityContentBase.php" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 9.0.8_vs_9.1.0-rc1.

    1 <?php
    2 
    3 namespace Drupal\migrate\Plugin\migrate\destination;
    4 
    5 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
    6 use Drupal\Core\Entity\ContentEntityInterface;
    7 use Drupal\Core\Entity\EntityFieldManagerInterface;
    8 use Drupal\Core\Entity\EntityInterface;
    9 use Drupal\Core\Entity\EntityStorageInterface;
   10 use Drupal\Core\Entity\FieldableEntityInterface;
   11 use Drupal\Core\Field\FieldTypePluginManagerInterface;
   12 use Drupal\Core\TypedData\TranslatableInterface;
   13 use Drupal\Core\TypedData\TypedDataInterface;
   14 use Drupal\migrate\Audit\HighestIdInterface;
   15 use Drupal\migrate\Exception\EntityValidationException;
   16 use Drupal\migrate\Plugin\MigrateValidatableEntityInterface;
   17 use Drupal\migrate\Plugin\MigrationInterface;
   18 use Drupal\migrate\MigrateException;
   19 use Drupal\migrate\Plugin\MigrateIdMapInterface;
   20 use Drupal\migrate\Row;
   21 use Symfony\Component\DependencyInjection\ContainerInterface;
   22 
   23 /**
   24  * Provides destination class for all content entities lacking a specific class.
   25  *
   26  * Available configuration keys:
   27  * - translations: (optional) Boolean, indicates if the entity is translatable,
   28  *   defaults to FALSE.
   29  * - overwrite_properties: (optional) A list of properties that will be
   30  *   overwritten if an entity with the same ID already exists. Any properties
   31  *   that are not listed will not be overwritten.
   32  * - validate: (optional) Boolean, indicates whether an entity should be
   33  *   validated, defaults to FALSE.
   34  *
   35  * Example:
   36  *
   37  * The example below will create a 'node' entity of content type 'article'.
   38  *
   39  * The language of the source will be used because the configuration
   40  * 'translations: true' was set. Without this configuration option the site's
   41  * default language would be used.
   42  *
   43  * The example content type has fields 'title', 'body' and 'field_example'.
   44  * The text format of the body field is defaulted to 'basic_html'. The example
   45  * uses the EmbeddedDataSource source plugin for the sake of simplicity.
   46  *
   47  * If the migration is executed again in an update mode, any updates done in the
   48  * destination Drupal site to the 'title' and 'body' fields would be overwritten
   49  * with the original source values. Updates done to 'field_example' would be
   50  * preserved because 'field_example' is not included in 'overwrite_properties'
   51  * configuration.
   52  * @code
   53  * id: custom_article_migration
   54  * label: Custom article migration
   55  * source:
   56  *   plugin: embedded_data
   57  *   data_rows:
   58  *     -
   59  *       id: 1
   60  *       langcode: 'fi'
   61  *       title: 'Sivun otsikko'
   62  *       field_example: 'Huhuu'
   63  *       content: '<p>Hoi maailma</p>'
   64  *   ids:
   65  *     id:
   66  *       type: integer
   67  * process:
   68  *   nid: id
   69  *   langcode: langcode
   70  *   title: title
   71  *   field_example: field_example
   72  *   'body/0/value': content
   73  *   'body/0/format':
   74  *     plugin: default_value
   75  *     default_value: basic_html
   76  * destination:
   77  *   plugin: entity:node
   78  *   default_bundle: article
   79  *   translations: true
   80  *   overwrite_properties:
   81  *     - title
   82  *     - body
   83  *   # Run entity and fields validation before saving an entity.
   84  *   # @see \Drupal\Core\Entity\FieldableEntityInterface::validate()
   85  *   validate: true
   86  * @endcode
   87  *
   88  * @see \Drupal\migrate\Plugin\migrate\destination\Entity
   89  * @see \Drupal\migrate\Plugin\migrate\destination\EntityRevision
   90  */
   91 class EntityContentBase extends Entity implements HighestIdInterface, MigrateValidatableEntityInterface {
   92   use DeprecatedServicePropertyTrait;
   93 
   94   /**
   95    * {@inheritdoc}
   96    */
   97   protected $deprecatedProperties = ['entityManager' => 'entity.manager'];
   98 
   99   /**
  100    * Entity field manager.
  101    *
  102    * @var \Drupal\Core\Entity\EntityFieldManagerInterface
  103    */
  104   protected $entityFieldManager;
  105 
  106   /**
  107    * Field type plugin manager.
  108    *
  109    * @var \Drupal\Core\Field\FieldTypePluginManagerInterface
  110    */
  111   protected $fieldTypeManager;
  112 
  113   /**
  114    * Constructs a content entity.
  115    *
  116    * @param array $configuration
  117    *   A configuration array containing information about the plugin instance.
  118    * @param string $plugin_id
  119    *   The plugin ID for the plugin instance.
  120    * @param mixed $plugin_definition
  121    *   The plugin implementation definition.
  122    * @param \Drupal\migrate\Plugin\MigrationInterface $migration
  123    *   The migration entity.
  124    * @param \Drupal\Core\Entity\EntityStorageInterface $storage
  125    *   The storage for this entity type.
  126    * @param array $bundles
  127    *   The list of bundles this entity type has.
  128    * @param \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager
  129    *   The entity field manager.
  130    * @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
  131    *   The field type plugin manager service.
  132    */
  133   public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityFieldManagerInterface $entity_field_manager, FieldTypePluginManagerInterface $field_type_manager) {
  134     parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
  135     $this->entityFieldManager = $entity_field_manager;
  136     $this->fieldTypeManager = $field_type_manager;
  137   }
  138 
  139   /**
  140    * {@inheritdoc}
  141    */
  142   public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
  143     $entity_type = static::getEntityTypeId($plugin_id);
  144     return new static(
  145       $configuration,
  146       $plugin_id,
  147       $plugin_definition,
  148       $migration,
  149       $container->get('entity_type.manager')->getStorage($entity_type),
  150       array_keys($container->get('entity_type.bundle.info')->getBundleInfo($entity_type)),
  151       $container->get('entity_field.manager'),
  152       $container->get('plugin.manager.field.field_type')
  153     );
  154   }
  155 
  156   /**
  157    * {@inheritdoc}
  158    *
  159    * @throws \Drupal\migrate\MigrateException
  160    *   When an entity cannot be looked up.
  161    * @throws \Drupal\migrate\Exception\EntityValidationException
  162    *   When an entity validation hasn't been passed.
  163    */
  164   public function import(Row $row, array $old_destination_id_values = []) {
  165     $this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
  166     $entity = $this->getEntity($row, $old_destination_id_values);
  167     if (!$entity) {
  168       throw new MigrateException('Unable to get entity');
  169     }
  170     assert($entity instanceof ContentEntityInterface);
  171     if ($this->isEntityValidationRequired($entity)) {
  172       $this->validateEntity($entity);
  173     }
  174     $ids = $this->save($entity, $old_destination_id_values);
  175     if ($this->isTranslationDestination()) {
  176       $ids[] = $entity->language()->getId();
  177     }
  178     return $ids;
  179   }
  180 
  181   /**
  182    * {@inheritdoc}
  183    */
  184   public function isEntityValidationRequired(FieldableEntityInterface $entity) {
  185     // Prioritize the entity method over migration config because it won't be
  186     // possible to save that entity unvalidated.
  187     /* @see \Drupal\Core\Entity\ContentEntityBase::preSave() */
  188     return $entity->isValidationRequired() || !empty($this->configuration['validate']);
  189   }
  190 
  191   /**
  192    * {@inheritdoc}
  193    */
  194   public function validateEntity(FieldableEntityInterface $entity) {
  195     $violations = $entity->validate();
  196 
  197     if (count($violations) > 0) {
  198       throw new EntityValidationException($violations);
  199     }
  200   }
  201 
  202   /**
  203    * Saves the entity.
  204    *
  205    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  206    *   The content entity.
  207    * @param array $old_destination_id_values
  208    *   (optional) An array of destination ID values. Defaults to an empty array.
  209    *
  210    * @return array
  211    *   An array containing the entity ID.
  212    */
  213   protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
  214     $entity->save();
  215     return [$entity->id()];
  216   }
  217 
  218   /**
  219    * {@inheritdoc}
  220    */
  221   public function isTranslationDestination() {
  222     return !empty($this->configuration['translations']);
  223   }
  224 
  225   /**
  226    * {@inheritdoc}
  227    */
  228   public function getIds() {
  229     $ids = [];
  230 
  231     $id_key = $this->getKey('id');
  232     $ids[$id_key] = $this->getDefinitionFromEntity($id_key);
  233 
  234     if ($this->isTranslationDestination()) {
  235       $langcode_key = $this->getKey('langcode');
  236       if (!$langcode_key) {
  237         throw new MigrateException(sprintf('The "%s" entity type does not support translations.', $this->storage->getEntityTypeId()));
  238       }
  239       $ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
  240     }
  241 
  242     return $ids;
  243   }
  244 
  245   /**
  246    * Updates an entity with the new values from row.
  247    *
  248    * @param \Drupal\Core\Entity\EntityInterface $entity
  249    *   The entity to update.
  250    * @param \Drupal\migrate\Row $row
  251    *   The row object to update from.
  252    *
  253    * @return \Drupal\Core\Entity\EntityInterface
  254    *   An updated entity from row values.
  255    */
  256   protected function updateEntity(EntityInterface $entity, Row $row) {
  257     $empty_destinations = $row->getEmptyDestinationProperties();
  258     // By default, an update will be preserved.
  259     $rollback_action = MigrateIdMapInterface::ROLLBACK_PRESERVE;
  260 
  261     // Make sure we have the right translation.
  262     if ($this->isTranslationDestination()) {
  263       $property = $this->storage->getEntityType()->getKey('langcode');
  264       if ($row->hasDestinationProperty($property)) {
  265         $language = $row->getDestinationProperty($property);
  266         if (!$entity->hasTranslation($language)) {
  267           $entity->addTranslation($language);
  268 
  269           // We're adding a translation, so delete it on rollback.
  270           $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE;
  271         }
  272         $entity = $entity->getTranslation($language);
  273       }
  274     }
  275 
  276     // If the migration has specified a list of properties to be overwritten,
  277     // clone the row with an empty set of destination values, and re-add only
  278     // the specified properties.
  279     if (isset($this->configuration['overwrite_properties'])) {
  280       $empty_destinations = array_intersect($empty_destinations, $this->configuration['overwrite_properties']);
  281       $clone = $row->cloneWithoutDestination();
  282       foreach ($this->configuration['overwrite_properties'] as $property) {
  283         $clone->setDestinationProperty($property, $row->getDestinationProperty($property));
  284       }
  285       $row = $clone;
  286     }
  287 
  288     foreach ($row->getDestination() as $field_name => $values) {
  289       $field = $entity->$field_name;
  290       if ($field instanceof TypedDataInterface) {
  291         $field->setValue($values);
  292       }
  293     }
  294     foreach ($empty_destinations as $field_name) {
  295       $entity->$field_name = NULL;
  296     }
  297 
  298     $this->setRollbackAction($row->getIdMap(), $rollback_action);
  299 
  300     // We might have a different (translated) entity, so return it.
  301     return $entity;
  302   }
  303 
  304   /**
  305    * Populates as much of the stub row as possible.
  306    *
  307    * @param \Drupal\migrate\Row $row
  308    *   The row of data.
  309    */
  310   protected function processStubRow(Row $row) {
  311     $bundle_key = $this->getKey('bundle');
  312     if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
  313       if (empty($this->bundles)) {
  314         throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
  315       }
  316       $row->setDestinationProperty($bundle_key, reset($this->bundles));
  317     }
  318 
  319     // Populate any required fields not already populated.
  320     $fields = $this->entityFieldManager
  321       ->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
  322     foreach ($fields as $field_name => $field_definition) {
  323       if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
  324         // Use the configured default value for this specific field, if any.
  325         if ($default_value = $field_definition->getDefaultValueLiteral()) {
  326           $values = $default_value;
  327         }
  328         else {
  329           // Otherwise, ask the field type to generate a sample value.
  330           $field_type = $field_definition->getType();
  331           /** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
  332           $field_type_class = $this->fieldTypeManager
  333             ->getPluginClass($field_definition->getType());
  334           $values = $field_type_class::generateSampleValue($field_definition);
  335           if (is_null($values)) {
  336             // Handle failure to generate a sample value.
  337             throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
  338           }
  339         }
  340 
  341         $row->setDestinationProperty($field_name, $values);
  342       }
  343     }
  344   }
  345 
  346   /**
  347    * {@inheritdoc}
  348    */
  349   public function rollback(array $destination_identifier) {
  350     if ($this->isTranslationDestination()) {
  351       // Attempt to remove the translation.
  352       $entity = $this->storage->load(reset($destination_identifier));
  353       if ($entity && $entity instanceof TranslatableInterface) {
  354         if ($key = $this->getKey('langcode')) {
  355           if (isset($destination_identifier[$key])) {
  356             $langcode = $destination_identifier[$key];
  357             if ($entity->hasTranslation($langcode)) {
  358               // Make sure we don't remove the default translation.
  359               $translation = $entity->getTranslation($langcode);
  360               if (!$translation->isDefaultTranslation()) {
  361                 $entity->removeTranslation($langcode);
  362                 $entity->save();
  363               }
  364             }
  365           }
  366         }
  367       }
  368     }
  369     else {
  370       parent::rollback($destination_identifier);
  371     }
  372   }
  373 
  374   /**
  375    * {@inheritdoc}
  376    */
  377   public function getHighestId() {
  378     $values = $this->storage->getQuery()
  379       ->accessCheck(FALSE)
  380       ->sort($this->getKey('id'), 'DESC')
  381       ->range(0, 1)
  382       ->execute();
  383     return (int) current($values);
  384   }
  385 
  386 }