diff --git a/core/core.api.php b/core/core.api.php index 5045fc5..3b07651 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -1221,6 +1221,10 @@ * verified with standard control structures at all times, not just checked in * development environments with assert() statements on. * + * When runtime assertions fail in PHP 7 an \AssertionError is thrown. + * Drupal uses an assertion callback to do the same in PHP 5.x so that unit + * tests involving runtime assertions will work uniformly across both versions. + * * The Drupal project primarily uses runtime assertions to enforce the * expectations of the API by failing when incorrect calls are made by code * under development. While PHP type hinting does this for objects and arrays, @@ -1228,12 +1232,21 @@ * complex data structures such as cache and render arrays. They ensure that * methods' return values are the documented datatypes. They also verify that * objects have been properly configured and set up by the service container. - * They supplement unit tests by checking scenarios that do not have unit tests - * written for them. - * - * There are two php settings which affect runtime assertions. The first, - * assert.exception, should always be set to 1. The second is zend.assertions. - * Set this to -1 in production and 1 in development. + * Runtime assertions are checked throughout development. They supplement unit + * tests by checking scenarios that do not have unit tests written for them, + * and by testing the API calls made by all the code in the system. + * + * When using assert() keep the following in mind: + * - Runtime assertions are disabled by default in production and enabled in + * development, so they can't be used as control structures. Use exceptions + * for errors that can occur in production no matter how unlikely they are. + * - Assert() functions in a buggy manner prior to PHP 7. If you do not use a + * string for the first argument of the statement but instead use a function + * call or expression then that code will be evaluated even when runtime + * assertions are turned off. To avoid this you must use a string as the + * first argument, and assert will pass this string to the eval() statement. + * - Since runtime assertion strings are parsed by eval() use caution when + * using them to work with data that may be unsanitized. * * See https://www.drupal.org/node/2492225 for more information on runtime * assertions. diff --git a/core/core.link_relation_types.yml b/core/core.link_relation_types.yml index 21cdd64..d4ffce3 100644 --- a/core/core.link_relation_types.yml +++ b/core/core.link_relation_types.yml @@ -9,9 +9,6 @@ add-page: delete-form: uri: https://drupal.org/link-relations/delete-form description: A form where a resource of this type can be deleted. -delete-multiple-form: - uri: https://drupal.org/link-relations/delete-multiple-form - description: A form where multiple resources of this type can be deleted. revision: uri: https://drupal.org/link-relations/revision description: A particular version of this resource. diff --git a/core/core.services.yml b/core/core.services.yml index 9e82af7..0fc93c1 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1132,11 +1132,6 @@ services: arguments: ['@entity_type.manager', '@entity_type.bundle.info'] tags: - { name: access_check, applies_to: _entity_create_any_access } - access_check.entity_delete_multiple: - class: Drupal\Core\Entity\EntityDeleteMultipleAccessCheck - arguments: ['@entity_type.manager', '@tempstore.private', '@request_stack'] - tags: - - { name: access_check, applies_to: _entity_delete_multiple_access } access_check.theme: class: Drupal\Core\Theme\ThemeAccessCheck arguments: ['@theme_handler'] diff --git a/core/lib/Drupal/Component/Transliteration/data/x03.php b/core/lib/Drupal/Component/Transliteration/data/x03.php index 1cc3129..f61602d 100644 --- a/core/lib/Drupal/Component/Transliteration/data/x03.php +++ b/core/lib/Drupal/Component/Transliteration/data/x03.php @@ -14,11 +14,11 @@ 0x50 => NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0x60 => '', '', '', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0x70 => NULL, NULL, NULL, NULL, '\'', ',', NULL, NULL, NULL, NULL, 'i', NULL, NULL, NULL, '?', NULL, - 0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ';', 'E', 'I', 'I', NULL, 'O', NULL, 'U', 'O', - 0x90 => 'I', 'A', 'B', 'G', 'D', 'E', 'Z', 'I', 'Th', 'I', 'K', 'L', 'M', 'N', 'X', 'O', - 0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'F', 'H', 'Ps', 'O', 'I', 'Y', 'a', 'e', 'i', 'i', - 0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'i', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o', - 0xC0 => 'p', 'r', 's', 's', 't', 'y', 'f', 'h', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL, + 0x80 => NULL, NULL, NULL, NULL, '', '', 'A', ':', 'E', 'E', 'I', NULL, 'O', NULL, 'Y', 'O', + 0x90 => 'i', 'A', 'B', 'G', 'D', 'E', 'Z', 'E', 'TH', 'I', 'K', 'L', 'M', 'N', 'X', 'O', + 0xA0 => 'P', 'R', NULL, 'S', 'T', 'Y', 'PH', 'CH', 'PS', 'O', 'I', 'Y', 'a', 'e', 'e', 'i', + 0xB0 => 'y', 'a', 'b', 'g', 'd', 'e', 'z', 'e', 'th', 'i', 'k', 'l', 'm', 'n', 'x', 'o', + 0xC0 => 'p', 'r', 's', 's', 't', 'y', 'ph', 'ch', 'ps', 'o', 'i', 'y', 'o', 'y', 'o', NULL, 0xD0 => 'b', 'th', 'Y', 'Y', 'Y', 'ph', 'p', '&', NULL, NULL, 'St', 'st', 'W', 'w', 'Q', 'q', 0xE0 => 'Sp', 'sp', 'Sh', 'sh', 'F', 'f', 'Kh', 'kh', 'H', 'h', 'G', 'g', 'CH', 'ch', 'Ti', 'ti', 0xF0 => 'k', 'r', 's', 'j', 'TH', 'e', NULL, 'S', 's', 'S', 'S', 's', NULL, NULL, NULL, NULL, diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php b/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php deleted file mode 100644 index fcf76a4..0000000 --- a/core/lib/Drupal/Core/Action/Plugin/Action/DeleteAction.php +++ /dev/null @@ -1,99 +0,0 @@ -currentUser = $current_user; - $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); - - parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager); - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { - return new static( - $configuration, - $plugin_id, - $plugin_definition, - $container->get('entity_type.manager'), - $container->get('tempstore.private'), - $container->get('current_user') - ); - } - - /** - * {@inheritdoc} - */ - public function executeMultiple(array $entities) { - /** @var \Drupal\Core\Entity\EntityInterface[] $entities */ - $selection = []; - foreach ($entities as $entity) { - $langcode = $entity->language()->getId(); - $selection[$entity->id()][$langcode] = $langcode; - } - $this->tempStore->set($this->currentUser->id() . ':' . $this->getPluginDefinition()['type'], $selection); - } - - /** - * {@inheritdoc} - */ - public function execute($object = NULL) { - $this->executeMultiple([$object]); - } - - /** - * {@inheritdoc} - */ - public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { - return $object->access('delete', $account, $return_as_object); - } - -} diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php index 5cd529a..29159c2 100644 --- a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php +++ b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityActionDeriverBase.php @@ -6,8 +6,6 @@ use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; -use Drupal\Core\StringTranslation\StringTranslationTrait; -use Drupal\Core\StringTranslation\TranslationInterface; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -15,8 +13,6 @@ */ abstract class EntityActionDeriverBase extends DeriverBase implements ContainerDeriverInterface { - use StringTranslationTrait; - /** * The entity type manager. * @@ -29,22 +25,16 @@ * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. - * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation - * The string translation service. */ - public function __construct(EntityTypeManagerInterface $entity_type_manager, TranslationInterface $string_translation) { + public function __construct(EntityTypeManagerInterface $entity_type_manager) { $this->entityTypeManager = $entity_type_manager; - $this->stringTranslation = $string_translation; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, $base_plugin_id) { - return new static( - $container->get('entity_type.manager'), - $container->get('string_translation') - ); + return new static($container->get('entity_type.manager')); } /** diff --git a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php b/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php deleted file mode 100644 index 4be4588..0000000 --- a/core/lib/Drupal/Core/Action/Plugin/Action/Derivative/EntityDeleteActionDeriver.php +++ /dev/null @@ -1,40 +0,0 @@ -derivatives)) { - $definitions = []; - foreach ($this->getApplicableEntityTypes() as $entity_type_id => $entity_type) { - $definition = $base_plugin_definition; - $definition['type'] = $entity_type_id; - $definition['label'] = $this->t('Delete @entity_type', ['@entity_type' => $entity_type->getSingularLabel()]); - $definition['confirm_form_route_name'] = 'entity.' . $entity_type->id() . '.delete_multiple_form'; - $definitions[$entity_type_id] = $definition; - } - $this->derivatives = $definitions; - } - - return $this->derivatives; - } - - /** - * {@inheritdoc} - */ - protected function isApplicable(EntityTypeInterface $entity_type) { - return $entity_type->hasLinkTemplate('delete-multiple-form'); - } - -} diff --git a/core/lib/Drupal/Core/Database/Query/SelectInterface.php b/core/lib/Drupal/Core/Database/Query/SelectInterface.php index e42fcc6..6a91e72 100644 --- a/core/lib/Drupal/Core/Database/Query/SelectInterface.php +++ b/core/lib/Drupal/Core/Database/Query/SelectInterface.php @@ -95,7 +95,7 @@ public function &getGroupBy(); * Note that this method must be called by reference as well: * * @code - * $tables =& $query->getTables(); + * $fields =& $query->getTables(); * @endcode * * @return diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php index cbb7f88..f43bc3b 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php @@ -20,4 +20,25 @@ */ interface ContentEntityInterface extends \Traversable, FieldableEntityInterface, TranslatableRevisionableInterface { + /** + * 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(); + + /** + * 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. + * + * @internal + * + * @return $this + */ + public function updateLoadedRevisionId(); + } diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php index 538dffd..b82af9b 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php @@ -152,23 +152,6 @@ protected function initFieldValues(ContentEntityInterface $entity, array $values /** * Checks whether any entity revision is translated. * - * @param \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\TranslatableInterface $entity - * The entity object to be checked. - * - * @return bool - * TRUE if the entity has at least one translation in any revision, FALSE - * otherwise. - * - * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages() - * @see \Drupal\Core\Entity\ContentEntityStorageBase::isAnyStoredRevisionTranslated() - */ - protected function isAnyRevisionTranslated(TranslatableInterface $entity) { - return $entity->getTranslationLanguages(FALSE) || $this->isAnyStoredRevisionTranslated($entity); - } - - /** - * Checks whether any stored entity revision is translated. - * * A revisionable entity can have translations in a pending revision, hence * the default revision may appear as not translated. This determines whether * the entity has any translation in the storage and thus should be considered @@ -182,9 +165,8 @@ protected function isAnyRevisionTranslated(TranslatableInterface $entity) { * otherwise. * * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages() - * @see \Drupal\Core\Entity\ContentEntityStorageBase::isAnyRevisionTranslated() */ - protected function isAnyStoredRevisionTranslated(TranslatableInterface $entity) { + protected function isAnyRevisionTranslated(TranslatableInterface $entity) { /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ if ($entity->isNew()) { return FALSE; diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php deleted file mode 100644 index 470c1b0..0000000 --- a/core/lib/Drupal/Core/Entity/EntityDeleteMultipleAccessCheck.php +++ /dev/null @@ -1,86 +0,0 @@ -entityTypeManager = $entity_type_manager; - $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); - $this->requestStack = $request_stack; - } - - /** - * Checks if the user has delete access for at least one item of the store. - * - * @param \Drupal\Core\Session\AccountInterface $account - * Run access checks for this account. - * @param string $entity_type_id - * Entity type ID. - * - * @return \Drupal\Core\Access\AccessResult - * Allowed or forbidden, neutral if tempstore is empty. - */ - public function access(AccountInterface $account, $entity_type_id) { - if (!$this->requestStack->getCurrentRequest()->getSession()) { - return AccessResult::neutral(); - } - $selection = $this->tempStore->get($account->id() . ':' . $entity_type_id); - if (empty($selection) || !is_array($selection)) { - return AccessResult::neutral(); - } - - $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($selection)); - foreach ($entities as $entity) { - // As long as the user has access to delete one entity allow access to the - // delete form. Access will be checked again in - // Drupal\Core\Entity\Form\DeleteMultipleForm::submit() in case it has - // changed in the meantime. - if ($entity->access('delete', $account)) { - return AccessResult::allowed(); - } - } - return AccessResult::forbidden(); - } - -} diff --git a/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php b/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php deleted file mode 100644 index aea3291..0000000 --- a/core/lib/Drupal/Core/Entity/Form/DeleteMultipleForm.php +++ /dev/null @@ -1,323 +0,0 @@ - langcodes format. - * - * @var array - */ - protected $selection = []; - - /** - * The entity type definition. - * - * @var \Drupal\Core\Entity\EntityTypeInterface - */ - protected $entityType; - - /** - * Constructs a new DeleteMultiple object. - * - * @param \Drupal\Core\Session\AccountInterface $current_user - * The current user. - * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager - * The entity type manager. - * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory - * The tempstore factory. - * @param \Drupal\Core\Messenger\MessengerInterface $messenger - * The messenger service. - */ - public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, MessengerInterface $messenger) { - $this->currentUser = $current_user; - $this->entityTypeManager = $entity_type_manager; - $this->tempStore = $temp_store_factory->get('entity_delete_multiple_confirm'); - $this->messenger = $messenger; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('current_user'), - $container->get('entity_type.manager'), - $container->get('tempstore.private'), - $container->get('messenger') - ); - } - - /** - * {@inheritdoc} - */ - public function getBaseFormId() { - return 'entity_delete_multiple_confirm_form'; - } - - /** - * {@inheritdoc} - */ - public function getFormId() { - // Get entity type ID from the route because ::buildForm has not yet been - // called. - $entity_type_id = $this->getRouteMatch()->getParameter('entity_type_id'); - return $entity_type_id . '_delete_multiple_confirm_form'; - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this @item?', 'Are you sure you want to delete these @items?', [ - '@item' => $this->entityType->getSingularLabel(), - '@items' => $this->entityType->getPluralLabel(), - ]); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - if ($this->entityType->hasLinkTemplate('collection')) { - return new Url('entity.' . $this->entityTypeId . '.collection'); - } - else { - return new Url(''); - } - } - - /** - * {@inheritdoc} - */ - public function getConfirmText() { - return $this->t('Delete'); - } - - /** - * {@inheritdoc} - */ - public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL) { - $this->entityTypeId = $entity_type_id; - $this->entityType = $this->entityTypeManager->getDefinition($this->entityTypeId); - $this->selection = $this->tempStore->get($this->currentUser->id() . ':' . $entity_type_id); - if (empty($this->entityTypeId) || empty($this->selection)) { - return new RedirectResponse($this->getCancelUrl() - ->setAbsolute() - ->toString()); - } - - $items = []; - $entities = $this->entityTypeManager->getStorage($entity_type_id)->loadMultiple(array_keys($this->selection)); - foreach ($this->selection as $id => $selected_langcodes) { - $entity = $entities[$id]; - foreach ($selected_langcodes as $langcode) { - $key = $id . ':' . $langcode; - if ($entity instanceof TranslatableInterface) { - $entity = $entity->getTranslation($langcode); - $default_key = $id . ':' . $entity->getUntranslated()->language()->getId(); - - // Build a nested list of translations that will be deleted if the - // entity has multiple translations. - $entity_languages = $entity->getTranslationLanguages(); - if (count($entity_languages) > 1 && $entity->isDefaultTranslation()) { - $names = []; - foreach ($entity_languages as $translation_langcode => $language) { - $names[] = $language->getName(); - unset($items[$id . ':' . $translation_langcode]); - } - $items[$default_key] = [ - 'label' => [ - '#markup' => $this->t('@label (Original translation) - The following @entity_type translations will be deleted:', - [ - '@label' => $entity->label(), - '@entity_type' => $this->entityType->getSingularLabel(), - ]), - ], - 'deleted_translations' => [ - '#theme' => 'item_list', - '#items' => $names, - ], - ]; - } - elseif (!isset($items[$default_key])) { - $items[$key] = $entity->label(); - } - } - elseif (!isset($items[$key])) { - $items[$key] = $entity->label(); - } - } - } - - $form['entities'] = [ - '#theme' => 'item_list', - '#items' => $items, - ]; - $form = parent::buildForm($form, $form_state); - - return $form; - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $total_count = 0; - $delete_entities = []; - $delete_translations = []; - $inaccessible_entities = []; - $storage = $this->entityTypeManager->getStorage($this->entityTypeId); - - $entities = $storage->loadMultiple(array_keys($this->selection)); - foreach ($this->selection as $id => $selected_langcodes) { - $entity = $entities[$id]; - if (!$entity->access('delete', $this->currentUser)) { - $inaccessible_entities[] = $entity; - continue; - } - foreach ($selected_langcodes as $langcode) { - if ($entity instanceof TranslatableInterface) { - $entity = $entity->getTranslation($langcode); - // If the entity is the default translation then deleting it will - // delete all the translations. - if ($entity->isDefaultTranslation()) { - $delete_entities[$id] = $entity; - // If there are translations already marked for deletion then remove - // them as they will be deleted anyway. - unset($delete_translations[$id]); - // Update the total count. Since a single delete will delete all - // translations, we need to add the number of translations to the - // count. - $total_count += count($entity->getTranslationLanguages()); - } - // Add the translation to the list of translations to be deleted - // unless the default translation is being deleted. - elseif (!isset($delete_entities[$id])) { - $delete_translations[$id][] = $entity; - } - } - elseif (!isset($delete_entities[$id])) { - $delete_entities[$id] = $entity; - $total_count++; - } - } - } - - if ($delete_entities) { - $storage->delete($delete_entities); - foreach ($delete_entities as $entity) { - $this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label has been deleted.', [ - '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), - '%label' => $entity->label(), - ]); - } - } - - if ($delete_translations) { - /** @var \Drupal\Core\Entity\TranslatableInterface[][] $delete_translations */ - foreach ($delete_translations as $id => $translations) { - $entity = $entities[$id]->getUntranslated(); - foreach ($translations as $translation) { - $entity->removeTranslation($translation->language()->getId()); - } - $entity->save(); - foreach ($translations as $translation) { - $this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [ - '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), - '%label' => $entity->label(), - '@language' => $translation->language()->getName(), - ]); - } - $total_count += count($translations); - } - } - - if ($total_count) { - $this->messenger->addStatus($this->getDeletedMessage($total_count)); - } - if ($inaccessible_entities) { - $this->messenger->addWarning($this->getInaccessibleMessage(count($inaccessible_entities))); - } - $this->tempStore->delete($this->currentUser->id()); - $form_state->setRedirectUrl($this->getCancelUrl()); - } - - /** - * Returns the message to show the user after an item was deleted. - * - * @param int $count - * Count of deleted translations. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * The item deleted message. - */ - protected function getDeletedMessage($count) { - return $this->formatPlural($count, 'Deleted @count item.', 'Deleted @count items.'); - } - - /** - * Returns the message to show the user when an item has not been deleted. - * - * @param int $count - * Count of deleted translations. - * - * @return \Drupal\Core\StringTranslation\TranslatableMarkup - * The item inaccessible message. - */ - protected function getInaccessibleMessage($count) { - return $this->formatPlural($count, "@count item has not been deleted because you do not have the necessary permissions.", "@count items have not been deleted because you do not have the necessary permissions."); - } - -} diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php index 383f41e..875b8c2 100644 --- a/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/ConditionAggregateInterface.php @@ -45,7 +45,7 @@ public function exists($field, $function, $langcode = NULL); * * @param string $field * @return ConditionInterface - * @see \Drupal\Core\Entity\Query\QueryInterface::notExists() + * @see \Drupal\Core\Entity\Query\QueryInterface::notexists() */ public function notExists($field, $function, $langcode = NULL); diff --git a/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php index 7657c00..d42e48f 100644 --- a/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/ConditionInterface.php @@ -51,7 +51,7 @@ public function exists($field, $langcode = NULL); * * @param string $field * @return ConditionInterface - * @see \Drupal\Core\Entity\Query\QueryInterface::notExists() + * @see \Drupal\Core\Entity\Query\QueryInterface::notexists() */ public function notExists($field, $langcode = NULL); diff --git a/core/lib/Drupal/Core/Entity/RevisionableInterface.php b/core/lib/Drupal/Core/Entity/RevisionableInterface.php index 58f1bc5..0fc6db2 100644 --- a/core/lib/Drupal/Core/Entity/RevisionableInterface.php +++ b/core/lib/Drupal/Core/Entity/RevisionableInterface.php @@ -40,27 +40,6 @@ public function setNewRevision($value = TRUE); public function getRevisionId(); /** - * 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(); - - /** - * 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. - * - * @internal - * - * @return $this - */ - public function updateLoadedRevisionId(); - - /** * Checks if this entity is the default revision. * * @param bool $new_value diff --git a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php index b04fc3e..1abee11 100644 --- a/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php +++ b/core/lib/Drupal/Core/Entity/Routing/AdminHtmlRouteProvider.php @@ -54,14 +54,4 @@ protected function getDeleteFormRoute(EntityTypeInterface $entity_type) { } } - /** - * {@inheritdoc} - */ - protected function getDeleteMultipleFormRoute(EntityTypeInterface $entity_type) { - if ($route = parent::getDeleteMultipleFormRoute($entity_type)) { - $route->setOption('_admin_route', TRUE); - return $route; - } - } - } diff --git a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php index 0a99b33..fb40fd5 100644 --- a/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php +++ b/core/lib/Drupal/Core/Entity/Routing/DefaultHtmlRouteProvider.php @@ -24,7 +24,6 @@ * - edit-form * - delete-form * - collection - * - delete-multiple-form * * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider. */ @@ -99,10 +98,6 @@ public function getRoutes(EntityTypeInterface $entity_type) { $collection->add("entity.{$entity_type_id}.collection", $collection_route); } - if ($delete_multiple_route = $this->getDeleteMultipleFormRoute($entity_type)) { - $collection->add('entity.' . $entity_type->id() . '.delete_multiple_form', $delete_multiple_route); - } - return $collection; } @@ -355,23 +350,4 @@ protected function getEntityTypeIdKeyType(EntityTypeInterface $entity_type) { return $field_storage_definitions[$entity_type->getKey('id')]->getType(); } - /** - * Returns the delete multiple form route. - * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type. - * - * @return \Symfony\Component\Routing\Route|null - * The generated route, if available. - */ - protected function getDeleteMultipleFormRoute(EntityTypeInterface $entity_type) { - if ($entity_type->hasLinkTemplate('delete-multiple-form') && $entity_type->hasHandlerClass('form', 'delete-multiple-confirm')) { - $route = new Route($entity_type->getLinkTemplate('delete-multiple-form')); - $route->setDefault('_form', $entity_type->getFormClass('delete-multiple-confirm')); - $route->setDefault('entity_type_id', $entity_type->id()); - $route->setRequirement('_entity_delete_multiple_access', $entity_type->id()); - return $route; - } - } - } diff --git a/core/lib/Drupal/Core/Routing/Enhancer/RouteEnhancerInterface.php b/core/lib/Drupal/Core/Routing/Enhancer/RouteEnhancerInterface.php index 781f9c6..d376985 100644 --- a/core/lib/Drupal/Core/Routing/Enhancer/RouteEnhancerInterface.php +++ b/core/lib/Drupal/Core/Routing/Enhancer/RouteEnhancerInterface.php @@ -5,7 +5,7 @@ use Drupal\Core\Routing\EnhancerInterface; use Symfony\Component\Routing\Route; -@trigger_error('\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Instead, you should use \Drupal\Core\Routing\EnhancerInterface. See https://www.drupal.org/node/2894934', E_USER_DEPRECATED); +@trigger_error('\Drupal\Core\Routing\Enhancer\RouteEnhancerInterface is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, you should use \Drupal\Core\Routing\EnhancerInterface. See https://www.drupal.org/node/2894934', E_USER_DEPRECATED); /** * A route enhance service to determine route enhance rules. diff --git a/core/lib/Drupal/Core/Routing/RouteFilterInterface.php b/core/lib/Drupal/Core/Routing/RouteFilterInterface.php index a0e122c..efe97a2 100644 --- a/core/lib/Drupal/Core/Routing/RouteFilterInterface.php +++ b/core/lib/Drupal/Core/Routing/RouteFilterInterface.php @@ -4,7 +4,7 @@ use Symfony\Component\Routing\Route; -@trigger_error('\Drupal\Core\Routing\Enhancer\RouteFilterInterface is deprecated in Drupal 8.5.0 and will be removed before Drupal 9.0.0. Instead, you should use \Drupal\Core\Routing\FilterInterface. See https://www.drupal.org/node/2894934', E_USER_DEPRECATED); +@trigger_error('\Drupal\Core\Routing\Enhancer\RouteFilterInterface is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, you should use \Drupal\Core\Routing\FilterInterface. See https://www.drupal.org/node/2894934', E_USER_DEPRECATED); /** * A route filter service to filter down the collection of route instances. diff --git a/core/modules/action/tests/src/Functional/BulkFormTest.php b/core/modules/action/tests/src/Functional/BulkFormTest.php index ec6ca48..439ffe4 100644 --- a/core/modules/action/tests/src/Functional/BulkFormTest.php +++ b/core/modules/action/tests/src/Functional/BulkFormTest.php @@ -148,7 +148,7 @@ public function testBulkForm() { $errors = $this->xpath('//div[contains(@class, "messages--status")]'); $this->assertFalse($errors, 'No action message shown.'); $this->drupalPostForm(NULL, [], t('Delete')); - $this->assertText(t('Deleted 5 content items.')); + $this->assertText(t('Deleted 5 posts.')); // Check if we got redirected to the original page. $this->assertUrl('test_bulk_form'); } diff --git a/core/modules/comment/comment.routing.yml b/core/modules/comment/comment.routing.yml index 6daeace..80ee86c 100644 --- a/core/modules/comment/comment.routing.yml +++ b/core/modules/comment/comment.routing.yml @@ -59,18 +59,8 @@ comment.multiple_delete_confirm: defaults: _title: 'Delete' _form: '\Drupal\comment\Form\ConfirmDeleteMultiple' - entity_type_id: 'comment' requirements: - _entity_delete_multiple_access: 'comment' - -entity.comment.delete_multiple_form: - path: '/admin/content/comment/delete' - defaults: - _title: 'Delete' - _form: '\Drupal\comment\Form\ConfirmDeleteMultiple' - entity_type_id: 'comment' - requirements: - _entity_delete_multiple_access: 'comment' + _permission: 'administer comments' comment.reply: path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}' diff --git a/core/modules/comment/config/install/system.action.comment_delete_action.yml b/core/modules/comment/config/install/system.action.comment_delete_action.yml index 08f7966..035299e 100644 --- a/core/modules/comment/config/install/system.action.comment_delete_action.yml +++ b/core/modules/comment/config/install/system.action.comment_delete_action.yml @@ -6,5 +6,5 @@ dependencies: id: comment_delete_action label: 'Delete comment' type: comment -plugin: entity:delete_action:comment +plugin: comment_delete_action configuration: { } diff --git a/core/modules/comment/config/schema/comment.schema.yml b/core/modules/comment/config/schema/comment.schema.yml index 0f64318..c29088b 100644 --- a/core/modules/comment/config/schema/comment.schema.yml +++ b/core/modules/comment/config/schema/comment.schema.yml @@ -44,8 +44,6 @@ action.configuration.comment_unpublish_action: type: action_configuration_default label: 'Unpublish comment configuration' -# @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. -# @see https://www.drupal.org/node/2934349 action.configuration.comment_delete_action: type: action_configuration_default label: 'Delete comment configuration' diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php index 5f5a611..233c752 100644 --- a/core/modules/comment/src/Entity/Comment.php +++ b/core/modules/comment/src/Entity/Comment.php @@ -56,7 +56,6 @@ * links = { * "canonical" = "/comment/{comment}", * "delete-form" = "/comment/{comment}/delete", - * "delete-multiple-form" = "/admin/content/comment/delete", * "edit-form" = "/comment/{comment}/edit", * "create" = "/comment", * }, diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php index 8a93ecd..4fe7291 100644 --- a/core/modules/comment/src/Form/CommentAdminOverview.php +++ b/core/modules/comment/src/Form/CommentAdminOverview.php @@ -290,9 +290,9 @@ public function submitForm(array &$form, FormStateInterface $form_state) { $info[$comment->id()][$langcode] = $langcode; } $this->tempStoreFactory - ->get('entity_delete_multiple_confirm') - ->set($this->currentUser()->id() . ':comment', $info); - $form_state->setRedirect('entity.comment.delete_multiple_form'); + ->get('comment_multiple_delete_confirm') + ->set($this->currentUser()->id(), $info); + $form_state->setRedirect('comment.multiple_delete_confirm'); } } diff --git a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php index 825b0af..7044393 100644 --- a/core/modules/comment/src/Form/ConfirmDeleteMultiple.php +++ b/core/modules/comment/src/Form/ConfirmDeleteMultiple.php @@ -2,21 +2,76 @@ namespace Drupal\comment\Form; -use Drupal\Core\Entity\Form\DeleteMultipleForm as EntityDeleteMultipleForm; +use Drupal\comment\CommentStorageInterface; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Provides the comment multiple delete confirmation form. * * @internal */ -class ConfirmDeleteMultiple extends EntityDeleteMultipleForm { +class ConfirmDeleteMultiple extends ConfirmFormBase { + + /** + * The tempstore factory. + * + * @var \Drupal\Core\TempStore\PrivateTempStoreFactory + */ + protected $tempStoreFactory; + + /** + * The comment storage. + * + * @var \Drupal\comment\CommentStorageInterface + */ + protected $commentStorage; + + /** + * An array of comments to be deleted. + * + * @var string[][] + */ + protected $commentInfo; + + /** + * Creates an new ConfirmDeleteMultiple form. + * + * @param \Drupal\comment\CommentStorageInterface $comment_storage + * The comment storage. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. + */ + public function __construct(CommentStorageInterface $comment_storage, PrivateTempStoreFactory $temp_store_factory) { + $this->commentStorage = $comment_storage; + $this->tempStoreFactory = $temp_store_factory; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('comment'), + $container->get('tempstore.private') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'comment_multiple_delete_confirm'; + } /** * {@inheritdoc} */ public function getQuestion() { - return $this->formatPlural(count($this->selection), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?'); + return $this->formatPlural(count($this->commentInfo), 'Are you sure you want to delete this comment and all its children?', 'Are you sure you want to delete these comments and all their children?'); } /** @@ -29,15 +84,116 @@ public function getCancelUrl() { /** * {@inheritdoc} */ - protected function getDeletedMessage($count) { - return $this->formatPlural($count, 'Deleted @count comment.', 'Deleted @count comments.'); + public function getConfirmText() { + return $this->t('Delete'); } /** * {@inheritdoc} */ - protected function getInaccessibleMessage($count) { - return $this->formatPlural($count, "@count comment has not been deleted because you do not have the necessary permissions.", "@count comments have not been deleted because you do not have the necessary permissions."); + public function buildForm(array $form, FormStateInterface $form_state) { + $this->commentInfo = $this->tempStoreFactory->get('comment_multiple_delete_confirm')->get($this->currentUser()->id()); + if (empty($this->commentInfo)) { + return $this->redirect('comment.admin'); + } + /** @var \Drupal\comment\CommentInterface[] $comments */ + $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); + + $items = []; + foreach ($this->commentInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $comment = $comments[$id]->getTranslation($langcode); + $key = $id . ':' . $langcode; + $default_key = $id . ':' . $comment->getUntranslated()->language()->getId(); + + // If we have a translated entity we build a nested list of translations + // that will be deleted. + $languages = $comment->getTranslationLanguages(); + if (count($languages) > 1 && $comment->isDefaultTranslation()) { + $names = []; + foreach ($languages as $translation_langcode => $language) { + $names[] = $language->getName(); + unset($items[$id . ':' . $translation_langcode]); + } + $items[$default_key] = [ + 'label' => [ + '#markup' => $this->t('@label (Original translation) - The following comment translations will be deleted:', ['@label' => $comment->label()]), + ], + 'deleted_translations' => [ + '#theme' => 'item_list', + '#items' => $names, + ], + ]; + } + elseif (!isset($items[$default_key])) { + $items[$key] = $comment->label(); + } + } + } + + $form['comments'] = [ + '#theme' => 'item_list', + '#items' => $items, + ]; + + return parent::buildForm($form, $form_state); + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + if ($form_state->getValue('confirm') && !empty($this->commentInfo)) { + $total_count = 0; + $delete_comments = []; + /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */ + $delete_translations = []; + /** @var \Drupal\comment\CommentInterface[] $comments */ + $comments = $this->commentStorage->loadMultiple(array_keys($this->commentInfo)); + + foreach ($this->commentInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $comment = $comments[$id]->getTranslation($langcode); + if ($comment->isDefaultTranslation()) { + $delete_comments[$id] = $comment; + unset($delete_translations[$id]); + $total_count += count($comment->getTranslationLanguages()); + } + elseif (!isset($delete_comments[$id])) { + $delete_translations[$id][] = $comment; + } + } + } + + if ($delete_comments) { + $this->commentStorage->delete($delete_comments); + $this->logger('content')->notice('Deleted @count comments.', ['@count' => count($delete_comments)]); + } + + if ($delete_translations) { + $count = 0; + foreach ($delete_translations as $id => $translations) { + $comment = $comments[$id]->getUntranslated(); + foreach ($translations as $translation) { + $comment->removeTranslation($translation->language()->getId()); + } + $comment->save(); + $count += count($translations); + } + if ($count) { + $total_count += $count; + $this->logger('content')->notice('Deleted @count comment translations.', ['@count' => $count]); + } + } + + if ($total_count) { + drupal_set_message($this->formatPlural($total_count, 'Deleted 1 comment.', 'Deleted @count comments.')); + } + + $this->tempStoreFactory->get('comment_multiple_delete_confirm')->delete($this->currentUser()->id()); + } + + $form_state->setRedirectUrl($this->getCancelUrl()); } } diff --git a/core/modules/comment/src/Plugin/Action/DeleteComment.php b/core/modules/comment/src/Plugin/Action/DeleteComment.php index ef6116c..5c6941d 100644 --- a/core/modules/comment/src/Plugin/Action/DeleteComment.php +++ b/core/modules/comment/src/Plugin/Action/DeleteComment.php @@ -2,33 +2,97 @@ namespace Drupal\comment\Plugin\Action; -use Drupal\Core\Action\Plugin\Action\DeleteAction; -use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Action\ActionBase; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Deletes a comment. * - * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. - * Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. - * - * @see \Drupal\Core\Action\Plugin\Action\DeleteAction - * @see https://www.drupal.org/node/2934349 - * * @Action( * id = "comment_delete_action", - * label = @Translation("Delete comment") + * label = @Translation("Delete comment"), + * type = "comment", + * confirm_form_route_name = "comment.multiple_delete_confirm" * ) */ -class DeleteComment extends DeleteAction { +class DeleteComment extends ActionBase implements ContainerFactoryPluginInterface { + + /** + * The tempstore object. + * + * @var \Drupal\Core\TempStore\PrivateTempStore + */ + protected $tempStore; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a new DeleteComment object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param array $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. + * @param \Drupal\Core\Session\AccountInterface $current_user + * The current user. + */ + public function __construct(array $configuration, $plugin_id, array $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { + $this->currentUser = $current_user; + $this->tempStore = $temp_store_factory->get('comment_multiple_delete_confirm'); + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('tempstore.private'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + $info = []; + /** @var \Drupal\comment\CommentInterface $comment */ + foreach ($entities as $comment) { + $langcode = $comment->language()->getId(); + $info[$comment->id()][$langcode] = $langcode; + } + $this->tempStore->set($this->currentUser->id(), $info); + } + + /** + * {@inheritdoc} + */ + public function execute($entity = NULL) { + $this->executeMultiple([$entity]); + } /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $temp_store_factory, $current_user); - @trigger_error(__NAMESPACE__ . '\DeleteComment is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', E_USER_DEPRECATED); + public function access($comment, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\comment\CommentInterface $comment */ + return $comment->access('delete', $account, $return_as_object); } } diff --git a/core/modules/content_moderation/content_moderation.info.yml b/core/modules/content_moderation/content_moderation.info.yml index 335e92b..b25c7ce 100644 --- a/core/modules/content_moderation/content_moderation.info.yml +++ b/core/modules/content_moderation/content_moderation.info.yml @@ -1,9 +1,9 @@ name: 'Content Moderation' type: module -description: 'Provides moderation states for content.' +description: 'Provides moderation states for content' version: VERSION core: 8.x -package: Core +package: Core (Experimental) configure: entity.workflow.collection dependencies: - workflows diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module index 985f91f..2ae1e0d 100644 --- a/core/modules/content_moderation/content_moderation.module +++ b/core/modules/content_moderation/content_moderation.module @@ -20,12 +20,10 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; -use Drupal\Core\Url; use Drupal\workflows\WorkflowInterface; use Drupal\Core\Action\Plugin\Action\PublishAction; use Drupal\Core\Action\Plugin\Action\UnpublishAction; use Drupal\workflows\Entity\Workflow; -use Drupal\views\Entity\View; /** * Implements hook_help(). @@ -36,20 +34,13 @@ function content_moderation_help($route_name, RouteMatchInterface $route_match) case 'help.page.content_moderation': $output = ''; $output .= '

' . t('About') . '

'; - $output .= '

' . t('The Content Moderation module allows you to expand on Drupal\'s "unpublished" and "published" states for content. It allows you to have a published version that is live, but have a separate working copy that is undergoing review before it is published. This is achieved by using Workflows to apply different states and transitions to entities as needed. For more information, see the online documentation for the Content Moderation module.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation', ':workflows' => Url::fromRoute('help.page', ['name' => 'workflows'])->toString()]) . '

'; + $output .= '

' . t('The Content Moderation module provides moderation for content by applying workflows to content. For more information, see the online documentation for the Content Moderation module.', [':content_moderation' => 'https://www.drupal.org/documentation/modules/content_moderation']) . '

'; $output .= '

' . t('Uses') . '

'; $output .= '
'; - $output .= '
' . t('Applying workflows') . '
'; - $output .= '
' . t('Content Moderation allows you to apply Workflows to content, custom blocks, and other content entities, to provide more fine-grained publishing options. For example, a Basic page might have states such as Draft and Published, with allowed transitions such as Draft to Published (making the current revision "live"), and Published to Draft (making a new draft revision of published content).', [':workflows' => Url::fromRoute('help.page', ['name' => 'workflows'])->toString(), ':field_help' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '
'; - if (\Drupal::moduleHandler()->moduleExists('views')) { - $moderated_content_view = View::load('moderated_content'); - if (isset($moderated_content_view) && $moderated_content_view->status() === TRUE) { - $output .= '
' . t('Moderating content') . '
'; - $output .= '
' . t('You can view a list of content awaiting moderation on the moderated content page. This will show any content in an unpublished state, such as Draft or Archived, to help surface content that requires more work from content editors.', [':moderated' => Url::fromRoute('view.moderated_content.moderated_content')->toString()]) . '
'; - } - } + $output .= '
' . t('Configuring workflows') . '
'; + $output .= '
' . t('Enable the Workflow UI module to create, edit and delete content moderation workflows.') . '

'; $output .= '
' . t('Configure Content Moderation permissions') . '
'; - $output .= '
' . t('Each transition is exposed as a permission. If a user has the permission for a transition, they can use the transition to change the state of the content item, from Draft to Published.') . '
'; + $output .= '
' . t('Each transition is exposed as a permission. If a user has the permission for a transition, then they can move that node from the start state to the end state') . '

'; $output .= '
'; return $output; } diff --git a/core/modules/content_moderation/content_moderation.services.yml b/core/modules/content_moderation/content_moderation.services.yml index 3d6055a..5035b67 100644 --- a/core/modules/content_moderation/content_moderation.services.yml +++ b/core/modules/content_moderation/content_moderation.services.yml @@ -1,7 +1,7 @@ services: paramconverter.latest_revision: class: Drupal\content_moderation\ParamConverter\EntityRevisionConverter - parent: paramconverter.entity + arguments: ['@entity.manager', '@content_moderation.moderation_information'] tags: - { name: paramconverter, priority: 5 } content_moderation.state_transition_validation: diff --git a/core/modules/content_moderation/css/content_moderation.module.css b/core/modules/content_moderation/css/content_moderation.module.css index 1df1823..4c6a0c1 100644 --- a/core/modules/content_moderation/css/content_moderation.module.css +++ b/core/modules/content_moderation/css/content_moderation.module.css @@ -2,7 +2,7 @@ * @file * Component styles for the content_moderation module. */ -.entity-moderation-form { +ul.entity-moderation-form { list-style: none; display: -webkit-flex; /* Safari */ display: flex; @@ -12,27 +12,31 @@ align-items: flex-start; } -.entity-moderation-form__item { +ul.entity-moderation-form li { margin-right: 2em; display: table; } -.entity-moderation-form__item:last-child { +ul.entity-moderation-form li:last-child { -webkit-align-self: flex-end; /* Safari */ align-self: flex-end; margin-right: 0; } -.entity-moderation-form .form-item { +ul.entity-moderation-form .form-item { margin-top: 1em; margin-bottom: 1em; } -.entity-moderation-form .form-item label { +ul.entity-moderation-form .form-item label { padding-bottom: 0.25em; display: table; } -.entity-moderation-form input[type=submit] { +ul.entity-moderation-form input[type=submit] { margin-bottom: 1.2em; } + +ul.entity-moderation-form .btn { + margin-bottom: 1.1em; +} diff --git a/core/modules/content_moderation/css/content_moderation.theme.css b/core/modules/content_moderation/css/content_moderation.theme.css index 827e911..36ebf04 100644 --- a/core/modules/content_moderation/css/content_moderation.theme.css +++ b/core/modules/content_moderation/css/content_moderation.theme.css @@ -2,7 +2,7 @@ * @file * Theme styles for the content_moderation module. */ -.entity-moderation-form { +ul.entity-moderation-form { border: 1px dashed #bbb; margin: 2em 0; background: #fff; diff --git a/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php b/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php index ac6af35..f72a47e 100644 --- a/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php +++ b/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php @@ -88,7 +88,7 @@ protected function getLatestVersionRoute(EntityTypeInterface $entity_type) { ->setOption('parameters', [ $entity_type_id => [ 'type' => 'entity:' . $entity_type_id, - 'load_latest_revision' => TRUE, + 'load_pending_revision' => 1, ], ]); diff --git a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php index effd0fe..1503f93 100644 --- a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php +++ b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php @@ -2,29 +2,104 @@ namespace Drupal\content_moderation\ParamConverter; +use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityManagerInterface; use Drupal\Core\ParamConverter\EntityConverter; +use Drupal\Core\TypedData\TranslatableInterface; +use Drupal\content_moderation\ModerationInformationInterface; +use Symfony\Component\Routing\Route; /** * Defines a class for making sure the edit-route loads the current draft. - * - * @internal - * This class only exists to provide backwards compatibility with the - * load_pending_revision flag, the predecessor to load_latest_revision. The - * core entity converter now natively loads the latest revision of an entity - * when the load_latest_revision flag is present. This flag is also added - * automatically to all entity forms. */ class EntityRevisionConverter extends EntityConverter { /** + * Moderation information service. + * + * @var \Drupal\content_moderation\ModerationInformationInterface + */ + protected $moderationInformation; + + /** + * EntityRevisionConverter constructor. + * + * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager + * The entity manager, needed by the parent class. + * @param \Drupal\content_moderation\ModerationInformationInterface $moderation_info + * The moderation info utility service. + */ + public function __construct(EntityManagerInterface $entity_manager, ModerationInformationInterface $moderation_info) { + parent::__construct($entity_manager, \Drupal::languageManager()); + $this->moderationInformation = $moderation_info; + } + + /** + * {@inheritdoc} + */ + public function applies($definition, $name, Route $route) { + return $this->hasPendingRevisionFlag($definition) || $this->isEditFormPage($route); + } + + /** + * Determines if the route definition includes a pending revision flag. + * + * This is a custom flag defined by the Content Moderation module to load + * pending revisions rather than the default revision on a given route. + * + * @param array $definition + * The parameter definition provided in the route options. + * + * @return bool + * TRUE if the pending revision flag is set, FALSE otherwise. + */ + protected function hasPendingRevisionFlag(array $definition) { + return (isset($definition['load_pending_revision']) && $definition['load_pending_revision']); + } + + /** + * Determines if a given route is the edit-form for an entity. + * + * @param \Symfony\Component\Routing\Route $route + * The route definition. + * + * @return bool + * Returns TRUE if the route is the edit form of an entity, FALSE otherwise. + */ + protected function isEditFormPage(Route $route) { + if ($default = $route->getDefault('_entity_form')) { + // If no operation is provided, use 'default'. + $default .= '.default'; + list($entity_type_id, $operation) = explode('.', $default); + if (!$this->entityManager->hasDefinition($entity_type_id)) { + return FALSE; + } + $entity_type = $this->entityManager->getDefinition($entity_type_id); + return in_array($operation, ['default', 'edit']) && $entity_type && $entity_type->isRevisionable(); + } + } + + /** * {@inheritdoc} */ public function convert($value, $definition, $name, array $defaults) { - if (!empty($definition['load_pending_revision'])) { - @trigger_error('The load_pending_revision flag has been deprecated. You should use load_latest_revision instead.', E_USER_DEPRECATED); - $definition['load_latest_revision'] = TRUE; + $entity = parent::convert($value, $definition, $name, $defaults); + + if ($entity && $this->moderationInformation->isModeratedEntity($entity) && !$this->moderationInformation->isLatestRevision($entity)) { + $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults); + $latest_revision = $this->moderationInformation->getLatestRevision($entity_type_id, $value); + + if ($latest_revision instanceof EntityInterface) { + // If the entity type is translatable, ensure we return the proper + // translation object for the current context. + if ($entity instanceof TranslatableInterface) { + $latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']); + } + $entity = $latest_revision; + } } - return parent::convert($value, $definition, $name, $defaults); + + return $entity; } } diff --git a/core/modules/content_moderation/templates/entity-moderation-form.html.twig b/core/modules/content_moderation/templates/entity-moderation-form.html.twig index 7638e37..b3665a7 100644 --- a/core/modules/content_moderation/templates/entity-moderation-form.html.twig +++ b/core/modules/content_moderation/templates/entity-moderation-form.html.twig @@ -1,8 +1,8 @@ {{ attach_library('content_moderation/content_moderation') }} {{ form|without('current', 'new_state', 'revision_log', 'submit') }} diff --git a/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php b/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php index 69eee60..3deaffa 100644 --- a/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php +++ b/core/modules/content_moderation/tests/src/Kernel/EntityRevisionConverterTest.php @@ -2,22 +2,22 @@ namespace Drupal\Tests\content_moderation\Kernel; +use Drupal\entity_test\Entity\EntityTest; +use Drupal\entity_test\Entity\EntityTestRev; use Drupal\KernelTests\KernelTestBase; use Drupal\node\Entity\Node; use Drupal\node\Entity\NodeType; +use Drupal\workflows\Entity\Workflow; /** * @coversDefaultClass \Drupal\content_moderation\ParamConverter\EntityRevisionConverter * @group content_moderation - * @group legacy */ class EntityRevisionConverterTest extends KernelTestBase { - /** - * {@inheritdoc} - */ public static $modules = [ 'user', + 'entity_test', 'system', 'content_moderation', 'node', @@ -25,40 +25,119 @@ class EntityRevisionConverterTest extends KernelTestBase { ]; /** + * The entity type manager. + * + * @var \Drupal\Core\Entity\EntityTypeManagerInterface + */ + protected $entityTypeManager; + + /** + * The router without access checks. + * + * @var \Symfony\Component\Routing\RouterInterface + */ + protected $router; + + /** * {@inheritdoc} */ protected function setUp() { parent::setUp(); + + $this->installEntitySchema('entity_test'); + $this->installEntitySchema('entity_test_rev'); $this->installEntitySchema('node'); $this->installEntitySchema('user'); + $this->installEntitySchema('content_moderation_state'); + $this->installSchema('system', 'router'); $this->installSchema('system', 'sequences'); $this->installSchema('node', 'node_access'); + $this->installConfig(['content_moderation']); + \Drupal::service('router.builder')->rebuild(); + + $this->entityTypeManager = $this->container->get('entity_type.manager'); + $this->router = $this->container->get('router.no_access_checks'); } /** * @covers ::convert - * @expectedDeprecationMessage The load_pending_revision flag has been deprecated. You should use load_latest_revision instead. */ - public function testDeprecatedLoadPendingRevisionFlag() { - NodeType::create([ + public function testConvertNonRevisionableEntityType() { + $entity_test = EntityTest::create([ + 'name' => 'test', + ]); + + $entity_test->save(); + + $result = $this->router->match('/entity_test/' . $entity_test->id()); + + $this->assertInstanceOf(EntityTest::class, $result['entity_test']); + $this->assertEquals($entity_test->getRevisionId(), $result['entity_test']->getRevisionId()); + } + + /** + * @covers ::applies + */ + public function testConvertNoEditFormHandler() { + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('entity_test_rev', 'entity_test_rev'); + $workflow->save(); + + $entity_test_rev = EntityTestRev::create([ + 'name' => 'Default Revision', + 'moderation_state' => 'published', + ]); + $entity_test_rev->save(); + + $entity_test_rev->name = 'Pending revision'; + $entity_test_rev->moderation_state = 'draft'; + $entity_test_rev->save(); + + // Ensure the entity type does not provide an explicit 'edit' form class. + $definition = $this->entityTypeManager->getDefinition($entity_test_rev->getEntityTypeId()); + $this->assertNull($definition->getFormClass('edit')); + + // Ensure the revision converter is invoked for the edit route. + $result = $this->router->match("/entity_test_rev/manage/{$entity_test_rev->id()}/edit"); + $this->assertEquals($entity_test_rev->getRevisionId(), $result['entity_test_rev']->getRevisionId()); + } + + /** + * @covers ::convert + */ + public function testConvertWithRevisionableEntityType() { + $node_type = NodeType::create([ 'type' => 'article', - ])->save(); + ]); + $node_type->save(); + $workflow = Workflow::load('editorial'); + $workflow->getTypePlugin()->addEntityTypeAndBundle('node', 'article'); + $workflow->save(); + $revision_ids = []; $node = Node::create([ 'title' => 'test', 'type' => 'article', ]); + $node->moderation_state->value = 'published'; + $node->save(); + + $revision_ids[] = $node->getRevisionId(); + + $node->setNewRevision(TRUE); $node->save(); + $revision_ids[] = $node->getRevisionId(); - $node->isDefaultRevision(FALSE); $node->setNewRevision(TRUE); + $node->moderation_state->value = 'draft'; $node->save(); + $revision_ids[] = $node->getRevisionId(); + + $result = $this->router->match('/node/' . $node->id() . '/edit'); - $converted = $this->container->get('paramconverter.latest_revision')->convert($node->id(), [ - 'load_pending_revision' => TRUE, - 'type' => 'entity:node', - ], 'node', []); - $this->assertEquals($converted->getLoadedRevisionId(), $node->getLoadedRevisionId()); + $this->assertInstanceOf(Node::class, $result['node']); + $this->assertEquals($revision_ids[2], $result['node']->getRevisionId()); + $this->assertFalse($result['node']->isDefaultRevision()); } } diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php index 6aae426..d1107eb 100644 --- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php +++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php @@ -70,6 +70,7 @@ protected function alterRoutes(RouteCollection $collection) { 'parameters' => [ $entity_type_id => [ 'type' => 'entity:' . $entity_type_id, + 'load_latest_revision' => TRUE, ], ], '_admin_route' => $is_admin, @@ -102,6 +103,7 @@ protected function alterRoutes(RouteCollection $collection) { ], $entity_type_id => [ 'type' => 'entity:' . $entity_type_id, + 'load_latest_revision' => TRUE, ], ], '_admin_route' => $is_admin, @@ -127,6 +129,7 @@ protected function alterRoutes(RouteCollection $collection) { ], $entity_type_id => [ 'type' => 'entity:' . $entity_type_id, + 'load_latest_revision' => TRUE, ], ], '_admin_route' => $is_admin, @@ -152,6 +155,7 @@ protected function alterRoutes(RouteCollection $collection) { ], $entity_type_id => [ 'type' => 'entity:' . $entity_type_id, + 'load_latest_revision' => TRUE, ], ], '_admin_route' => $is_admin, diff --git a/core/modules/datetime/datetime.install b/core/modules/datetime/datetime.install new file mode 100644 index 0000000..05f4780 --- /dev/null +++ b/core/modules/datetime/datetime.install @@ -0,0 +1,117 @@ +get('entity_type.manager'); + /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */ + $entity_field_manager = \Drupal::getContainer()->get('entity_field.manager'); + /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $entity_last_installed_schema_repository */ + $entity_last_installed_schema_repository = \Drupal::getContainer()->get('entity.last_installed_schema.repository'); + + $entity_type_manager->useCaches(FALSE); + $entity_field_manager->useCaches(FALSE); + + $change_list = []; + + foreach ($entity_type_manager->getDefinitions() as $entity_type_id => $entity_type) { + if ($entity_type_manager->getStorage($entity_type_id) instanceof \Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface) { + $field_changes = []; + + $storage_definitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id); + $original_storage_definitions = $entity_last_installed_schema_repository->getLastInstalledFieldStorageDefinitions($entity_type_id); + + // Detect updated field storage definitions. + foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) { + if ($storage_definition->getType() === 'datetime') { + if ($entity_type_manager->getStorage($entity_type_id)->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) { + $field_changes[$field_name] = TRUE; + } + } + } + + if ($field_changes) { + $change_list[$entity_type_id] = [ + 'entity_type' => $entity_type->id(), + 'field_storage_definitions' => $field_changes, + 'base_table' => $entity_type->getBaseTable(), + 'revision_table' => $entity_type->getRevisionTable(), + ]; + } + } + } + + $field_spec = [ + 'description' => 'The date value.', + 'type' => 'varchar', + 'length' => 25, + ]; + + $keys_new = [ + 'value' => ['value'], + ]; + + $schema = \Drupal::database()->schema(); + + foreach ($change_list as $entity_type_id => $changes) { + foreach (array_keys($changes['field_storage_definitions']) as $field_name) { + $value_field_name = $field_name . '_value'; + + $base_table = $changes['base_table'] . '__' . $field_name; + $schema->dropIndex($base_table, 'value'); + $schema->changeField($base_table, $value_field_name, $value_field_name, $field_spec, $keys_new); + + \Drupal::database() + ->update($base_table) + ->expression($value_field_name, "CONCAT({$value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->execute(); + + if ($changes['revision_table']) { + $revision_table = $changes['revision_table'] . '__' . $field_name; + $schema->dropIndex($revision_table, 'value'); + $schema->changeField($revision_table, $value_field_name, $value_field_name, $field_spec, $keys_new); + + \Drupal::database() + ->update($revision_table) + ->expression($value_field_name, "CONCAT({$value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->execute(); + } + } + } + + $definitions = \Drupal::keyValue('entity.definitions.installed')->getAll(); + $definitions_copy = $definitions; + foreach ($definitions as $item_name => $item_value) { + $suffix = '.field_storage_definitions'; + if (substr($item_name, -strlen($suffix)) == $suffix) { + foreach ($item_value as $field_name => $field_definition) { + if ($field_definition instanceof FieldStorageConfig && $field_definition->getType() === 'datetime') { + $reflection = new \ReflectionObject($field_definition); + $schema_property = $reflection->getProperty('schema'); + $schema_property->setAccessible(TRUE); + $schema = $field_definition->getSchema(); + $schema['columns']['value']['length'] = 25; + $schema_property->setValue($field_definition, $schema); + $schema_property->setAccessible(FALSE); + $definitions_copy[$item_name][$field_name] = $field_definition; + } + } + } + } + \Drupal::keyValue('entity.definitions.installed')->setMultiple($definitions_copy); +} diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module index 245d0c4..a925706 100644 --- a/core/modules/datetime/datetime.module +++ b/core/modules/datetime/datetime.module @@ -27,7 +27,7 @@ * * @see https://www.drupal.org/node/2912980 */ -const DATETIME_DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s'; +const DATETIME_DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s+00:00'; /** * Defines the format that dates should be stored in. diff --git a/core/modules/datetime/src/DateTimeComputed.php b/core/modules/datetime/src/DateTimeComputed.php index 16f664b..5ea3652 100644 --- a/core/modules/datetime/src/DateTimeComputed.php +++ b/core/modules/datetime/src/DateTimeComputed.php @@ -2,11 +2,11 @@ namespace Drupal\datetime; -use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\Core\TypedData\DataDefinitionInterface; use Drupal\Core\TypedData\TypedDataInterface; use Drupal\Core\TypedData\TypedData; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; /** @@ -48,6 +48,7 @@ public function getValue() { $datetime_type = $item->getFieldDefinition()->getSetting('datetime_type'); $storage_format = $datetime_type === DateTimeItem::DATETIME_TYPE_DATE ? DateTimeItemInterface::DATE_STORAGE_FORMAT : DateTimeItemInterface::DATETIME_STORAGE_FORMAT; + try { $date = DrupalDateTime::createFromFormat($storage_format, $value, DateTimeItemInterface::STORAGE_TIMEZONE); if ($date instanceof DrupalDateTime && !$date->hasErrors()) { diff --git a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php index a6680f6..1d41688 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php +++ b/core/modules/datetime/src/Plugin/Field/FieldFormatter/DateTimeFormatterBase.php @@ -227,7 +227,7 @@ protected function buildDate(DrupalDateTime $date) { */ protected function buildDateWithIsoAttribute(DrupalDateTime $date) { // Create the ISO date in Universal Time. - $iso_date = $date->format("Y-m-d\TH:i:s") . 'Z'; + $iso_date = $date->format("Y-m-d\TH:i:sP", ['timezone' => 'UTC']); $this->setTimeZone($date); diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php index 3264069..6528192 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItem.php @@ -69,7 +69,7 @@ public static function schema(FieldStorageDefinitionInterface $field_definition) 'value' => [ 'description' => 'The date value.', 'type' => 'varchar', - 'length' => 20, + 'length' => 25, ], ], 'indexes' => [ diff --git a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php index a9231c6..ae0343e 100644 --- a/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php +++ b/core/modules/datetime/src/Plugin/Field/FieldType/DateTimeItemInterface.php @@ -15,7 +15,7 @@ /** * Defines the format that date and time should be stored in. */ - const DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s'; + const DATETIME_STORAGE_FORMAT = 'Y-m-d\TH:i:s\+00:00'; /** * Defines the format that dates should be stored in. diff --git a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php index 5acd6fc..c9ceea3 100644 --- a/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php +++ b/core/modules/datetime/tests/src/Functional/DateTimeFieldTest.php @@ -116,7 +116,7 @@ public function testDateField() { // Verify that a date is displayed. Since this is a date-only // field, it is expected to display the time as 00:00:00. $expected = format_date($date->getTimestamp(), $new_value, '', DateTimeItemInterface::STORAGE_TIMEZONE); - $expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE); + $expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', DateTimeItemInterface::STORAGE_TIMEZONE); $output = $this->renderTestEntity($id); $expected_markup = ''; $this->assertContains($expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [ @@ -282,7 +282,7 @@ public function testDatetimeField() { case 'format_type': // Verify that a date is displayed. $expected = format_date($date->getTimestamp(), $new_value); - $expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $expected_iso = format_date($date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $output = $this->renderTestEntity($id); $expected_markup = ''; $this->assertContains($expected_markup, $output, SafeMarkup::format('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => $new_value, '%expected' => $expected, '%expected_iso' => $expected_iso])); diff --git a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php index ffce48c..1b02e08 100644 --- a/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php +++ b/core/modules/datetime/tests/src/Functional/EntityResource/EntityTest/EntityTestDatetimeTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Url; use Drupal\entity_test\Entity\EntityTest; use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem; +use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface; use Drupal\field\Entity\FieldConfig; use Drupal\field\Entity\FieldStorageConfig; use Drupal\Tests\rest\Functional\AnonResourceTestTrait; @@ -25,7 +26,7 @@ class EntityTestDatetimeTest extends EntityTestResourceTestBase { * * @var string */ - protected static $dateString = '2017-03-01T20:02:00'; + protected static $dateString = '2017-03-01T20:02:00+00:00'; /** * Datetime test field name. @@ -136,17 +137,17 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); $response = $this->request($method, $url, $request_options); - $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' is invalid for the format 'Y-m-d\\TH:i:s'\n"; + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' is invalid for the format '" . DateTimeItemInterface::DATETIME_STORAGE_FORMAT . "'\n"; $this->assertResourceErrorResponse(422, $message, $response); // DX: 422 when date format is incorrect. $normalization = $this->getNormalizedPostEntity(); - $value = '2017-13-55T20:02:00'; + $value = '2017-13-55T20:02:00+00:00'; $normalization[static::$fieldName][0]['value'] = $value; $request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format); $response = $this->request($method, $url, $request_options); - $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' did not parse properly for the format 'Y-m-d\\TH:i:s'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n"; + $message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' did not parse properly for the format '" . DateTimeItemInterface::DATETIME_STORAGE_FORMAT . "'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n"; $this->assertResourceErrorResponse(422, $message, $response); } } diff --git a/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php index 75f892a..dc145b1 100644 --- a/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php +++ b/core/modules/datetime/tests/src/Kernel/DateTimeItemTest.php @@ -69,7 +69,7 @@ public function testDateTime() { // Verify entity creation. $entity = EntityTest::create(); - $value = '2014-01-01T20:00:00'; + $value = '2014-01-01T20:00:00+00:00'; $entity->field_datetime = $value; $entity->name->value = $this->randomMachineName(); $this->entityValidateAndSave($entity); @@ -84,7 +84,7 @@ public function testDateTime() { $this->assertEquals(DateTimeItemInterface::STORAGE_TIMEZONE, $entity->field_datetime->date->getTimeZone()->getName()); // Verify changing the date value. - $new_value = '2016-11-04T00:21:00'; + $new_value = '2016-11-04T00:21:00+00:00'; $entity->field_datetime->value = $new_value; $this->assertEqual($entity->field_datetime->value, $new_value); $this->assertEquals(DateTimeItemInterface::STORAGE_TIMEZONE, $entity->field_datetime->date->getTimeZone()->getName()); @@ -164,7 +164,7 @@ public function testSetValue() { // Test DateTimeItem::setValue() using string. $entity = EntityTest::create(); - $value = '2014-01-01T20:00:00'; + $value = '2014-01-01T20:00:00+00:00'; $entity->get('field_datetime')->set(0, $value); $this->entityValidateAndSave($entity); // Load the entity and ensure the field was saved correctly. @@ -175,7 +175,7 @@ public function testSetValue() { // Test DateTimeItem::setValue() using property array. $entity = EntityTest::create(); - $value = '2014-01-01T20:00:00'; + $value = '2014-01-01T20:00:00+00:00'; $entity->set('field_datetime', $value); $this->entityValidateAndSave($entity); // Load the entity and ensure the field was saved correctly. @@ -220,7 +220,7 @@ public function testSetValueProperty() { $this->fieldStorage->setSetting('datetime_type', DateTimeItem::DATETIME_TYPE_DATETIME); $this->fieldStorage->save(); $entity = EntityTest::create(); - $value = '2014-01-01T20:00:00'; + $value = '2014-01-01T20:00:00+00:00'; $entity->set('field_datetime', $value); $this->entityValidateAndSave($entity); @@ -269,10 +269,10 @@ public function datetimeValidationProvider() { return [ // Valid ISO 8601 dates, but unsupported by DateTimeItem. ['2014-01-01T20:00:00Z'], - ['2014-01-01T20:00:00+04:00'], ['2014-01-01T20:00:00+0400'], ['2014-01-01T20:00:00+04'], ['2014-01-01T20:00:00.123'], + ['2014-01-01T20:00:00'], ['2014-01-01T200000'], ['2014-01-01T2000'], ['2014-01-01T20'], diff --git a/core/modules/datetime/tests/src/Kernel/Views/FilterDateTimeTest.php b/core/modules/datetime/tests/src/Kernel/Views/FilterDateTimeTest.php index 7cb0fa0..17cf74b 100644 --- a/core/modules/datetime/tests/src/Kernel/Views/FilterDateTimeTest.php +++ b/core/modules/datetime/tests/src/Kernel/Views/FilterDateTimeTest.php @@ -46,9 +46,9 @@ protected function setUp($import_test_views = TRUE) { // Add some basic test nodes. $dates = [ - '2000-10-10T00:01:30', - '2001-10-10T12:12:12', - '2002-10-10T14:14:14', + '2000-10-10T00:01:30+00:00', + '2001-10-10T12:12:12+00:00', + '2002-10-10T14:14:14+00:00', // The date storage timezone is used (this mimics the steps taken in the // widget: \Drupal\datetime\Plugin\Field\FieldWidget::messageFormValues(). \Drupal::service('date.formatter')->format(static::$date, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, DateTimeItemInterface::STORAGE_TIMEZONE), @@ -187,7 +187,7 @@ protected function _testExact() { $view->filter[$field]->value['max'] = ''; // Use the date from node 3. Use the site timezone (mimics a value entered // through the UI). - $view->filter[$field]->value['value'] = \Drupal::service('date.formatter')->format(static::$date, 'custom', DateTimeItemInterface::DATETIME_STORAGE_FORMAT, static::$timezone); + $view->filter[$field]->value['value'] = \Drupal::service('date.formatter')->format(static::$date, 'custom', 'Y-m-d\TH:i:s', static::$timezone); $view->setDisplay('default'); $this->executeView($view); $expected_result = [ diff --git a/core/modules/datetime_range/datetime_range.install b/core/modules/datetime_range/datetime_range.install new file mode 100644 index 0000000..1285cf9 --- /dev/null +++ b/core/modules/datetime_range/datetime_range.install @@ -0,0 +1,108 @@ +useCaches(FALSE); + $change_list = []; + + foreach ($entity_manager->getDefinitions() as $entity_type_id => $entity_type) { + if ($entity_manager->getStorage($entity_type_id) instanceof \Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface) { + $field_changes = []; + + $storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id); + $original_storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions($entity_type_id); + + // Detect updated field storage definitions. + foreach (array_intersect_key($storage_definitions, $original_storage_definitions) as $field_name => $storage_definition) { + if ($storage_definition->getType() === 'daterange') { + if ($entity_manager->getStorage($entity_type_id)->requiresFieldStorageSchemaChanges($storage_definition, $original_storage_definitions[$field_name])) { + $field_changes[$field_name] = TRUE; + } + } + } + + if ($field_changes) { + $change_list[$entity_type_id] = [ + 'field_storage_definitions' => $field_changes, + 'base_table' => $entity_type->getBaseTable(), + 'revision_table' => $entity_type->getRevisionTable(), + ]; + } + } + } + + $field_spec = [ + 'description' => 'The start date value.', + 'type' => 'varchar', + 'length' => 25, + ]; + + $end_field_spec = [ + 'description' => 'The end date value.', + 'type' => 'varchar', + 'length' => 25, + ]; + + $keys_new = [ + 'value' => ['value'], + ]; + + $end_keys_new = [ + 'end_value' => ['end_value'], + ]; + + $schema = \Drupal::database()->schema(); + + foreach ($change_list as $entity_type_id => $changes) { + foreach (array_keys($changes['field_storage_definitions']) as $field_name) { + $value_field_name = $field_name . '_value'; + $end_value_field_name = $field_name . 'end_value'; + + $base_table = $changes['base_table'] . '__' . $field_name; + $schema->dropIndex($base_table, 'value'); + $schema->changeField($base_table, $value_field_name, $value_field_name, $field_spec, $keys_new); + $schema->dropIndex($base_table, 'end_value'); + $schema->changeField($base_table, $end_value_field_name, $end_value_field_name, $end_field_spec, $end_keys_new); + + \Drupal::database() + ->update($base_table) + ->expression($value_field_name, "CONCAT({$value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->expression($end_value_field_name, "CONCAT({$end_value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->execute(); + + if ($changes['revision_table']) { + $revision_table = $changes['revision_table'] . '__' . $field_name; + $schema->dropIndex($revision_table, 'value'); + $schema->changeField($revision_table, $value_field_name, $value_field_name, $field_spec, $keys_new); + $schema->dropIndex($revision_table, 'end_value'); + $schema->changeField($revision_table, $end_value_field_name, $end_value_field_name, $end_field_spec, $end_keys_new); + + $results = \Drupal::database()->query('SELECT * FROM ' . $revision_table)->fetchAll(); + foreach ($results as &$result) { + \Drupal::database() + ->update($revision_table) + ->expression($value_field_name, "CONCAT({$value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->expression($end_value_field_name, "CONCAT({$end_value_field_name}, :offset)", [ + ':offset' => '+00:00', + ]) + ->execute(); + } + } + } + } +} diff --git a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php index bd83b98..b143e8c 100644 --- a/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php +++ b/core/modules/datetime_range/tests/src/Functional/DateRangeFieldTest.php @@ -130,10 +130,10 @@ public function testDateRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', DateTimeItemInterface::STORAGE_TIMEZONE); $start_expected_markup = ''; $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE); - $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', DateTimeItemInterface::STORAGE_TIMEZONE); $end_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [ @@ -228,7 +228,7 @@ public function testDateRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long', '', DateTimeItemInterface::STORAGE_TIMEZONE); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', DateTimeItemInterface::STORAGE_TIMEZONE); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', DateTimeItemInterface::STORAGE_TIMEZONE); $start_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute in %timezone.', [ @@ -332,10 +332,10 @@ public function testDatetimeRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $start_expected_markup = ''; $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); - $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $end_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); @@ -413,7 +413,7 @@ public function testDatetimeRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $start_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); @@ -500,10 +500,10 @@ public function testAlldayRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $start_expected_markup = ''; $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); - $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $end_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); @@ -580,10 +580,10 @@ public function testAlldayRangeField() { ->save(); $start_expected = $this->dateFormatter->format($start_date->getTimestamp(), 'long'); - $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $start_expected_iso = $this->dateFormatter->format($start_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $start_expected_markup = ''; $end_expected = $this->dateFormatter->format($end_date->getTimestamp(), 'long'); - $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s\Z', 'UTC'); + $end_expected_iso = $this->dateFormatter->format($end_date->getTimestamp(), 'custom', 'Y-m-d\TH:i:s+00:00', 'UTC'); $end_expected_markup = ''; $output = $this->renderTestEntity($id); $this->assertContains($start_expected_markup, $output, new FormattableMarkup('Formatted date field using %value format displayed as %expected with %expected_iso attribute.', ['%value' => 'long', '%expected' => $start_expected, '%expected_iso' => $start_expected_iso])); diff --git a/core/modules/layout_builder/js/layout-builder.es6.js b/core/modules/layout_builder/js/layout-builder.es6.js index cdb5bee..391e462 100644 --- a/core/modules/layout_builder/js/layout-builder.es6.js +++ b/core/modules/layout_builder/js/layout-builder.es6.js @@ -14,19 +14,15 @@ * An object containing information about the item being sorted. */ update(event, ui) { - // Check if the region from the event and region for the item match. - const itemRegion = ui.item.closest('.layout-builder--layout__region'); - if (event.target === itemRegion[0]) { - // Find the destination delta. - const deltaTo = ui.item.closest('[data-layout-delta]').data('layout-delta'); - // If the block didn't leave the original delta use the destination. - const deltaFrom = ui.sender ? ui.sender.closest('[data-layout-delta]').data('layout-delta') : deltaTo; + // Only process if the item was moved from one region to another. + if (ui.sender) { ajax({ url: [ ui.item.closest('[data-layout-update-url]').data('layout-update-url'), - deltaFrom, - deltaTo, - itemRegion.data('region'), + ui.sender.closest('[data-layout-delta]').data('layout-delta'), + ui.item.closest('[data-layout-delta]').data('layout-delta'), + ui.sender.data('region'), + $(this).data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid'), ] diff --git a/core/modules/layout_builder/js/layout-builder.js b/core/modules/layout_builder/js/layout-builder.js index 661c5e3..d4dadc8 100644 --- a/core/modules/layout_builder/js/layout-builder.js +++ b/core/modules/layout_builder/js/layout-builder.js @@ -16,13 +16,9 @@ connectWith: '.layout-builder--layout__region', update: function update(event, ui) { - var itemRegion = ui.item.closest('.layout-builder--layout__region'); - if (event.target === itemRegion[0]) { - var deltaTo = ui.item.closest('[data-layout-delta]').data('layout-delta'); - - var deltaFrom = ui.sender ? ui.sender.closest('[data-layout-delta]').data('layout-delta') : deltaTo; + if (ui.sender) { ajax({ - url: [ui.item.closest('[data-layout-update-url]').data('layout-update-url'), deltaFrom, deltaTo, itemRegion.data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) { + url: [ui.item.closest('[data-layout-update-url]').data('layout-update-url'), ui.sender.closest('[data-layout-delta]').data('layout-delta'), ui.item.closest('[data-layout-delta]').data('layout-delta'), ui.sender.data('region'), $(this).data('region'), ui.item.data('layout-block-uuid'), ui.item.prev('[data-layout-block-uuid]').data('layout-block-uuid')].filter(function (element) { return element !== undefined; }).join('/') }).execute(); diff --git a/core/modules/layout_builder/layout_builder.routing.yml b/core/modules/layout_builder/layout_builder.routing.yml index 9846553..085eb30 100644 --- a/core/modules/layout_builder/layout_builder.routing.yml +++ b/core/modules/layout_builder/layout_builder.routing.yml @@ -99,7 +99,7 @@ layout_builder.remove_block: layout_builder_tempstore: TRUE layout_builder.move_block: - path: '/layout_builder/move/block/{section_storage_type}/{section_storage}/{delta_from}/{delta_to}/{region_to}/{block_uuid}/{preceding_block_uuid}' + path: '/layout_builder/move/block/{section_storage_type}/{section_storage}/{delta_from}/{delta_to}/{region_from}/{region_to}/{block_uuid}/{preceding_block_uuid}' defaults: _controller: '\Drupal\layout_builder\Controller\MoveBlockController::build' delta_from: null diff --git a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php index 5ccddfa..3f3b95e 100644 --- a/core/modules/layout_builder/src/Controller/LayoutBuilderController.php +++ b/core/modules/layout_builder/src/Controller/LayoutBuilderController.php @@ -215,7 +215,7 @@ protected function buildAdministrativeSection(SectionStorageInterface $section_s ]; $build[$region]['layout_builder_add_block']['#type'] = 'container'; $build[$region]['layout_builder_add_block']['#attributes'] = ['class' => ['add-block']]; - $build[$region]['layout_builder_add_block']['#weight'] = 1000; + $build[$region]['layout_builder_add_block']['#weight'] = -1000; $build[$region]['#attributes']['data-region'] = $region; $build[$region]['#attributes']['class'][] = 'layout-builder--layout__region'; } diff --git a/core/modules/layout_builder/src/Controller/MoveBlockController.php b/core/modules/layout_builder/src/Controller/MoveBlockController.php index cb8e71f..7411aa0 100644 --- a/core/modules/layout_builder/src/Controller/MoveBlockController.php +++ b/core/modules/layout_builder/src/Controller/MoveBlockController.php @@ -56,6 +56,8 @@ public static function create(ContainerInterface $container) { * The delta of the original section. * @param int $delta_to * The delta of the destination section. + * @param string $region_from + * The original region for this block. * @param string $region_to * The new region for this block. * @param string $block_uuid @@ -66,7 +68,7 @@ public static function create(ContainerInterface $container) { * @return \Drupal\Core\Ajax\AjaxResponse * An AJAX response. */ - public function build(SectionStorageInterface $section_storage, $delta_from, $delta_to, $region_to, $block_uuid, $preceding_block_uuid = NULL) { + public function build(SectionStorageInterface $section_storage, $delta_from, $delta_to, $region_from, $region_to, $block_uuid, $preceding_block_uuid = NULL) { $section = $section_storage->getSection($delta_from); $component = $section->getComponent($block_uuid); @@ -85,7 +87,7 @@ public function build(SectionStorageInterface $section_storage, $delta_from, $de $section->insertAfterComponent($preceding_block_uuid, $component); } else { - $section->insertComponent(0, $component); + $section->appendComponent($component); } $this->layoutTempstoreRepository->set($section_storage); diff --git a/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php b/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php index e7ae850..cbce0a7 100644 --- a/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php +++ b/core/modules/layout_builder/tests/src/FunctionalJavascript/FieldBlockTest.php @@ -41,7 +41,7 @@ protected function setUp() { 'administer blocks', 'access administration pages', ]); - $user->field_date = '1978-11-19T05:00:00'; + $user->field_date = '1978-11-19T05:00:00+00:00'; $user->save(); $this->drupalLogin($user); } diff --git a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php index df7a5ff..67c2dfa 100644 --- a/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php +++ b/core/modules/locale/tests/src/Functional/LocaleConfigTranslationImportTest.php @@ -21,18 +21,6 @@ class LocaleConfigTranslationImportTest extends BrowserTestBase { public static $modules = ['language', 'locale_test_translate']; /** - * {@inheritdoc} - */ - protected function setUp() { - parent::setUp(); - - // @todo Re-enable the test and only skip it when the translation cannot be - // downloaded, or provide a translation as a fixture instead. See - // https://www.drupal.org/project/drupal/issues/2828143. - $this->markTestSkipped(); - } - - /** * Test update changes configuration translations if enabled after language. */ public function testConfigTranslationImport() { diff --git a/core/modules/migrate_drupal_ui/css/components/upgrade-analysis-report-tables.css b/core/modules/migrate_drupal_ui/css/components/upgrade-analysis-report-tables.css index 1de1a1e..f15ff6a 100644 --- a/core/modules/migrate_drupal_ui/css/components/upgrade-analysis-report-tables.css +++ b/core/modules/migrate_drupal_ui/css/components/upgrade-analysis-report-tables.css @@ -18,6 +18,3 @@ .upgrade-analysis-report__status-icon--checked:before { background-image: url(../../../../misc/icons/73b355/check.svg); } -.upgrade-analysis-report__status-icon--error:before { - background-image: url(../../../../misc/icons/e32700/error.svg); -} diff --git a/core/modules/migrate_drupal_ui/migrate_drupal_ui.module b/core/modules/migrate_drupal_ui/migrate_drupal_ui.module index 0a8471a..5f06cdc 100644 --- a/core/modules/migrate_drupal_ui/migrate_drupal_ui.module +++ b/core/modules/migrate_drupal_ui/migrate_drupal_ui.module @@ -26,7 +26,7 @@ function migrate_drupal_ui_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('On the Upgrade page, you are guided through performing the upgrade in several steps.', [':upgrade' => \Drupal::url('migrate_drupal_ui.upgrade')]) . '
'; $output .= '
  1. ' . t('You need to enter the database credentials of the Drupal site that you want to upgrade. You can also include its files directory in the upgrade.') . '
  2. '; - $output .= '
  3. ' . t('The next page provides an overview of the modules that will be upgraded and those that will not be upgraded, before you proceed to perform the upgrade.') . '
  4. '; + $output .= '
  5. ' . t('The next page then provides an overview of which upgrade paths are available or missing, before you proceed to perform the upgrade.') . '
  6. '; $output .= '
  7. ' . t('Lastly, a message is displayed about the number of upgrade tasks that were successful or failed.') . '
'; $output .= '
' . t('Reviewing the upgrade log') . '
'; $output .= '
' . t('You can review a log of upgrade messages by clicking the link in the message provided after the upgrade or by filtering the messages for the type migrate_drupal_ui on the Recent log messages page.', diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php index c080a39..04da0f5 100644 --- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php +++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php @@ -6,7 +6,6 @@ use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Form\ConfirmFormBase; use Drupal\Core\Form\FormStateInterface; -use Drupal\Core\Messenger\MessengerInterface; use Drupal\Core\Render\RendererInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Url; @@ -28,13 +27,6 @@ class MigrateUpgradeForm extends ConfirmFormBase { use MigrationConfigurationTrait; /** - * The current form step. - * - * @var string - */ - protected $step; - - /** * The state service. * * @var \Drupal\Core\State\StateInterface @@ -77,13 +69,6 @@ class MigrateUpgradeForm extends ConfirmFormBase { protected $moduleHandler; /** - * The messenger service. - * - * @var \Drupal\Core\Messenger\MessengerInterface - */ - protected $messenger; - - /** * List of extensions that do not need an upgrade path. * * This property is an array where the keys are the major Drupal core version @@ -195,14 +180,13 @@ class MigrateUpgradeForm extends ConfirmFormBase { * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. */ - public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, RendererInterface $renderer, MigrationPluginManagerInterface $plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, ModuleHandlerInterface $module_handler, MessengerInterface $messenger) { + public function __construct(StateInterface $state, DateFormatterInterface $date_formatter, RendererInterface $renderer, MigrationPluginManagerInterface $plugin_manager, MigrateFieldPluginManagerInterface $field_plugin_manager, ModuleHandlerInterface $module_handler) { $this->state = $state; $this->dateFormatter = $date_formatter; $this->renderer = $renderer; $this->pluginManager = $plugin_manager; $this->fieldPluginManager = $field_plugin_manager; $this->moduleHandler = $module_handler; - $this->messenger = $messenger; } /** @@ -215,8 +199,7 @@ public static function create(ContainerInterface $container) { $container->get('renderer'), $container->get('plugin.manager.migration'), $container->get('plugin.manager.migrate.field'), - $container->get('module_handler'), - $container->get('messenger') + $container->get('module_handler') ); } @@ -231,8 +214,8 @@ public function getFormId() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { - $this->step = $form_state->get('step') ?: 'overview'; - switch ($this->step) { + $step = $form_state->get('step') ?: 'overview'; + switch ($step) { case 'overview': return $this->buildOverviewForm($form, $form_state); @@ -246,7 +229,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { return $this->buildConfirmForm($form, $form_state); default: - $this->messenger->addError($this->t('Unrecognized form step @step', ['@step' => $this->step])); + drupal_set_message($this->t('Unrecognized form step @step', ['@step' => $step]), 'error'); return []; } } @@ -618,7 +601,7 @@ public function buildIdConflictForm(array &$form, FormStateInterface $form_state return $this->buildForm($form, $form_state); } - $this->messenger->addWarning($this->t('WARNING: Content may be overwritten on your new site.')); + drupal_set_message($this->t('WARNING: Content may be overwritten on your new site.'), 'warning'); $form = parent::buildForm($form, $form_state); $form['actions']['submit']['#submit'] = ['::submitConfirmIdConflictForm']; @@ -759,11 +742,11 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { $migration_id = $migration->getPluginId(); $source_module = $migration->getSourcePlugin()->getSourceModule(); if (!$source_module) { - $this->messenger->addError($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id])); + drupal_set_message($this->t('Source module not found for @migration_id.', ['@migration_id' => $migration_id]), 'error'); } $destination_module = $migration->getDestinationPlugin()->getDestinationModule(); if (!$destination_module) { - $this->messenger->addError($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id])); + drupal_set_message($this->t('Destination module not found for @migration_id.', ['@migration_id' => $migration_id]), 'error'); } if ($source_module && $destination_module) { @@ -815,17 +798,17 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { '#title' => [ '#type' => 'html_tag', '#tag' => 'span', - '#value' => $this->t('Modules that will not be upgraded'), - '#attributes' => ['id' => ['error']], + '#value' => $this->t('Missing upgrade paths'), + '#attributes' => ['id' => ['warning']], ], - '#description' => $this->t('There are no modules installed on your new site to replace these modules. If you proceed with the upgrade now, configuration and/or content needed by these modules will not be available on your new site. For more information, see Review the pre-upgrade analysis in the Upgrading to Drupal 8 handbook.', [':review' => 'https://www.drupal.org/docs/8/upgrade/upgrade-using-web-browser#pre-upgrade-analysis', ':migrate' => 'https://www.drupal.org/docs/8/upgrade']), + '#description' => $this->t('The following items will not be upgraded. For more information see Upgrading from Drupal 6 or 7 to Drupal 8.', [':migrate' => 'https://www.drupal.org/upgrade/migrate']), '#weight' => 2, ]; $missing_module_list['module_list'] = [ '#type' => 'table', '#header' => [ - $this->t('Drupal @version', ['@version' => $version]), - $this->t('Drupal 8'), + $this->t('Source module: Drupal @version', ['@version' => $version]), + $this->t('Upgrade module: Drupal 8'), ], ]; $missing_count = 0; @@ -841,22 +824,21 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { '#attributes' => [ 'class' => [ 'upgrade-analysis-report__status-icon', - 'upgrade-analysis-report__status-icon--error', + 'upgrade-analysis-report__status-icon--warning', ], ], ], - 'destination_module' => ['#plain_text' => 'Not upgraded'], + 'destination_module' => ['#plain_text' => 'Missing'], ]; } } - // Available migrations. $available_module_list = [ '#type' => 'details', '#title' => [ '#type' => 'html_tag', '#tag' => 'span', - '#value' => $this->t('Modules that will be upgraded'), + '#value' => $this->t('Available upgrade paths'), '#attributes' => ['id' => ['checked']], ], '#weight' => 3, @@ -865,8 +847,8 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { $available_module_list['module_list'] = [ '#type' => 'table', '#header' => [ - $this->t('Drupal @version', ['@version' => $version]), - $this->t('Drupal 8'), + $this->t('Source module: Drupal @version', ['@version' => $version]), + $this->t('Upgrade module: Drupal 8'), ], ]; @@ -903,8 +885,8 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { $counters[] = [ '#theme' => 'status_report_counter', '#amount' => $missing_count, - '#text' => $this->formatPlural($missing_count, 'Module will not be upgraded', 'Modules will not be upgraded'), - '#severity' => 'error', + '#text' => $this->formatPlural($missing_count, 'Missing upgrade path', 'Missing upgrade paths'), + '#severity' => 'warning', '#weight' => 0, ]; $general_info[] = $missing_module_list; @@ -913,7 +895,7 @@ public function buildConfirmForm(array $form, FormStateInterface $form_state) { $counters[] = [ '#theme' => 'status_report_counter', '#amount' => $available_count, - '#text' => $this->formatPlural($available_count, 'Module will be upgraded', 'Modules will be upgraded'), + '#text' => $this->formatPlural($available_count, 'Available upgrade path', 'Available upgrade paths'), '#severity' => 'checked', '#weight' => 1, ]; @@ -978,10 +960,7 @@ protected function getDatabaseTypes() { * {@inheritdoc} */ public function getQuestion() { - if ($this->step === 'confirm_id_conflicts') { - return $this->t('Upgrade analysis report'); - } - return $this->t('What will be upgraded?'); + return $this->t('Upgrade analysis report'); } /** diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php index 4758a5e..cc450ec 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeExecuteTestBase.php @@ -99,7 +99,7 @@ public function testMigrateUpgradeExecute() { $session->pageTextContains('There is translated content of these types:'); $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.')); $session->statusCodeEquals(200); - $session->pageTextContains('What will be upgraded?'); + $session->pageTextContains('Upgrade analysis report'); // Ensure there are no errors about missing modules from the test module. $session->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.')); $session->pageTextNotContains(t('Source module not found for migration_provider_test.')); diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php index 624a052..8496b11 100644 --- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php +++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php @@ -132,17 +132,17 @@ protected function assertUpgradePaths(WebAssert $session, array $available_paths // Test the available migration paths. foreach ($available_paths as $available) { $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']"); - $session->elementNotExists('xpath', "//span[contains(@class, 'error') and text() = '$available']"); + $session->elementNotExists('xpath', "//span[contains(@class, 'warning') and text() = '$available']"); } // Test the missing migration paths. foreach ($missing_paths as $missing) { - $session->elementExists('xpath', "//span[contains(@class, 'error') and text() = '$missing']"); + $session->elementExists('xpath', "//span[contains(@class, 'warning') and text() = '$missing']"); $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']"); } // Test the total count of missing and available paths. - $session->elementsCount('xpath', "//span[contains(@class, 'upgrade-analysis-report__status-icon--error')]", count($missing_paths)); + $session->elementsCount('xpath', "//span[contains(@class, 'upgrade-analysis-report__status-icon--warning')]", count($missing_paths)); $session->elementsCount('xpath', "//span[contains(@class, 'upgrade-analysis-report__status-icon--checked')]", count($available_paths)); } diff --git a/core/modules/node/config/install/system.action.node_delete_action.yml b/core/modules/node/config/install/system.action.node_delete_action.yml index da2d886..97662dc 100644 --- a/core/modules/node/config/install/system.action.node_delete_action.yml +++ b/core/modules/node/config/install/system.action.node_delete_action.yml @@ -6,5 +6,5 @@ dependencies: id: node_delete_action label: 'Delete content' type: node -plugin: entity:delete_action:node +plugin: node_delete_action configuration: { } diff --git a/core/modules/node/config/schema/node.schema.yml b/core/modules/node/config/schema/node.schema.yml index b6fb7bc..50b3f3e 100644 --- a/core/modules/node/config/schema/node.schema.yml +++ b/core/modules/node/config/schema/node.schema.yml @@ -80,8 +80,6 @@ action.configuration.node_save_action: type: action_configuration_default label: 'Save content configuration' -# @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. -# @see https://www.drupal.org/node/2934349 action.configuration.node_delete_action: type: action_configuration_default label: 'Delete content configuration' diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml index d592bd1..a016e3a 100644 --- a/core/modules/node/node.routing.yml +++ b/core/modules/node/node.routing.yml @@ -2,17 +2,8 @@ node.multiple_delete_confirm: path: '/admin/content/node/delete' defaults: _form: '\Drupal\node\Form\DeleteMultiple' - entity_type_id: 'node' requirements: - _entity_delete_multiple_access: 'node' - -entity.node.delete_multiple_form: - path: '/admin/content/node/delete' - defaults: - _form: '\Drupal\node\Form\DeleteMultiple' - entity_type_id: 'node' - requirements: - _entity_delete_multiple_access: 'node' + _permission: 'administer nodes' node.add_page: path: '/node/add' diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php index ccee95b..368ce1b 100644 --- a/core/modules/node/src/Entity/Node.php +++ b/core/modules/node/src/Entity/Node.php @@ -33,8 +33,7 @@ * "form" = { * "default" = "Drupal\node\NodeForm", * "delete" = "Drupal\node\Form\NodeDeleteForm", - * "edit" = "Drupal\node\NodeForm", - * "delete-multiple-confirm" = "Drupal\node\Form\DeleteMultiple" + * "edit" = "Drupal\node\NodeForm" * }, * "route_provider" = { * "html" = "Drupal\node\Entity\NodeRouteProvider", @@ -72,7 +71,6 @@ * links = { * "canonical" = "/node/{node}", * "delete-form" = "/node/{node}/delete", - * "delete-multiple-form" = "/admin/content/node/delete", * "edit-form" = "/node/{node}/edit", * "version-history" = "/node/{node}/revisions", * "revision" = "/node/{node}/revisions/{node_revision}/view", diff --git a/core/modules/node/src/Form/DeleteMultiple.php b/core/modules/node/src/Form/DeleteMultiple.php index a36de5f..ef2c89f 100644 --- a/core/modules/node/src/Form/DeleteMultiple.php +++ b/core/modules/node/src/Form/DeleteMultiple.php @@ -2,15 +2,78 @@ namespace Drupal\node\Form; -use Drupal\Core\Entity\Form\DeleteMultipleForm as EntityDeleteMultipleForm; +use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; +use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\RedirectResponse; /** * Provides a node deletion confirmation form. * * @internal */ -class DeleteMultiple extends EntityDeleteMultipleForm { +class DeleteMultiple extends ConfirmFormBase { + + /** + * The array of nodes to delete. + * + * @var string[][] + */ + protected $nodeInfo = []; + + /** + * The tempstore factory. + * + * @var \Drupal\Core\TempStore\PrivateTempStoreFactory + */ + protected $tempStoreFactory; + + /** + * The node storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $manager; + + /** + * Constructs a DeleteMultiple form object. + * + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. + * @param \Drupal\Core\Entity\EntityManagerInterface $manager + * The entity manager. + */ + public function __construct(PrivateTempStoreFactory $temp_store_factory, EntityManagerInterface $manager) { + $this->tempStoreFactory = $temp_store_factory; + $this->storage = $manager->getStorage('node'); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('tempstore.private'), + $container->get('entity.manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'node_multiple_delete_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return $this->formatPlural(count($this->nodeInfo), 'Are you sure you want to delete this item?', 'Are you sure you want to delete these items?'); + } /** * {@inheritdoc} @@ -22,15 +85,117 @@ public function getCancelUrl() { /** * {@inheritdoc} */ - protected function getDeletedMessage($count) { - return $this->formatPlural($count, 'Deleted @count content item.', 'Deleted @count content items.'); + public function getConfirmText() { + return t('Delete'); } /** * {@inheritdoc} */ - protected function getInaccessibleMessage($count) { - return $this->formatPlural($count, "@count content item has not been deleted because you do not have the necessary permissions.", "@count content items have not been deleted because you do not have the necessary permissions."); + public function buildForm(array $form, FormStateInterface $form_state) { + $this->nodeInfo = $this->tempStoreFactory->get('node_multiple_delete_confirm')->get(\Drupal::currentUser()->id()); + if (empty($this->nodeInfo)) { + return new RedirectResponse($this->getCancelUrl()->setAbsolute()->toString()); + } + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo)); + + $items = []; + foreach ($this->nodeInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $node = $nodes[$id]->getTranslation($langcode); + $key = $id . ':' . $langcode; + $default_key = $id . ':' . $node->getUntranslated()->language()->getId(); + + // If we have a translated entity we build a nested list of translations + // that will be deleted. + $languages = $node->getTranslationLanguages(); + if (count($languages) > 1 && $node->isDefaultTranslation()) { + $names = []; + foreach ($languages as $translation_langcode => $language) { + $names[] = $language->getName(); + unset($items[$id . ':' . $translation_langcode]); + } + $items[$default_key] = [ + 'label' => [ + '#markup' => $this->t('@label (Original translation) - The following content translations will be deleted:', ['@label' => $node->label()]), + ], + 'deleted_translations' => [ + '#theme' => 'item_list', + '#items' => $names, + ], + ]; + } + elseif (!isset($items[$default_key])) { + $items[$key] = $node->label(); + } + } + } + + $form['nodes'] = [ + '#theme' => 'item_list', + '#items' => $items, + ]; + $form = parent::buildForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + if ($form_state->getValue('confirm') && !empty($this->nodeInfo)) { + $total_count = 0; + $delete_nodes = []; + /** @var \Drupal\Core\Entity\ContentEntityInterface[][] $delete_translations */ + $delete_translations = []; + /** @var \Drupal\node\NodeInterface[] $nodes */ + $nodes = $this->storage->loadMultiple(array_keys($this->nodeInfo)); + + foreach ($this->nodeInfo as $id => $langcodes) { + foreach ($langcodes as $langcode) { + $node = $nodes[$id]->getTranslation($langcode); + if ($node->isDefaultTranslation()) { + $delete_nodes[$id] = $node; + unset($delete_translations[$id]); + $total_count += count($node->getTranslationLanguages()); + } + elseif (!isset($delete_nodes[$id])) { + $delete_translations[$id][] = $node; + } + } + } + + if ($delete_nodes) { + $this->storage->delete($delete_nodes); + $this->logger('content')->notice('Deleted @count posts.', ['@count' => count($delete_nodes)]); + } + + if ($delete_translations) { + $count = 0; + foreach ($delete_translations as $id => $translations) { + $node = $nodes[$id]->getUntranslated(); + foreach ($translations as $translation) { + $node->removeTranslation($translation->language()->getId()); + } + $node->save(); + $count += count($translations); + } + if ($count) { + $total_count += $count; + $this->logger('content')->notice('Deleted @count content translations.', ['@count' => $count]); + } + } + + if ($total_count) { + drupal_set_message($this->formatPlural($total_count, 'Deleted 1 post.', 'Deleted @count posts.')); + } + + $this->tempStoreFactory->get('node_multiple_delete_confirm')->delete(\Drupal::currentUser()->id()); + } + + $form_state->setRedirect('system.admin_content'); } } diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php index 2fc6728..2bb7f4a 100644 --- a/core/modules/node/src/NodeViewBuilder.php +++ b/core/modules/node/src/NodeViewBuilder.php @@ -4,6 +4,7 @@ use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityViewBuilder; +use Drupal\node\Entity\Node; /** * View builder handler for nodes. @@ -33,7 +34,6 @@ public function buildComponents(array &$build, array $entities, array $displays, $view_mode, $entity->language()->getId(), !empty($entity->in_preview), - $entity->isDefaultRevision() ? NULL : $entity->getLoadedRevisionId(), ], ], ]; @@ -77,14 +77,11 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode) { * The language in which the node entity is being viewed. * @param bool $is_in_preview * Whether the node is currently being previewed. - * @param $revision_id - * (optional) The identifier of the node revision to be loaded. If none - * is provided, the default revision will be loaded. * * @return array * A renderable array representing the node links. */ - public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_in_preview, $revision_id = NULL) { + public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_in_preview) { $links = [ '#theme' => 'links__node', '#pre_render' => ['drupal_pre_render_links'], @@ -92,10 +89,7 @@ public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_i ]; if (!$is_in_preview) { - $storage = \Drupal::entityTypeManager()->getStorage('node'); - /** @var \Drupal\node\NodeInterface $revision */ - $revision = !isset($revision_id) ? $storage->load($node_entity_id) : $storage->loadRevision($revision_id); - $entity = $revision->getTranslation($langcode); + $entity = Node::load($node_entity_id)->getTranslation($langcode); $links['node'] = static::buildLinks($entity, $view_mode); // Allow other modules to alter the node links. diff --git a/core/modules/node/src/Plugin/Action/DeleteNode.php b/core/modules/node/src/Plugin/Action/DeleteNode.php index 64fbe12..19f8a7a 100644 --- a/core/modules/node/src/Plugin/Action/DeleteNode.php +++ b/core/modules/node/src/Plugin/Action/DeleteNode.php @@ -2,33 +2,98 @@ namespace Drupal\node\Plugin\Action; -use Drupal\Core\Action\Plugin\Action\DeleteAction; -use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Action\ActionBase; +use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\TempStore\PrivateTempStoreFactory; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Redirects to a node deletion form. * - * @deprecated in Drupal 8.6.x, to be removed before Drupal 9.0.0. - * Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. - * - * @see \Drupal\Core\Action\Plugin\Action\DeleteAction - * @see https://www.drupal.org/node/2934349 - * * @Action( * id = "node_delete_action", - * label = @Translation("Delete content") + * label = @Translation("Delete content"), + * type = "node", + * confirm_form_route_name = "node.multiple_delete_confirm" * ) */ -class DeleteNode extends DeleteAction { +class DeleteNode extends ActionBase implements ContainerFactoryPluginInterface { + + /** + * The tempstore object. + * + * @var \Drupal\Core\TempStore\SharedTempStore + */ + protected $tempStore; + + /** + * The current user. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a new DeleteNode object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin ID for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store_factory + * The tempstore factory. + * @param \Drupal\Core\Session\AccountInterface $current_user + * Current user. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { + $this->currentUser = $current_user; + $this->tempStore = $temp_store_factory->get('node_multiple_delete_confirm'); + + parent::__construct($configuration, $plugin_id, $plugin_definition); + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('tempstore.private'), + $container->get('current_user') + ); + } + + /** + * {@inheritdoc} + */ + public function executeMultiple(array $entities) { + $info = []; + /** @var \Drupal\node\NodeInterface $node */ + foreach ($entities as $node) { + $langcode = $node->language()->getId(); + $info[$node->id()][$langcode] = $langcode; + } + $this->tempStore->set($this->currentUser->id(), $info); + } + + /** + * {@inheritdoc} + */ + public function execute($object = NULL) { + $this->executeMultiple([$object]); + } /** * {@inheritdoc} */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, PrivateTempStoreFactory $temp_store_factory, AccountInterface $current_user) { - parent::__construct($configuration, $plugin_id, $plugin_definition, $entity_type_manager, $temp_store_factory, $current_user); - @trigger_error(__NAMESPACE__ . '\DeleteNode is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', E_USER_DEPRECATED); + public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) { + /** @var \Drupal\node\NodeInterface $object */ + return $object->access('delete', $account, $return_as_object); } } diff --git a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php index 894a460..319890b 100644 --- a/core/modules/node/tests/src/Functional/Views/BulkFormTest.php +++ b/core/modules/node/tests/src/Functional/Views/BulkFormTest.php @@ -249,15 +249,15 @@ public function testBulkDeletion() { $this->drupalPostForm(NULL, $edit, t('Apply to selected items')); $label = $this->loadNode(1)->label(); - $this->assertText("$label (Original translation) - The following content item translations will be deleted:"); + $this->assertText("$label (Original translation) - The following content translations will be deleted:"); $label = $this->loadNode(2)->label(); - $this->assertText("$label (Original translation) - The following content item translations will be deleted:"); + $this->assertText("$label (Original translation) - The following content translations will be deleted:"); $label = $this->loadNode(3)->getTranslation('en')->label(); $this->assertText($label); - $this->assertNoText("$label (Original translation) - The following content item translations will be deleted:"); + $this->assertNoText("$label (Original translation) - The following content translations will be deleted:"); $label = $this->loadNode(4)->label(); $this->assertText($label); - $this->assertNoText("$label (Original translation) - The following content item translations will be deleted:"); + $this->assertNoText("$label (Original translation) - The following content translations will be deleted:"); $this->drupalPostForm(NULL, [], t('Delete')); @@ -273,7 +273,7 @@ public function testBulkDeletion() { $node = $this->loadNode(5); $this->assertTrue($node, '5: Node has not been deleted'); - $this->assertText('Deleted 8 content items.'); + $this->assertText('Deleted 8 posts.'); } /** diff --git a/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php b/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php deleted file mode 100644 index d92bd8f..0000000 --- a/core/modules/node/tests/src/Kernel/NodeViewBuilderTest.php +++ /dev/null @@ -1,101 +0,0 @@ -storage = $this->entityManager->getStorage('node'); - $this->viewBuilder = $this->entityManager->getViewBuilder('node'); - $this->renderer = $this->container->get('renderer'); - - $type = NodeType::create([ - 'type' => 'article', - 'name' => 'Article', - ]); - $type->save(); - - $this->installSchema('node', 'node_access'); - $this->installConfig(['system', 'node']); - } - - /** - * Tests that node links are displayed correctly in pending revisions. - * - * @covers ::buildComponents - * @covers ::renderLinks - * @covers ::buildLinks - */ - public function testPendingRevisionLinks() { - $account = User::create([ - 'name' => $this->randomString(), - ]); - $account->save(); - - $title = $this->randomMachineName(); - $node = Node::create([ - 'type' => 'article', - 'title' => $title, - 'uid' => $account->id(), - ]); - $node->save(); - - /** @var \Drupal\node\NodeInterface $pending_revision */ - $pending_revision = $this->storage->createRevision($node, FALSE); - $draft_title = $title . ' draft'; - $pending_revision->setTitle($draft_title); - $pending_revision->save(); - - $build = $this->viewBuilder->view($node, 'teaser'); - $output = (string) $this->renderer->renderPlain($build); - $this->assertContains("title=\"$title\"", $output); - - $build = $this->viewBuilder->view($pending_revision, 'teaser'); - $output = (string) $this->renderer->renderPlain($build); - $this->assertContains("title=\"$draft_title\"", $output); - } - -} diff --git a/core/modules/rdf/tests/src/Kernel/Field/DateTimeFieldRdfaTest.php b/core/modules/rdf/tests/src/Kernel/Field/DateTimeFieldRdfaTest.php index fc78691..eb5f934 100644 --- a/core/modules/rdf/tests/src/Kernel/Field/DateTimeFieldRdfaTest.php +++ b/core/modules/rdf/tests/src/Kernel/Field/DateTimeFieldRdfaTest.php @@ -21,7 +21,7 @@ class DateTimeFieldRdfaTest extends FieldRdfaTestBase { * * @var string */ - protected $testValue = '2014-01-28T06:01:01'; + protected $testValue = '2014-01-28T06:01:01+00:00'; /** * {@inheritdoc} @@ -48,7 +48,7 @@ protected function setUp() { * Tests the default formatter. */ public function testDefaultFormatter() { - $this->assertFormatterRdfa(['type' => 'datetime_default'], 'http://schema.org/dateCreated', ['value' => $this->testValue . 'Z', 'type' => 'literal', 'datatype' => 'http://www.w3.org/2001/XMLSchema#dateTime']); + $this->assertFormatterRdfa(['type' => 'datetime_default'], 'http://schema.org/dateCreated', ['value' => $this->testValue, 'type' => 'literal', 'datatype' => 'http://www.w3.org/2001/XMLSchema#dateTime']); } } diff --git a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php index c677d08..88a2f81 100644 --- a/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php +++ b/core/modules/settings_tray/tests/src/FunctionalJavascript/SettingsTrayBlockFormTest.php @@ -11,7 +11,6 @@ use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait; use Drupal\Tests\system\FunctionalJavascript\OffCanvasTestBase; use Drupal\user\Entity\Role; -use Drupal\user\RoleInterface; /** * Testing opening and saving block forms in the off-canvas dialog. @@ -37,6 +36,7 @@ class SettingsTrayBlockFormTest extends OffCanvasTestBase { 'toolbar', 'contextual', 'settings_tray', + 'quickedit', 'search', 'block_content', 'settings_tray_test', @@ -62,6 +62,7 @@ protected function setUp() { 'access contextual links', 'access toolbar', 'administer nodes', + 'access in-place editing', 'search content', ]); $this->drupalLogin($user); @@ -293,8 +294,6 @@ protected function openBlockForm($block_selector, $contextual_link_container = ' * Tests QuickEdit links behavior. */ public function testQuickEditLinks() { - $this->container->get('module_installer')->install(['quickedit']); - $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']); $quick_edit_selector = '#quickedit-entity-toolbar'; $node_selector = '[data-quickedit-entity-id="node/1"]'; $body_selector = '[data-quickedit-field-id="node/1/body/en/full"]'; @@ -512,8 +511,6 @@ protected function createBlockContentType($label, $create_body = FALSE) { * "Quick edit settings" is settings_tray.module link. */ public function testCustomBlockLinks() { - $this->container->get('module_installer')->install(['quickedit']); - $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']); $this->drupalGet('user'); $page = $this->getSession()->getPage(); $links = $page->findAll('css', "#block-custom .contextual-links li a"); diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module index 6670e5a..0e0ea2a 100644 --- a/core/modules/simpletest/simpletest.module +++ b/core/modules/simpletest/simpletest.module @@ -661,6 +661,10 @@ function simpletest_clean_environment() { else { drupal_set_message(t('Clear results is disabled and the test results table will not be cleared.'), 'warning'); } + + // Detect test classes that have been added, renamed or deleted. + \Drupal::cache()->delete('simpletest'); + \Drupal::cache()->delete('simpletest_phpunit'); } /** diff --git a/core/modules/simpletest/simpletest.services.yml b/core/modules/simpletest/simpletest.services.yml index 326cf38..8b645de 100644 --- a/core/modules/simpletest/simpletest.services.yml +++ b/core/modules/simpletest/simpletest.services.yml @@ -1,9 +1,4 @@ services: test_discovery: class: Drupal\simpletest\TestDiscovery - arguments: ['@app.root', '@class_loader', '@module_handler'] - cache_context.test_discovery: - class: Drupal\simpletest\Cache\Context\TestDiscoveryCacheContext - arguments: ['@test_discovery', '@private_key'] - tags: - - { name: cache.context} + arguments: ['@app.root', '@class_loader', '@module_handler', '@?cache.discovery'] diff --git a/core/modules/simpletest/src/Cache/Context/TestDiscoveryCacheContext.php b/core/modules/simpletest/src/Cache/Context/TestDiscoveryCacheContext.php deleted file mode 100644 index e3d5cf3..0000000 --- a/core/modules/simpletest/src/Cache/Context/TestDiscoveryCacheContext.php +++ /dev/null @@ -1,94 +0,0 @@ -testDiscovery = $test_discovery; - $this->privateKey = $private_key; - } - - /** - * {@inheritdoc} - */ - public static function getLabel() { - return t('Test discovery'); - } - - /** - * {@inheritdoc} - */ - public function getContext() { - if (empty($this->hash)) { - $tests = $this->testDiscovery->getTestClasses(); - $this->hash = $this->hash(serialize($tests)); - } - return $this->hash; - } - - /** - * {@inheritdoc} - */ - public function getCacheableMetadata() { - return new CacheableMetadata(); - } - - /** - * Hashes the given string. - * - * @param string $identifier - * The string to be hashed. - * - * @return string - * The hash. - */ - protected function hash($identifier) { - return hash('sha256', $this->privateKey->get() . Settings::getHashSalt() . $identifier); - } - -} diff --git a/core/modules/simpletest/src/Form/SimpletestTestForm.php b/core/modules/simpletest/src/Form/SimpletestTestForm.php index 69f31e4..0b704f9 100644 --- a/core/modules/simpletest/src/Form/SimpletestTestForm.php +++ b/core/modules/simpletest/src/Form/SimpletestTestForm.php @@ -110,10 +110,6 @@ public function buildForm(array $form, FormStateInterface $form_state) { ]; $form['tests'] = [ - '#cache' => [ - 'keys' => ['simpletest_ui_table'], - 'contexts' => ['test_discovery'], - ], '#type' => 'table', '#id' => 'simpletest-form-table', '#tableselect' => TRUE, diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php index da3d3af..9e6c32c 100644 --- a/core/modules/simpletest/src/TestDiscovery.php +++ b/core/modules/simpletest/src/TestDiscovery.php @@ -6,6 +6,7 @@ use Doctrine\Common\Reflection\StaticReflectionParser; use Drupal\Component\Annotation\Reflection\MockFileFinder; use Drupal\Component\Utility\NestedArray; +use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ExtensionDiscovery; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\simpletest\Exception\MissingGroupException; @@ -24,11 +25,11 @@ class TestDiscovery { protected $classLoader; /** - * Statically cached list of test classes. + * Backend for caching discovery results. * - * @var array + * @var \Drupal\Core\Cache\CacheBackendInterface */ - protected $testClasses; + protected $cacheBackend; /** * Cached map of all test namespaces to respective directories. @@ -69,11 +70,14 @@ class TestDiscovery { * \Symfony\Component\ClassLoader\ApcClassLoader. * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler * The module handler. + * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend + * (optional) Backend for caching discovery results. */ - public function __construct($root, $class_loader, ModuleHandlerInterface $module_handler) { + public function __construct($root, $class_loader, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend = NULL) { $this->root = $root; $this->classLoader = $class_loader; $this->moduleHandler = $module_handler; + $this->cacheBackend = $cache_backend; } /** @@ -155,9 +159,9 @@ public function getTestClasses($extension = NULL, array $types = []) { $reader = new SimpleAnnotationReader(); $reader->addNamespace('Drupal\\simpletest\\Annotation'); - if (!isset($extension) && empty($types)) { - if (!empty($this->testClasses)) { - return $this->testClasses; + if (!isset($extension)) { + if ($this->cacheBackend && $cache = $this->cacheBackend->get('simpletest:discovery:classes')) { + return $cache->data; } } $list = []; @@ -211,8 +215,10 @@ public function getTestClasses($extension = NULL, array $types = []) { // Allow modules extending core tests to disable originals. $this->moduleHandler->alter('simpletest', $list); - if (!isset($extension) && empty($types)) { - $this->testClasses = $list; + if (!isset($extension)) { + if ($this->cacheBackend) { + $this->cacheBackend->set('simpletest:discovery:classes', $list); + } } if ($types) { diff --git a/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php b/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php deleted file mode 100644 index 25b2af9..0000000 --- a/core/modules/simpletest/tests/src/Kernel/Cache/Context/TestDiscoveryCacheContextTest.php +++ /dev/null @@ -1,53 +0,0 @@ -getMockBuilder(TestDiscovery::class) - ->setMethods(['getTestClasses']) - ->disableOriginalConstructor() - ->getMock(); - // Set getTestClasses() to return different results on subsequent calls. - // This emulates changed tests in the filesystem. - $discovery->expects($this->any()) - ->method('getTestClasses') - ->willReturnOnConsecutiveCalls( - ['group1' => ['Test']], - ['group2' => ['Test2']] - ); - - // Make our cache context object. - $cache_context = new TestDiscoveryCacheContext($discovery, $this->container->get('private_key')); - - // Generate a context hash. - $context_hash = $cache_context->getContext(); - - // Since the context stores the hash, we have to reset it. - $hash_ref = new \ReflectionProperty($cache_context, 'hash'); - $hash_ref->setAccessible(TRUE); - $hash_ref->setValue($cache_context, NULL); - - // And then assert that we did not generate the same hash for different - // content. - $this->assertNotSame($context_hash, $cache_context->getContext()); - } - -} diff --git a/core/modules/system/system.post_update.php b/core/modules/system/system.post_update.php index 66fa24a..da13077 100644 --- a/core/modules/system/system.post_update.php +++ b/core/modules/system/system.post_update.php @@ -111,22 +111,3 @@ function system_post_update_change_action_plugins() { } } } - -/** - * Change plugin IDs of delete actions. - */ -function system_post_update_change_delete_action_plugins() { - $old_new_action_id_map = [ - 'comment_delete_action' => 'entity:delete_action:comment', - 'node_delete_action' => 'entity:delete_action:node', - ]; - - /** @var \Drupal\system\Entity\Action[] $actions */ - $actions = \Drupal::entityTypeManager()->getStorage('action')->loadMultiple(); - foreach ($actions as $action) { - if (isset($old_new_action_id_map[$action->getPlugin()->getPluginId()])) { - $action->setPlugin($old_new_action_id_map[$action->getPlugin()->getPluginId()]); - $action->save(); - } - } -} diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php index 8fa69a3..862958e 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevPub.php @@ -17,8 +17,7 @@ * "access" = "Drupal\entity_test\EntityTestAccessControlHandler", * "form" = { * "default" = "Drupal\entity_test\EntityTestForm", - * "delete" = "Drupal\entity_test\EntityTestDeleteForm", - * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" + * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, * "translation" = "Drupal\content_translation\ContentTranslationHandler", * "views_data" = "Drupal\views\EntityViewsData", @@ -46,7 +45,6 @@ * "add-form" = "/entity_test_mulrevpub/add", * "canonical" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}", * "delete-form" = "/entity_test/delete/entity_test_mulrevpub/{entity_test_mulrevpub}", - * "delete-multiple-form" = "/entity_test/delete", * "edit-form" = "/entity_test_mulrevpub/manage/{entity_test_mulrevpub}/edit", * "revision" = "/entity_test_mulrevpub/{entity_test_mulrevpub}/revision/{entity_test_mulrevpub_revision}/view", * } diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php index 6fd4904..5aa3ae1 100644 --- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php +++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestRev.php @@ -16,8 +16,7 @@ * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", * "form" = { * "default" = "Drupal\entity_test\EntityTestForm", - * "delete" = "Drupal\entity_test\EntityTestDeleteForm", - * "delete-multiple-confirm" = "Drupal\Core\Entity\Form\DeleteMultipleForm" + * "delete" = "Drupal\entity_test\EntityTestDeleteForm" * }, * "view_builder" = "Drupal\entity_test\EntityTestViewBuilder", * "translation" = "Drupal\content_translation\ContentTranslationHandler", @@ -42,7 +41,6 @@ * "add-form" = "/entity_test_rev/add", * "canonical" = "/entity_test_rev/manage/{entity_test_rev}", * "delete-form" = "/entity_test/delete/entity_test_rev/{entity_test_rev}", - * "delete-multiple-form" = "/entity_test_rev/delete_multiple", * "edit-form" = "/entity_test_rev/manage/{entity_test_rev}/edit", * "revision" = "/entity_test_rev/{entity_test_rev}/revision/{entity_test_rev_revision}/view", * } diff --git a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php index e3bdcf6..4094d00 100644 --- a/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php +++ b/core/modules/system/tests/src/Functional/Update/UpdateActionsWithEntityPluginsTest.php @@ -53,33 +53,4 @@ public function testUpdateActionsWithEntityPlugins() { } } - /** - * Tests upgrading comment and node delete actions to generic entity ones. - * - * @see system_post_update_change_delete_action_plugins() - */ - public function testUpdateDeleteActionsWithEntityPlugins() { - // comment_delete_actions is not part of the dump files. - $array = [ - 'node_delete_action' => ['node_delete_action', 'entity:delete_action:node'], - ]; - - foreach ($array as $key => list($before, $after)) { - /** @var \Drupal\system\Entity\Action $action */ - $action = Action::load($key); - $this->assertSame($before, $action->getPlugin()->getPluginId()); - } - - $this->runUpdates(); - - foreach ($array as $key => list($before, $after)) { - /** @var \Drupal\system\Entity\Action $action */ - $action = Action::load($key); - $this->assertSame($after, $action->getPlugin()->getPluginId()); - - // Check that the type the action is based on will be a module dependency. - $this->assertArraySubset(['module' => [$action->getPluginDefinition()['type']]], $action->getDependencies()); - } - } - } diff --git a/core/profiles/demo_umami/config/install/field.field.block_content.banner_block.field_banner_image.yml b/core/profiles/demo_umami/config/install/field.field.block_content.banner_block.field_banner_image.yml index ba22548..4778eb7 100644 --- a/core/profiles/demo_umami/config/install/field.field.block_content.banner_block.field_banner_image.yml +++ b/core/profiles/demo_umami/config/install/field.field.block_content.banner_block.field_banner_image.yml @@ -22,8 +22,8 @@ settings: max_filesize: '' max_resolution: '' min_resolution: '' - alt_field: true - alt_field_required: false + alt_field: false + alt_field_required: true title_field: false title_field_required: false default_image: diff --git a/core/profiles/demo_umami/modules/demo_umami_content/demo_umami_content.info.yml b/core/profiles/demo_umami/modules/demo_umami_content/demo_umami_content.info.yml index e9ad043..32f0038 100644 --- a/core/profiles/demo_umami/modules/demo_umami_content/demo_umami_content.info.yml +++ b/core/profiles/demo_umami/modules/demo_umami_content/demo_umami_content.info.yml @@ -1,7 +1,6 @@ name: 'Umami demo: Content' description: Imports the content for the Umami demo. type: module -version: VERSION core: 8.x package: 'Core (Experimental)' dependencies: diff --git a/core/profiles/demo_umami/modules/demo_umami_content/src/InstallHelper.php b/core/profiles/demo_umami/modules/demo_umami_content/src/InstallHelper.php index 73b0484..d3c55b4 100644 --- a/core/profiles/demo_umami/modules/demo_umami_content/src/InstallHelper.php +++ b/core/profiles/demo_umami/modules/demo_umami_content/src/InstallHelper.php @@ -310,7 +310,6 @@ protected function importBlockContent() { ], 'field_banner_image' => [ 'target_id' => $this->createFileEntity($module_path . '/default_content/images/veggie-pasta-bake-hero-umami.jpg'), - 'alt' => 'Mouth watering vegetarian pasta bake with rich tomato sauce and cheese toppings', ], ], ]; diff --git a/core/profiles/demo_umami/modules/demo_umami_content/tests/src/Functional/UninstallDefaultContentTest.php b/core/profiles/demo_umami/modules/demo_umami_content/tests/src/Functional/UninstallDefaultContentTest.php index 2c9129c..76a5d7c 100644 --- a/core/profiles/demo_umami/modules/demo_umami_content/tests/src/Functional/UninstallDefaultContentTest.php +++ b/core/profiles/demo_umami/modules/demo_umami_content/tests/src/Functional/UninstallDefaultContentTest.php @@ -99,8 +99,6 @@ protected function assertImportedCustomBlock(EntityStorageInterface $block_stora $assert = $this->assertSession(); $this->drupalGet('/recipes'); $assert->pageTextContains('Super easy vegetarian pasta bake'); - $img_alt_text = $assert->elementExists('css', '#block-umami-banner-recipes img')->getAttribute('alt'); - $this->assertEquals('Mouth watering vegetarian pasta bake with rich tomato sauce and cheese toppings', $img_alt_text); $count = $block_storage->getQuery() ->condition('type', 'banner_block') diff --git a/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php b/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php index 3e8d697..34aeb63 100644 --- a/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php +++ b/core/profiles/demo_umami/tests/src/Functional/DemoUmamiProfileTest.php @@ -111,7 +111,7 @@ public function testEditNodesByAdmin() { } /** - * Tests that the Umami theme is available on the Appearance page. + * Tests that the Umami theme is the default theme on the Appearance page. */ public function testAppearance() { $account = $this->drupalCreateUser(['administer themes']); diff --git a/core/profiles/demo_umami/themes/umami/umami.info.yml b/core/profiles/demo_umami/themes/umami/umami.info.yml index 2fe2d0f..b65a67a 100644 --- a/core/profiles/demo_umami/themes/umami/umami.info.yml +++ b/core/profiles/demo_umami/themes/umami/umami.info.yml @@ -2,7 +2,6 @@ name: Umami type: theme base theme: classy description: 'The theme used for the out of the box initiative.' -version: VERSION core: 8.x libraries: - umami/global diff --git a/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php b/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php deleted file mode 100644 index 45f88d6..0000000 --- a/core/tests/Drupal/FunctionalTests/Entity/DeleteMultipleFormTest.php +++ /dev/null @@ -1,157 +0,0 @@ - 'default', - 'label' => 'Default', - ])->save(); - $this->account = $this->drupalCreateUser(['administer entity_test content']); - $this->drupalLogin($this->account); - } - - /** - * Tests the delete form for translatable entities. - */ - public function testTranslatableEntities() { - ConfigurableLanguage::create(['id' => 'es'])->save(); - ConfigurableLanguage::create(['id' => 'fr'])->save(); - - $selection = []; - - $entity1 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity1']); - $entity1->addTranslation('es', ['name' => 'entity1 spanish']); - $entity1->addTranslation('fr', ['name' => 'entity1 french']); - $entity1->save(); - $selection[$entity1->id()]['en'] = 'en'; - - $entity2 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity2']); - $entity2->addTranslation('es', ['name' => 'entity2 spanish']); - $entity2->addTranslation('fr', ['name' => 'entity2 french']); - $entity2->save(); - $selection[$entity2->id()]['es'] = 'es'; - $selection[$entity2->id()]['fr'] = 'fr'; - - $entity3 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'entity3']); - $entity3->addTranslation('es', ['name' => 'entity3 spanish']); - $entity3->addTranslation('fr', ['name' => 'entity3 french']); - $entity3->save(); - $selection[$entity3->id()]['fr'] = 'fr'; - - // This entity will be inaccessible because of - // Drupal\entity_test\EntityTestAccessControlHandler. - $entity4 = EntityTestMulRevPub::create(['type' => 'default', 'name' => 'forbid_access']); - $entity4->save(); - $selection[$entity4->id()]['en'] = 'en'; - - // Add the selection to the tempstore just like DeleteAction would. - $tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm'); - $tempstore->set($this->account->id() . ':entity_test_mulrevpub', $selection); - - $this->drupalGet('/entity_test/delete'); - $assert = $this->assertSession(); - $assert->statusCodeEquals(200); - $assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these test entity - revisions, data table, and published interface entities?'); - $list_selector = '#entity-test-mulrevpub-delete-multiple-confirm-form > div.item-list > ul'; - $assert->elementTextContains('css', $list_selector, 'entity1 (Original translation) - The following test entity - revisions, data table, and published interface translations will be deleted:'); - $assert->elementTextContains('css', $list_selector, 'entity2 spanish'); - $assert->elementTextContains('css', $list_selector, 'entity2 french'); - $assert->elementTextNotContains('css', $list_selector, 'entity3 spanish'); - $assert->elementTextContains('css', $list_selector, 'entity3 french'); - $delete_button = $this->getSession()->getPage()->findButton('Delete'); - $delete_button->click(); - $assert = $this->assertSession(); - $assert->addressEquals('/user/' . $this->account->id()); - $assert->responseContains('Deleted 6 items.'); - $assert->responseContains('1 item has not been deleted because you do not have the necessary permissions.'); - - \Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache(); - $remaining_entities = EntityTestMulRevPub::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]); - $this->assertCount(3, $remaining_entities); - } - - /** - * Tests the delete form for untranslatable entities. - */ - public function testUntranslatableEntities() { - $selection = []; - - $entity1 = EntityTestRev::create(['type' => 'default', 'name' => 'entity1']); - $entity1->save(); - $selection[$entity1->id()]['en'] = 'en'; - - $entity2 = EntityTestRev::create(['type' => 'default', 'name' => 'entity2']); - $entity2->save(); - $selection[$entity2->id()]['en'] = 'en'; - - // This entity will be inaccessible because of - // Drupal\entity_test\EntityTestAccessControlHandler. - $entity3 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']); - $entity3->save(); - $selection[$entity3->id()]['en'] = 'en'; - - // This entity will be inaccessible because of - // Drupal\entity_test\EntityTestAccessControlHandler. - $entity4 = EntityTestRev::create(['type' => 'default', 'name' => 'forbid_access']); - $entity4->save(); - $selection[$entity4->id()]['en'] = 'en'; - - // Add the selection to the tempstore just like DeleteAction would. - $tempstore = \Drupal::service('tempstore.private')->get('entity_delete_multiple_confirm'); - $tempstore->set($this->account->id() . ':entity_test_rev', $selection); - - $this->drupalGet('/entity_test_rev/delete_multiple'); - $assert = $this->assertSession(); - $assert->statusCodeEquals(200); - $assert->elementTextContains('css', '.page-title', 'Are you sure you want to delete these test entity - revisions entities?'); - $list_selector = '#entity-test-rev-delete-multiple-confirm-form > div.item-list > ul'; - $assert->elementTextContains('css', $list_selector, 'entity1'); - $assert->elementTextContains('css', $list_selector, 'entity2'); - $delete_button = $this->getSession()->getPage()->findButton('Delete'); - $delete_button->click(); - $assert = $this->assertSession(); - $assert->addressEquals('/user/' . $this->account->id()); - $assert->responseContains('Deleted 2 items.'); - $assert->responseContains('2 items have not been deleted because you do not have the necessary permissions.'); - - \Drupal::entityTypeManager()->getStorage('entity_test_mulrevpub')->resetCache(); - $remaining_entities = EntityTestRev::loadMultiple([$entity1->id(), $entity2->id(), $entity3->id(), $entity4->id()]); - $this->assertCount(2, $remaining_entities); - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php deleted file mode 100644 index e5a2bd9..0000000 --- a/core/tests/Drupal/KernelTests/Core/Action/DeleteActionTest.php +++ /dev/null @@ -1,85 +0,0 @@ -installEntitySchema('entity_test_mulrevpub'); - $this->installEntitySchema('user'); - $this->installSchema('system', ['sequences', 'key_value_expire']); - - $this->testUser = User::create([ - 'name' => 'foobar', - 'mail' => 'foobar@example.com', - ]); - $this->testUser->save(); - \Drupal::service('current_user')->setAccount($this->testUser); - } - - /** - * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityDeleteActionDeriver::getDerivativeDefinitions - */ - public function testGetDerivativeDefinitions() { - $deriver = new EntityDeleteActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); - $this->assertEquals([ - 'entity_test_mulrevpub' => [ - 'type' => 'entity_test_mulrevpub', - 'label' => 'Delete test entity - revisions, data table, and published interface', - 'action_label' => 'Delete', - 'confirm_form_route_name' => 'entity.entity_test_mulrevpub.delete_multiple_form', - ], - 'entity_test_rev' => [ - 'type' => 'entity_test_rev', - 'label' => 'Delete test entity - revisions', - 'action_label' => 'Delete', - 'confirm_form_route_name' => 'entity.entity_test_rev.delete_multiple_form', - ], - ], $deriver->getDerivativeDefinitions([ - 'action_label' => 'Delete', - ])); - } - - /** - * @covers \Drupal\Core\Action\Plugin\Action\DeleteAction::execute - */ - public function testDeleteAction() { - $entity = EntityTestMulRevPub::create(['name' => 'test']); - $entity->save(); - - $action = Action::create([ - 'id' => 'entity_delete_action', - 'plugin' => 'entity:delete_action:entity_test_mulrevpub', - ]); - $action->save(); - - $action->execute([$entity]); - $this->assertArraySubset(['module' => ['entity_test']], $action->getDependencies()); - - /** @var \Drupal\Core\TempStore\PrivateTempStoreFactory $temp_store */ - $temp_store = \Drupal::service('tempstore.private'); - $store_entries = $temp_store->get('entity_delete_multiple_confirm')->get($this->testUser->id() . ':entity_test_mulrevpub'); - $this->assertArraySubset([$this->testUser->id() => ['en' => 'en']], $store_entries); - } - -} diff --git a/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php index d381de1..04bf1c0 100644 --- a/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Action/PublishActionTest.php @@ -29,7 +29,7 @@ protected function setUp() { * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityPublishedActionDeriver::getDerivativeDefinitions */ public function testGetDerivativeDefinitions() { - $deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); + $deriver = new EntityPublishedActionDeriver(\Drupal::entityTypeManager()); $this->assertArraySubset([ 'entity_test_mulrevpub' => [ 'type' => 'entity_test_mulrevpub', diff --git a/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php b/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php index ed9b287..118f542 100644 --- a/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php +++ b/core/tests/Drupal/KernelTests/Core/Action/SaveActionTest.php @@ -29,7 +29,7 @@ protected function setUp() { * @covers \Drupal\Core\Action\Plugin\Action\Derivative\EntityChangedActionDeriver::getDerivativeDefinitions */ public function testGetDerivativeDefinitions() { - $deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager(), \Drupal::translation()); + $deriver = new EntityChangedActionDeriver(\Drupal::entityTypeManager()); $this->assertArraySubset([ 'entity_test_mul_changed' => [ 'type' => 'entity_test_mul_changed', diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php index d5cc533..ab01d91 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php @@ -359,20 +359,12 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab $previous_label = NULL; if (!$entity->isNewTranslation()) { $previous_label = $this->generateNewEntityLabel($entity, $previous_revision_id); - $latest_affected_revision_id = $this->storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId()); - } - else { - // Normally it would make sense to load the default revision in this - // case, however that would mean simulating here the logic that we need - // to test, thus "masking" possible flaws. To avoid that, we simply - // pretend we are starting from an earlier non translated revision. - // This ensures that the we can check that the merging logic is applied - // also when adding a new translation. - $latest_affected_revision_id = 1; } $previous_revision_id = (int) $entity->getLoadedRevisionId(); + $latest_affected_revision_id = $this->storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId()); /** @var \Drupal\Core\Entity\ContentEntityInterface $latest_affected_revision */ - $latest_affected_revision = $this->storage->loadRevision($latest_affected_revision_id); + $latest_affected_revision = isset($latest_affected_revision_id) ? + $this->storage->loadRevision($latest_affected_revision_id) : $this->storage->load($entity->id()); $translation = $latest_affected_revision->hasTranslation($active_langcode) ? $latest_affected_revision->getTranslation($active_langcode) : $latest_affected_revision->addTranslation($active_langcode); $entity = $this->storage->createRevision($translation, $default_revision); diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php index addbad2..d90756d 100644 --- a/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php +++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityRevisionTranslationTest.php @@ -192,12 +192,12 @@ public function testSetNewRevision() { /** * Tests that revision translations are correctly detected. * - * @covers \Drupal\Core\Entity\ContentEntityStorageBase::isAnyStoredRevisionTranslated + * @covers \Drupal\Core\Entity\ContentEntityStorageBase::isAnyRevisionTranslated */ - public function testIsAnyStoredRevisionTranslated() { + public function testIsAnyRevisionTranslated() { /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ $storage = $this->entityManager->getStorage('entity_test_mul'); - $method = new \ReflectionMethod(get_class($storage), 'isAnyStoredRevisionTranslated'); + $method = new \ReflectionMethod(get_class($storage), 'isAnyRevisionTranslated'); $method->setAccessible(TRUE); // Check that a non-revisionable new entity is handled correctly. diff --git a/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php b/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php index 40d3886..32379c8 100644 --- a/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php +++ b/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php @@ -2,7 +2,6 @@ namespace Drupal\KernelTests\Core\ParamConverter; -use Drupal\entity_test\Entity\EntityTest; use Drupal\entity_test\Entity\EntityTestMulRev; use Drupal\KernelTests\KernelTestBase; use Drupal\language\Entity\ConfigurableLanguage; @@ -42,7 +41,6 @@ protected function setUp() { $this->installEntitySchema('user'); $this->installEntitySchema('entity_test_mulrev'); - $this->installEntitySchema('entity_test'); $this->installConfig(['system', 'language']); $this->converter = $this->container->get('paramconverter.entity'); @@ -170,19 +168,4 @@ public function testOptimizedConvert() { $this->assertEquals($entity->getLoadedRevisionId(), $converted->getLoadedRevisionId()); } - /** - * Test the latest revision flag and non-revisionable entities. - */ - public function testConvertNonRevisionableEntityType() { - $entity = EntityTest::create(); - $entity->save(); - - $converted = $this->converter->convert(1, [ - 'load_latest_revision' => TRUE, - 'type' => 'entity:entity_test', - ], 'foo', []); - - $this->assertEquals($entity->id(), $converted->id()); - } - } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php index fdcb9e6..404c1f5 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityResolverManagerTest.php @@ -520,22 +520,6 @@ public function setLatestRevisionFlagTestCases() { ], ], ], - 'Entity form with no operation' => [ - [ - '_entity_form' => 'entity_test_rev' - ], - [ - 'entity_test_rev' => [ - 'type' => 'entity:entity_test_rev', - ], - ], - [ - 'entity_test_rev' => [ - 'type' => 'entity:entity_test_rev', - 'load_latest_revision' => TRUE, - ], - ], - ], 'Multiple entity parameters on an entity form' => [ [ '_entity_form' => 'entity_test_rev.edit' diff --git a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php index e71d7df..f5aa40a 100644 --- a/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php +++ b/core/tests/Drupal/Tests/Listeners/DeprecationListenerTrait.php @@ -325,8 +325,6 @@ public static function getSkippedDeprecations() { 'The "session_handler.write_check" service relies on the deprecated "Symfony\Component\HttpFoundation\Session\Storage\Handler\WriteCheckSessionHandler" class. It should either be deprecated or its implementation upgraded.', 'Not setting the strict option of the Choice constraint to true is deprecated since Symfony 3.4 and will throw an exception in 4.0.', 'Using the Yaml::PARSE_KEYS_AS_STRINGS flag is deprecated since Symfony 3.4 as it will be removed in 4.0. Quote your keys when they are evaluable instead.', - 'Drupal\node\Plugin\Action\DeleteNode is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', - 'Drupal\comment\Plugin\Action\DeleteComment is deprecated in Drupal 8.6.x, will be removed before Drupal 9.0.0. Use \Drupal\Core\Action\Plugin\Action\DeleteAction instead. See https://www.drupal.org/node/2934349.', ]; } diff --git a/core/themes/stable/css/content_moderation/content_moderation.module.css b/core/themes/stable/css/content_moderation/content_moderation.module.css deleted file mode 100644 index 1df1823..0000000 --- a/core/themes/stable/css/content_moderation/content_moderation.module.css +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file - * Component styles for the content_moderation module. - */ -.entity-moderation-form { - list-style: none; - display: -webkit-flex; /* Safari */ - display: flex; - -webkit-flex-wrap: wrap; /* Safari */ - flex-wrap: wrap; - -webkit-align-items: flex-start; /* Safari */ - align-items: flex-start; -} - -.entity-moderation-form__item { - margin-right: 2em; - display: table; -} - -.entity-moderation-form__item:last-child { - -webkit-align-self: flex-end; /* Safari */ - align-self: flex-end; - margin-right: 0; -} - -.entity-moderation-form .form-item { - margin-top: 1em; - margin-bottom: 1em; -} - -.entity-moderation-form .form-item label { - padding-bottom: 0.25em; - display: table; -} - -.entity-moderation-form input[type=submit] { - margin-bottom: 1.2em; -} diff --git a/core/themes/stable/css/content_moderation/content_moderation.theme.css b/core/themes/stable/css/content_moderation/content_moderation.theme.css deleted file mode 100644 index 827e911..0000000 --- a/core/themes/stable/css/content_moderation/content_moderation.theme.css +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @file - * Theme styles for the content_moderation module. - */ -.entity-moderation-form { - border: 1px dashed #bbb; - margin: 2em 0; - background: #fff; - padding-left: 1em; -} diff --git a/core/themes/stable/stable.info.yml b/core/themes/stable/stable.info.yml index 3f019d0..957a65c 100644 --- a/core/themes/stable/stable.info.yml +++ b/core/themes/stable/stable.info.yml @@ -45,13 +45,6 @@ libraries-override: theme: css/content_translation.admin.css: css/content_translation/content_translation.admin.css - content_moderation/content_moderation: - css: - component: - css/content_moderation.module.css: css/content_moderation/content_moderation.module.css - theme: - css/content_moderation.theme.css: css/content_moderation/content_moderation.theme.css - contextual/drupal.contextual-links: css: component: diff --git a/core/themes/stable/templates/content-edit/entity-moderation-form.html.twig b/core/themes/stable/templates/content-edit/entity-moderation-form.html.twig deleted file mode 100644 index 7638e37..0000000 --- a/core/themes/stable/templates/content-edit/entity-moderation-form.html.twig +++ /dev/null @@ -1,8 +0,0 @@ -{{ attach_library('content_moderation/content_moderation') }} - -{{ form|without('current', 'new_state', 'revision_log', 'submit') }}