You are here

ConfigEntityRevisionsConfigEntityTrait.php in Config Entity Revisions 8.2

File

src/ConfigEntityRevisionsConfigEntityTrait.php
View source
<?php

namespace Drupal\config_entity_revisions;

use Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityAdapter;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\TypedData\ListDataDefinition;

/**
 * Trait ConfigEntityRevisionsConfigEntityTrait.
 *
 * @package Drupal\config_entity_revisions
 */
trait ConfigEntityRevisionsConfigEntityTrait {

  /**
   * The ID of the revision that was loaded.
   *
   * @var int
   */
  public $loadedRevisionId;

  /**
   * Whether this revision is the default one.
   *
   * @var boolean
   */
  public $isDefaultRevision = FALSE;

  /*
   * Declare these fields so they're put in the storage object instead of the
   * ViewsUI object during entity building in the case of ViewRevisions.
   * We can then access them in createUpdateRevision.
   */

  /**
   * The revision.
   *
   * @var int
   */
  protected $revision;

  /**
   * The revision_log_message.
   *
   * @var array
   */
  protected $revision_log_message;

  /**
   * The current moderation state for this revision.
   *
   * @var \Drupal\content_moderation\Plugin\Field\ModerationStateFieldItemList
   */
  public $moderation_state;

  /**
   * Constructs an Entity object.
   *
   * @param array $values
   *   An array of values to set, keyed by property name. If the entity type
   *   has bundles, the bundle key has to be specified.
   * @param string $entity_type
   *   The type of the entity to create.
   */
  public function __construct(array $values, $entity_type) {
    parent::__construct($values, $entity_type);
    $this->entityTypeManager = \Drupal::service('entity_type.manager');

    // Add moderation info field.
    $entity_type_info = ConfigEntityRevisionsEntityTypeInfo::create(\Drupal::getContainer());
    $moderation_fields = $entity_type_info
      ->entityExtraFieldInfo();
    if (!empty($moderation_fields['config_entity_revisions'][$this
      ->getBundleName()]) && $this
      ->getContentEntity()) {

      // NB: By feeding the content entity as the parent, we dont need to
      // override the ModerationStateFieldItemList class.
      $entity_adapter = EntityAdapter::createFromEntity($this
        ->getContentEntity());
      $definition = ListDataDefinition::createFromItemType('field_item:string');
      $this->moderation_state = ModerationStateFieldItemList::createInstance($definition, 'moderation_state', $entity_adapter);
    }
  }

  /**
   * Restore the entity type manager after deserialisation.
   */
  public function __wakeup() {
    $this->entityTypeManager = \Drupal::service('entity_type.manager');
  }

  /**
   * The module implementing config entity revisions.
   *
   * @return string
   *   The name of the module implementing the API.
   */
  public function moduleName() {
    return $this->constants['module_name'];
  }

  /**
   * The config entity name.
   *
   * @return string
   *   The name of the entity being revisioned.
   */
  public function configEntityName() {
    return $this->constants['config_entity_name'];
  }

  /**
   * The content entity name in which revisions are being stored.
   *
   * @return string
   *   The name of the content entity in which revisions are being stored.
   */
  public function revisionsEntityName() {
    return $this->constants['revisions_entity_name'];
  }

  /**
   * The bundle name for content entities.
   *
   * @return string
   *   The bundle name for content entities in which revisions are being stored.
   */
  public function getBundleName() {
    return $this->constants['bundle_name'];
  }

  /**
   * The config entity setting name in which content entity ids are stored.
   *
   * @return string
   *   The name of the setting.
   */
  public function settingName() {
    return $this->constants['setting_name'];
  }

  /**
   * The human readable title for this entity.
   *
   * @return string
   *   The proper name (displayed to the user) of the module implementing the
   *   API.
   */
  public function title() {
    return $this->constants['title'];
  }

  /**
   * Whether this config entity has its own content entities.
   *
   * (eg Webforms have webform submissions).
   *
   * @return bool
   *   Does the config entity have its own content entities?
   */
  public function hasOwnContent() {
    return $this->constants['has_own_content'];
  }

  /**
   * The name of the content entity type.
   *
   * @return string
   *   The name of the content entities that the config entity has.
   */
  public function contentEntityType() {
    return $this->constants['content_entity_type'];
  }

  /**
   * Get the name of the parameter for this content.
   *
   * @return string
   *   The name of the parameter used for this content.
   */
  public function contentParameterName() {
    return $this->constants['content_parameter_name'];
  }

  /**
   * Get the content's parent reference field.
   *
   * @return string
   *   The name of the field referencing the parent.
   */
  public function contentParentReferenceField() {
    return $this->constants['content_parent_reference_field'];
  }

  /**
   * Return the name of the admin permission for this entity.
   *
   * @return string
   *   The name of the admin permission.
   */
  public function adminPermission() {
    return $this->constants['admin_permission'];
  }

  /**
   * Return whether the entity has a canonical URL.
   *
   * @return bool
   *   Whether the entity has a canonical URL.
   */
  public function hasCanonicalUrl() {
    return $this->constants['has_canonical_url'] ?: FALSE;
  }

  /**
   * Return the preview form ID, if applicable.
   *
   * @return mixed
   *   The preview form id, if applicable, or NULL.
   */
  public function previewFormId() {
    return $this->constants['preview_form_id'] ?: NULL;
  }

  /**
   * Get the revisioned entity - itself by default.
   *
   * @return \Drupal\Core\Config\Entity\ConfigEntityInterface
   *   The entity which is revisioned.
   */
  public function revisionedEntity() {
    return $this;
  }

  /**
   * Set in the configEntity an identifier for the matching content entity.
   *
   * @param mixed $contentEntityID
   *   The ID used to match the content entity.
   */
  public function setContentEntityId($contentEntityID) {
    $this
      ->setThirdPartySetting($this
      ->moduleName(), 'contentEntity_id', $contentEntityID);
  }

  /**
   * Get from the configEntity the ID of the matching content entity.
   *
   * @return int|null
   *   The ID (if any) of the matching content entity.
   */
  public function getContentEntityId() {
    return $this
      ->getThirdPartySetting($this
      ->moduleName(), 'contentEntity_id');
  }

  /**
   * Gets the revision identifier of the entity.
   *
   * @return int
   *   The revision identifier of the entity, or NULL if the entity does not
   *   have a revision identifier.
   */
  public function getRevisionId() {
    return $this->loadedRevisionId;
  }

  /**
   * Get the revisions entity storage.
   *
   * @return \Drupal\Core\Entity\ContentEntityStorageInterface
   *   The storage for the revisions entity.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function contentEntityStorage() {
    return $this->entityTypeManager
      ->getStorage('config_entity_revisions');
  }

  /**
   * Default revision of revisions entity that matches the config entity.
   *
   * @return \Drupal\Core\Entity\EntityInterface|null
   *   The matching entity.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getContentEntity() {
    $contentEntityID = $this
      ->getContentEntityId();
    if (!$contentEntityID) {
      return NULL;
    }

    /* @var $storage \Drupal\Core\Entity\ContentEntityStorageInterface */
    $storage = $this
      ->contentEntityStorage();

    // Get the matching revision ID if one is provided.
    return $this
      ->getRevisionId() ? $storage
      ->loadRevision($this
      ->getRevisionId()) : $storage
      ->load($contentEntityID);
  }

  /**
   * Determines whether a new revision should be created on save.
   *
   * @return bool
   *   TRUE if a new revision should be created.
   *
   * @see \Drupal\Core\Entity\EntityInterface::setNewRevision()
   */
  public function isNewRevision() {
    return $this->newRevision || $this
      ->getEntityType()
      ->hasKey('revision') && !$this
      ->getRevisionId();
  }

  /**
   * Enforces an entity to be saved as a new revision.
   *
   * @param bool $value
   *   (optional) Whether a new revision should be saved.
   *
   * @throws \LogicException
   *   Thrown if the entity does not support revisions.
   *
   * @see \Drupal\Core\Entity\EntityInterface::isNewRevision()
   */
  public function setNewRevision($value = TRUE) {
    if (!$this
      ->getEntityType()
      ->hasKey('revision')) {
      throw new \LogicException("Entity type {$this->getEntityTypeId()} does not support revisions.");
    }
    if ($value && !$this->newRevision) {

      // When saving a new revision, set any existing revision ID to NULL so as
      // to ensure that a new revision will actually be created.
      $this
        ->set($this
        ->getEntityType()
        ->getKey('revision'), NULL);
    }
    elseif (!$value && $this->newRevision) {

      // If ::setNewRevision(FALSE) is called after ::setNewRevision(TRUE) we
      // have to restore the loaded revision ID.
      $this
        ->set($this
        ->getEntityType()
        ->getKey('revision'), $this
        ->getLoadedRevisionId());
    }
    $this->newRevision = $value;
  }

  /**
   * Gets the loaded Revision ID of the entity.
   *
   * @return int
   *   The loaded Revision identifier of the entity, or NULL if the entity
   *   does not have a revision identifier.
   */
  public function getLoadedRevisionId() {
    return $this->loadedRevisionId;
  }

  /**
   * Updates the loaded Revision ID with the revision ID.
   *
   * This method should not be used, it could unintentionally cause the original
   * revision ID property value to be lost.
   *
   * @return $this
   * @internal
   *
   */
  public function updateLoadedRevisionId() {
    $this->loadedRevisionId = $this
      ->getRevisionId() ?: $this->loadedRevisionId;
    return $this;
  }

  /**
   * Checks if this entity is the default revision.
   *
   * @param bool $new_value
   *   (optional) A Boolean to (re)set the isDefaultRevision flag.
   *
   * @return bool
   *   TRUE if the entity is the default revision, FALSE otherwise. If
   *   $new_value was passed, the previous value is returned.
   */
  public function isDefaultRevision($new_value = NULL) {
    $return = $this->isDefaultRevision;
    if (isset($new_value)) {
      $this->isDefaultRevision = (bool) $new_value;
    }

    // New entities should always ensure at least one default revision exists,
    // creating an entity without a default revision is an invalid state.
    return $this
      ->isNew() || $return;
  }

  /**
   * Checks whether the entity object was a default revision when it was saved.
   *
   * @return bool
   *   TRUE if the entity object was a revision, FALSE otherwise.
   */
  public function wasDefaultRevision() {

    /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
    $entity_type = $this
      ->getEntityType();
    if (!$entity_type
      ->isRevisionable()) {
      return TRUE;
    }
    $revision_default_key = $entity_type
      ->getRevisionMetadataKey('revision_default');
    $value = $this
      ->isNew() || $this
      ->get($revision_default_key)->value;
    return $value;
  }

  /**
   * Checks if this entity is the latest revision.
   *
   * @return bool
   *   TRUE if the entity is the latest revision, FALSE otherwise.
   */
  public function isLatestRevision() {

    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
    $storage = $this
      ->entityTypeManager()
      ->getStorage($this
      ->getEntityTypeId());
    return $this
      ->getLoadedRevisionId() == $storage
      ->getLatestRevisionId($this
      ->id());
  }

  /**
   * Acts on a revision before it gets saved.
   *
   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
   *   The entity storage object.
   * @param \stdClass $record
   *   The revision object.
   */
  public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
  }

  /**
   * {@inheritdoc}
   */
  public function set($property_name, $value) {
    if ($this instanceof EntityWithPluginCollectionInterface) {
      $plugin_collections = $this
        ->getPluginCollections();
      if (isset($plugin_collections[$property_name])) {

        // If external code updates the settings, pass it along to the plugin.
        $plugin_collections[$property_name]
          ->setConfiguration($value);
      }
    }

    // Special handling for the moderation state.
    if (isset($this->{$property_name}) && $this->{$property_name} instanceof ModerationStateFieldItemList) {
      $this->moderation_state
        ->setValue($value);
      $this
        ->getContentEntity()->moderation_state
        ->setValue($value);
    }
    else {
      $this->{$property_name} = $value;
    }
    return $this;
  }

  /**
   * Save an updated version of the entity.
   *
   * In this case, we only save the entity if this new revision is/will be the
   * default revision. In other cases, we're just updating the content revision.
   */
  public function save() {

    // Configuration entity IDs are strings, and '0' is a valid ID.
    $id = $this
      ->id();
    if ($id === NULL || $id === '') {
      throw new EntityMalformedException('The entity does not have an ID.');
    }
    $contentEntityStorage = $this
      ->contentEntityStorage();
    $contentEntityStorage
      ->setConfigEntity($this);
    $next_choice_id = $contentEntityStorage
      ->getLatestPublishedRevisionOrLatestId($this
      ->getRevisionId());
    $new_moderation_state = $this
      ->get('moderation_state');
    $new_moderation_state = $new_moderation_state ? $new_moderation_state->value : 'published';

    // Only save the config entity if this is going to be the default revision
    // (ie the last published revision or the last revision if none are
    // published).
    // Ensure the content entity is always added/updated.
    $new_default = $new_moderation_state == 'published' || $this
      ->getRevisionId() == $next_choice_id;
    if ($new_default) {
      $result = parent::save($this);
    }
    else {

      // We need to save $latest's entity into the config entity if it has
      // changed.
      $next_choice_revision = $contentEntityStorage
        ->loadRevision($next_choice_id);
      $next_choice = $contentEntityStorage
        ->getConfigEntity($next_choice_revision);
      $result = $next_choice
        ->save();
    }

    // And then also update the content entity for our latest change.
    $contentEntityStorage
      ->createUpdateRevision($this);
    return $result;
  }

  /**
   * Gets an array of placeholders for this entity.
   *
   * Individual entity classes may override this method to add additional
   * placeholders if desired. If so, they should be sure to replicate the
   * property caching logic.
   *
   * @param string $rel
   *   The link relationship type, for example: canonical or edit-form.
   *
   * @return array
   *   An array of URI placeholders.
   */
  protected function urlRouteParameters($rel) {
    $uri_route_parameters = [];
    if (!in_array($rel, [
      'collection',
      'add-page',
      'add-form',
    ], TRUE)) {

      // The entity ID is needed as a route parameter.
      $uri_route_parameters[$this
        ->getEntityTypeId()] = $this
        ->id();
    }
    if ($rel === 'add-form' && $this
      ->getEntityType()
      ->hasKey('bundle')) {
      $parameter_name = $this
        ->getEntityType()
        ->getBundleEntityType() ?: $this
        ->getEntityType()
        ->getKey('bundle');
      $uri_route_parameters[$parameter_name] = $this
        ->bundle();
    }
    if ($rel === 'revision' && $this instanceof RevisionableInterface) {
      $uri_route_parameters[$this
        ->getEntityTypeId() . '_revision'] = $this
        ->getRevisionId();
    }
    if (!is_null($this->loadedRevisionId)) {
      $uri_route_parameters += [
        'revision_id' => $this->loadedRevisionId,
      ];
    }
    return $uri_route_parameters;
  }

}

Traits

Namesort descending Description
ConfigEntityRevisionsConfigEntityTrait Trait ConfigEntityRevisionsConfigEntityTrait.