Commit 895bc686 authored by Milos Bovan's avatar Milos Bovan Committed by Sascha Grossenbacher
Browse files

Issue #2978341 by mbovan, Berdir, yongt9412, colorfield, Charlie ChX Negyesi:...

Issue #2978341 by mbovan, Berdir, yongt9412, colorfield, Charlie ChX Negyesi: Support pending revisions and accepting translation as a specific moderation state
parent b2e53f8b
Loading
Loading
Loading
Loading
+200 −7
Original line number Diff line number Diff line
@@ -2,10 +2,15 @@

namespace Drupal\tmgmt_content;

use Drupal\content_translation\ContentTranslationManager;
use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\tmgmt\SourcePluginUiBase;
use Drupal\tmgmt_content\Plugin\tmgmt\Source\ContentEntitySource;

/**
 * Content entity source plugin UI.
@@ -131,10 +136,13 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
   * @return array
   */
  public function overviewRow(ContentEntityInterface $entity, array $bundles) {
    $label = $entity->label() ?: $this->t('@type: @id', array(
      '@type' => $entity->getEntityTypeId(),
      '@id' => $entity->id(),
    ));
    $entity_label = $entity->label();

    $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
    $use_latest_revisions = $entity->getEntityType()->isRevisionable() && ContentTranslationManager::isPendingRevisionSupportEnabled($entity->getEntityTypeId(), $entity->bundle());

    // Get the default revision.
    $default_revision = $use_latest_revisions ? $storage->load($entity->id()) : $entity;

    // Get existing translations and current job items for the entity
    // to determine translation statuses
@@ -144,7 +152,6 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {

    $row = array(
      'id' => $entity->id(),
      'title' => $entity->hasLinkTemplate('canonical') ? $entity->toLink($label, 'canonical')->toString() : ($entity->label() ?: $entity->id()),
    );

    if (count($bundles) > 1) {
@@ -154,6 +161,29 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
    // Load entity translation specific data.
    $manager = \Drupal::service('content_translation.manager');
    foreach (\Drupal::languageManager()->getLanguages() as $langcode => $language) {
      // @see Drupal\content_translation\Controller\ContentTranslationController::overview()
      // If the entity type is revisionable, we may have pending revisions
      // with translations not available yet in the default revision. Thus we
      // need to load the latest translation-affecting revision for each
      // language to be sure we are listing all available translations.
      if ($use_latest_revisions) {
        $entity = $default_revision;
        $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
        if ($latest_revision_id) {
          /** @var \Drupal\Core\Entity\ContentEntityInterface $latest_revision */
          $latest_revision = $storage->loadRevision($latest_revision_id);
          // Make sure we do not list removed translations, i.e. translations
          // that have been part of a default revision but no longer are.
          if (!$latest_revision->wasDefaultRevision() || $default_revision->hasTranslation($langcode)) {
            $entity = $latest_revision;
            // Update the label if we are dealing with the source language.
            if ($langcode === $source_lang) {
              $entity_label = $entity->label();
            }
          }
        }
        $translations = $entity->getTranslationLanguages();
      }

      $translation_status = 'current';

@@ -185,6 +215,12 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
        'class' => array('langstatus-' . $langcode),
      ];
    }

    $label = $entity_label ?: $this->t('@type: @id', [
      '@type' => $entity->getEntityTypeId(),
      '@id' => $entity->id(),
    ]);
    $row['title'] = $entity->hasLinkTemplate('canonical') ? $entity->toLink($label, 'canonical')->toString() : ($entity_label ?: $entity->id());
    return $row;
  }

@@ -260,7 +296,7 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
      batch_set($batch);
    }
    else {
      $entities = \Drupal::entityTypeManager()->getStorage($item_type)->loadMultiple(array_filter($form_state->getValue('items')));
      $entities = ContentEntitySource::loadMultiple($item_type, array_filter($form_state->getValue('items')));
      $job_items = 0;
      // Loop through entities and add them to continuous jobs.
      foreach ($entities as $entity) {
@@ -391,7 +427,10 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
    $query->addField('e', $id_key);

    $langcode_table_alias = 'e';
    if ($data_table = $entity_type->getDataTable()) {
    // @todo: Discuss if search should work on latest, default or all revisions.
    //   See https://www.drupal.org/project/tmgmt/issues/2984554.
    $data_table = $entity_type->isRevisionable() ? $entity_type->getRevisionDataTable() : $entity_type->getDataTable();
    if ($data_table) {
      $langcode_table_alias = $query->innerJoin($data_table, 'data_table', '%alias.' . $id_key . ' = e.' . $id_key . ' AND %alias.default_langcode = 1');
    }

@@ -512,4 +551,158 @@ class ContentEntitySourcePluginUi extends SourcePluginUiBase {
    }
  }

  /**
   * {@inheritdoc}
   */
  public function reviewForm(array $form, FormStateInterface $form_state, JobItemInterface $item) {
    $form = parent::reviewForm($form, $form_state, $item);

    // Only proceed to display the content moderation form if the job item is
    // either active or reviewable.
    if (!$item->isNeedsReview() && !$item->isActive()) {
      return $form;
    }

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    $entity = ContentEntitySource::load($item->getItemType(), $item->getItemId());

    if (!$form_state->isRebuilding() && $entity) {
      // In case the original entity is moderated, allow users to update the
      // content moderation state of the translation.
      if (ContentEntitySource::isModeratedEntity($entity)) {
        $form['moderation_state'] = $this->buildContentModerationElement($item, $entity);
      }
      // For non-moderated publishable entities, build a publish state form.
      elseif ($entity instanceof EntityPublishedInterface) {
        $form['status'] = $this->buildPublishStateElement($item, $entity);
      }
    }

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function reviewFormSubmit(array $form, FormStateInterface $form_state, JobItemInterface $item) {
    // At this point, we don't need to check whether an entity is moderated or
    // publishable. Instead, we look for a specific key that may be set.
    if ($form_state->hasValue(['moderation_state', 'new_state'])) {
      // We are using a special #moderation_state key to carry the information
      // about the new moderation state value.
      // See \Drupal\tmgmt_content\Plugin\tmgmt\Source\ContentEntitySource::doSaveTranslations()
      $moderation_state = (array) $form_state->getValue(['moderation_state', 'new_state']);
      $item->updateData(['#moderation_state'], $moderation_state, TRUE);
    }
    elseif ($form_state->hasValue(['status', 'published'])) {
      $published = (array) (bool) $form_state->getValue(['status', 'published']);
      $item->updateData(['#published'], $published, TRUE);
    }

    parent::reviewFormSubmit($form, $form_state, $item);
  }

  /**
   * Build a publish state element.
   *
   * @param \Drupal\tmgmt\JobItemInterface $item
   *   The job item.
   * @param \Drupal\Core\Entity\EntityPublishedInterface $entity
   *   The source publishable entity.
   *
   * @return array
   *   A publish state form element.
   */
  protected function buildPublishStateElement(JobItemInterface $item, EntityPublishedInterface $entity) {
    $element = [
      '#type' => 'fieldset',
      '#title' => $this->t('Translation publish status'),
      '#tree' => TRUE,
    ];
    $published = $item->getData(['#published'], 0);
    $default_value = isset($published[0]) ? $published[0] : $entity->isPublished();

    $published_title = $this->t('Published');
    $published_field = $entity->getEntityType()->getKey('published');
    if ($entity instanceof FieldableEntityInterface && $entity->hasField($published_field) && !$entity->get($published_field)->getFieldDefinition()->isTranslatable()) {
      $published_title = $this->t('Published (all languages)');
    }

    $element['published'] = [
      '#type' => 'checkbox',
      '#default_value' => $default_value,
      '#title' => $published_title,
    ];

    return $element;
  }

  /**
   * Build a content moderation elemenet for the translation.
   *
   * @param \Drupal\tmgmt\JobItemInterface $item
   *   The job item.
   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
   *   The source moderated entity.
   *
   * @return array
   *   A content moderation form element.
   */
  protected function buildContentModerationElement(JobItemInterface $item, ContentEntityInterface $entity) {
    $element = [];

    /** @var \Drupal\content_moderation\ModerationInformationInterface $moderation_info */
    $moderation_info = \Drupal::service('content_moderation.moderation_information');
    $workflow = $moderation_info->getWorkflowForEntity($entity);
    $moderation_validator = \Drupal::service('content_moderation.state_transition_validation');

    // Extract the current moderation state stored within the special key.
    $moderation_state = $item->getData(['#moderation_state'], 0);
    $current_state = isset($moderation_state[0]) ? $moderation_state[0] : $entity->get('moderation_state')->value;
    $default = $workflow->getTypePlugin()->getState($current_state);

    // Get a list of valid transitions.
    /** @var \Drupal\workflows\Transition[] $transitions */
    $transitions = $moderation_validator->getValidTransitions($entity, \Drupal::currentUser());

    $transition_labels = [];
    $default_value = NULL;
    foreach ($transitions as $transition) {
      $transition_to_state = $transition->to();
      $transition_labels[$transition_to_state->id()] = $transition_to_state->label();
      if ($default->id() === $transition_to_state->id()) {
        $default_value = $default->id();
      }
    }

    // See \Drupal\content_moderation\Plugin\Field\FieldWidget\ModerationStateWidget::formElement()
    $element += [
      '#type' => 'container',
      '#tree' => TRUE,
      'current' => [
        '#type' => 'item',
        '#title' => $this->t('Current source state'),
        '#markup' => $default->label(),
        '#wrapper_attributes' => [
          'class' => ['container-inline'],
        ],
      ],
      'new_state' => [
        '#type' => 'select',
        '#title' => $this->t('Translation state'),
        '#options' => $transition_labels,
        '#default_value' => $default_value,
        '#access' => !empty($transition_labels),
        '#wrapper_attributes' => [
          'class' => ['container-inline'],
        ],
      ],
    ];

    $element['#theme'] = ['entity_moderation_form'];
    $element['#attached']['library'][] = 'content_moderation/content_moderation';

    return $element;
  }

}
+7 −7
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\Core\Render\Element;
use Drupal\tmgmt_content\Plugin\tmgmt\Source\ContentEntitySource;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

@@ -54,9 +55,7 @@ class ContentTranslationPreviewController extends ControllerBase {
   */
  public function preview(JobItemInterface $tmgmt_job_item, $view_mode) {
    // Load entity.
    $entity = $this->entityTypeManager
      ->getStorage($tmgmt_job_item->getItemType())
      ->load($tmgmt_job_item->getItemId());
    $entity = ContentEntitySource::load($tmgmt_job_item->getItemType(), $tmgmt_job_item->getItemId(), $tmgmt_job_item->getJob()->getSourceLangcode());

    // We cannot show the preview for non-existing entities.
    if (!$entity) {
@@ -66,6 +65,8 @@ class ContentTranslationPreviewController extends ControllerBase {
    $target_langcode = $tmgmt_job_item->getJob()->getTargetLangcode();
    // Populate preview with target translation data.
    $preview = $this->makePreview($entity, $data, $target_langcode);
    // Set the entity into preview mode.
    $preview->in_preview = TRUE;
    // Build view for entity.
    $page = $this->entityTypeManager
      ->getViewBuilder($entity->getEntityTypeId())
@@ -89,10 +90,9 @@ class ContentTranslationPreviewController extends ControllerBase {
   */
  public function title(JobItemInterface $tmgmt_job_item) {
    $target_language = $tmgmt_job_item->getJob()->getTargetLanguage()->getName();
    $title = $this->entityTypeManager
      ->getStorage($tmgmt_job_item->getItemType())
      ->load($tmgmt_job_item->getItemId())
      ->label();
    $entity = ContentEntitySource::load($tmgmt_job_item->getItemType(), $tmgmt_job_item->getItemId(), $tmgmt_job_item->getJob()->getSourceLangcode());
    $title = $entity->label();

    return t("Preview of @title for @target_language", [
      '@title' => $title,
      '@target_language' => $target_language,
+173 −9
Original line number Diff line number Diff line
@@ -4,8 +4,12 @@ namespace Drupal\tmgmt_content\Plugin\tmgmt\Source;

use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Plugin\DataType\EntityReference;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Entity\TranslatableRevisionableStorageInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AnonymousUserSession;
@@ -44,11 +48,77 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
    return \Drupal::entityTypeManager()->getStorage($job_item->getItemType())->load($job_item->getItemId());
  }

  /**
   * Loads a list of entities for the given entity type ID.
   *
   * By providing the language code, the latest revisions affecting the
   * specified translation (language code) will be returned.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param array $entity_ids
   *   A list of entity IDs to load.
   * @param string|null $langcode
   *   (optional) The language code. Defaults to source entity language.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   Returns a list of entities.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function loadMultiple($entity_type_id, array $entity_ids, $langcode = NULL) {
    /** @var \Drupal\Core\Entity\EntityStorageInterface $storage */
    $storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);

    $entities = $storage->loadMultiple($entity_ids);

    // Load the latest revision if the entity type is revisionable.
    if ($storage->getEntityType()->isRevisionable() && $storage instanceof TranslatableRevisionableStorageInterface) {
      foreach ($entities as $entity_id => $entity) {
        // Use the specified langcode or fallback to the default language.
        $translation_langcode = $langcode ?: $entity->language()->getId();
        $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $translation_langcode);
        if ($revision_id) {
          $entities[$entity_id] = $storage->loadRevision($revision_id);
        }
      }
    }

    return $entities;
  }

  /**
   * Loads a single entity for the given entity type ID.
   *
   * By providing the language code, the latest revisions affecting the
   * specified translation (language code) will be returned.
   *
   * @param string $entity_type_id
   *   The entity type ID.
   * @param string $id
   *   The entity ID.
   * @param string|null $langcode
   *   (optional) The language code. Defaults to source entity language.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface|null
   *   The loaded entity or null if not found.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public static function load($entity_type_id, $id, $langcode = NULL) {
    $entities = static::loadMultiple($entity_type_id, [$id], $langcode);
    return isset($entities[$id]) ? $entities[$id] : NULL;
  }

  /**
   * {@inheritdoc}
   */
  public function getLabel(JobItemInterface $job_item) {
    if ($entity = $this->getEntity($job_item)) {
    // Use the source language to a get label for the job item.
    $langcode = $job_item->getJob() ? $job_item->getJob()->getSourceLangcode() : NULL;
    if ($entity = static::load($job_item->getItemType(), $job_item->getItemId(), $langcode)) {
      return $entity->label() ?: $entity->id();
    }
  }
@@ -58,7 +128,8 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
   */
  public function getUrl(JobItemInterface $job_item) {
    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    if ($entity = \Drupal::entityTypeManager()->getStorage($job_item->getItemType())->load($job_item->getItemId())) {
    $langcode = $job_item->getJob() ? $job_item->getJob()->getSourceLangcode() : NULL;
    if ($entity = static::load($job_item->getItemType(), $job_item->getItemId(), $langcode)) {
      if ($entity->hasLinkTemplate('canonical')) {
        $anonymous = new AnonymousUserSession();
        $url = $entity->toUrl();
@@ -82,7 +153,8 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
   * the Translation Management system.
   */
  public function getData(JobItemInterface $job_item) {
    $entity = $this->getEntity($job_item);
    $langcode = $job_item->getJob() ? $job_item->getJob()->getSourceLangcode() : NULL;
    $entity = static::load($job_item->getItemType(), $job_item->getItemId(), $langcode);
    if (!$entity) {
      throw new TMGMTException(t('Unable to load entity %type with id %id', array('%type' => $job_item->getItemType(), '%id' => $job_item->getItemId())));
    }
@@ -220,6 +292,23 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
    return $data;
  }

  /**
   * Determines whether an entity is moderated.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return bool
   *   TRUE if the entity is moderated. Otherwise, FALSE.
   */
  public static function isModeratedEntity(EntityInterface $entity) {
    if (!\Drupal::moduleHandler()->moduleExists('content_moderation')) {
      return FALSE;
    }

    return \Drupal::service('content_moderation.moderation_information')->isModeratedEntity($entity);
  }

  /**
   * Returns fields that should be embedded into the data for the given entity.
   *
@@ -278,8 +367,18 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
      return FALSE;
    }

    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
    if ($entity_revision = $this->getPendingRevisionWithCompositeReferenceField($job_item)) {
      $title = $entity_revision->hasLinkTemplate('latest-version') ? $entity_revision->toLink(NULL, 'latest-version')->toString() : $entity_revision->label();
      $job_item->addMessage('This translation cannot be accepted as there is a pending revision in the default translation. You must publish %title first before saving this translation.', [
        '%title' => $title,
      ], 'error');
      return FALSE;
    }

    $data = $job_item->getData();
    $this->doSaveTranslations($entity, $data, $target_langcode);

    $this->doSaveTranslations($entity, $data, $target_langcode, $job_item);
    return TRUE;
  }

@@ -356,11 +455,13 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
   *   The translation data for the fields.
   * @param string $target_langcode
   *   The target language.
   * @param \Drupal\tmgmt\JobItemInterface $item
   *   The job item.
   *
   * @throws \Exception
   *   Thrown when a field or field offset is missing.
   */
  protected function doSaveTranslations(ContentEntityInterface $entity, array $data, $target_langcode) {
  protected function doSaveTranslations(ContentEntityInterface $entity, array $data, $target_langcode, JobItemInterface $item) {
   // If the translation for this language does not exist yet, initialize it.
    if (!$entity->hasTranslation($target_langcode)) {
      $entity->addTranslation($target_langcode, $entity->toArray());
@@ -396,8 +497,34 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
          if ($target_entity = $this->findReferencedEntity($field, $field_item, $delta, $property)) {
            // If the field is an embeddable reference and the property is a
            // content entity, process it recursively.
            $this->doSaveTranslations($target_entity, $field_item[$property], $target_langcode);
            $this->doSaveTranslations($target_entity, $field_item[$property], $target_langcode, $item);
          }
        }
      }
    }

    if (isset($data['#moderation_state'][0]) && static::isModeratedEntity($translation)) {
      // If the entity is moderated, set the moderation state for translation.
      $translation->set('moderation_state', $data['#moderation_state'][0]);
    }
    // Otherwise, try to set a published status.
    elseif (isset($data['#published'][0]) && $translation instanceof EntityPublishedInterface) {
      $translation->setPublished($data['#published'][0]);
    }

    if ($entity->getEntityType()->isRevisionable()) {
      /** @var \Drupal\Core\Entity\TranslatableRevisionableStorageInterface $storage */
      $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());

      if ($storage instanceof TranslatableRevisionableStorageInterface) {
        // Always create a new revision of the translation.
        $translation = $storage->createRevision($translation, $translation->isDefaultRevision());

        if ($entity instanceof RevisionLogInterface) {
          $translation->setRevisionLogMessage($this->t('Created by translation job <a href=":url">@label</a>.', [
            ':url' => $item->getJob()->toUrl()->toString(),
            '@label' => $item->label(),
          ]));
        }
      }
    }
@@ -464,8 +591,7 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
   */
  public function shouldCreateContinuousItem(Job $job, $plugin, $item_type, $item_id) {
    $continuous_settings = $job->getContinuousSettings();
    $entity_type_manager = \Drupal::entityTypeManager();
    $entity = $entity_type_manager->getStorage($item_type)->load($item_id);
    $entity = static::load($item_type, $item_id, $job->getSourceLangcode());
    $translation_manager = \Drupal::service('content_translation.manager');
    $translation = $entity->hasTranslation($job->getTargetLangcode()) ? $entity->getTranslation($job->getTargetLangcode()) : NULL;
    $metadata = isset($translation) ? $translation_manager->getTranslationMetadata($translation) : NULL;
@@ -553,4 +679,42 @@ class ContentEntitySource extends SourcePluginBase implements SourcePreviewInter
    }
  }

  /**
   * Returns the source revision if it is a pending revision with an ERR field.
   *
   * @param \Drupal\tmgmt\JobItemInterface $job_item
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface|null
   *   The source revision entity if it is a pending revision with an ERR field.
   */
  public function getPendingRevisionWithCompositeReferenceField(JobItemInterface $job_item) {
    // Get the latest revision of the default translation.
    /** \Drupal\Core\Entity\ContentEntityInterface|null $entity */
    $entity = static::load($job_item->getItemType(), $job_item->getItemId());
    if (!$entity) {
      return NULL;
    }

    // If the given revision is not the default revision, check if there is at
    // least one non-translatable composite entity reference revisions field and
    // fail the validation.
    if (!$entity->isDefaultRevision()) {
      foreach ($entity->getFieldDefinitions() as $definition) {
        if (in_array($definition->getType(), ['entity_reference', 'entity_reference_revisions']) && !$definition->isTranslatable()) {
          $target_type_id = $definition->getSetting('target_type');
          $entity_type_manager = \Drupal::entityTypeManager();
          if (!$entity_type_manager->hasDefinition($target_type_id)) {
            continue;
          }
          // Check if the target entity type is considered a composite.
          if ($entity_type_manager->getDefinition($target_type_id)->get('entity_revision_parent_type_field')) {
            return $entity;
          }
        }
      }
    }

    return NULL;
  }

}
+4 −0
Original line number Diff line number Diff line
@@ -17,6 +17,10 @@ use Drupal\entity_test\Entity\EntityTestMul;
 *   entity_revision_parent_type_field = "parent_type",
 *   translatable = TRUE,
 *   content_translation_ui_skip = TRUE,
 *   handlers = {
 *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
 *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder"
 *   },
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
+470 −7

File changed.

Preview size limit exceeded, changes collapsed.

Loading