diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php index 590b80f0c5bf12955089f5866a80e4fad9a05bf0..48c3801eb4767e16fb6890297b3948ebf248dcc6 100644 --- a/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php +++ b/core/lib/Drupal/Core/Entity/ContentEntityDeleteForm.php @@ -7,11 +7,141 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Form\FormStateInterface; + /** * Provides a generic base class for a content entity deletion form. + * + * @todo Re-evaluate and streamline the entity deletion form class hierarchy in + * https://www.drupal.org/node/2491057. */ class ContentEntityDeleteForm extends ContentEntityConfirmFormBase { - use EntityDeleteFormTrait; + use EntityDeleteFormTrait { + getQuestion as traitGetQuestion; + logDeletionMessage as traitLogDeletionMessage; + getDeletionMessage as traitGetDeletionMessage; + getCancelUrl as traitGetCancelUrl; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state) { + $form = parent::buildForm($form, $form_state); + + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + if ($entity->isDefaultTranslation()) { + if (count($entity->getTranslationLanguages()) > 1) { + $languages = []; + foreach ($entity->getTranslationLanguages() as $language) { + $languages[] = $language->getName(); + } + + $form['deleted_translations'] = array( + '#theme' => 'item_list', + '#title' => $this->t('The following @entity-type translations will be deleted:', [ + '@entity-type' => $entity->getEntityType()->getLowercaseLabel() + ]), + '#items' => $languages, + ); + + $form['actions']['submit']['#value'] = $this->t('Delete all translations'); + } + } + else { + $form['actions']['submit']['#value'] = $this->t('Delete @language translation', array('@language' => $entity->language()->getName())); + } + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + + // Make sure that deleting a translation does not delete the whole entity. + if (!$entity->isDefaultTranslation()) { + $untranslated_entity = $entity->getUntranslated(); + $untranslated_entity->removeTranslation($entity->language()->getId()); + $untranslated_entity->save(); + $form_state->setRedirectUrl($untranslated_entity->urlInfo('canonical')); + } + else { + $entity->delete(); + $form_state->setRedirectUrl($this->getRedirectUrl()); + } + + drupal_set_message($this->getDeletionMessage()); + $this->logDeletionMessage(); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + return $entity->isDefaultTranslation() ? $this->traitGetCancelUrl() : $entity->urlInfo('canonical'); + } + + /** + * {@inheritdoc} + */ + protected function getDeletionMessage() { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + + if (!$entity->isDefaultTranslation()) { + return $this->t('The @entity-type %label @language translation has been deleted.', [ + '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), + '%label' => $entity->label(), + '@language' => $entity->language()->getName(), + ]); + } + + return $this->traitGetDeletionMessage(); + } + + /** + * {@inheritdoc} + */ + protected function logDeletionMessage() { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + + if (!$entity->isDefaultTranslation()) { + $this->logger($entity->getEntityType()->getProvider())->notice('The @entity-type %label @language translation has been deleted.', [ + '@entity-type' => $entity->getEntityType()->getLowercaseLabel(), + '%label' => $entity->label(), + '@language' => $entity->language()->getName(), + ]); + } + else { + $this->traitLogDeletionMessage(); + } + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ + $entity = $this->getEntity(); + + if (!$entity->isDefaultTranslation()) { + return $this->t('Are you sure you want to delete the @language translation of the @entity-type %label?', array( + '@language' => $entity->language()->getName(), + '@entity-type' => $this->getEntity()->getEntityType()->getLowercaseLabel(), + '%label' => $this->getEntity()->label(), + )); + } + + return $this->traitGetQuestion(); + } } diff --git a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php index d35bcea71aee8fe6e3856f0e38d899b7bb0c5aab..8b9fc0eb31ef7059fe8885fb95bf307f06aeceb8 100644 --- a/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php +++ b/core/lib/Drupal/Core/Entity/EntityDeleteFormTrait.php @@ -9,6 +9,7 @@ use Drupal\Core\Config\Entity\ConfigDependencyDeleteFormTrait; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; /** * Provides a trait for an entity deletion form. @@ -90,6 +91,24 @@ public function getCancelUrl() { } } + /** + * Returns the URL where the user should be redirected after deletion. + * + * @return \Drupal\Core\Url + * The redirect URL. + */ + protected function getRedirectUrl() { + $entity = $this->getEntity(); + if ($entity->hasLinkTemplate('collection')) { + // If available, return the collection URL. + return $entity->urlInfo('collection'); + } + else { + // Otherwise fall back to the front page. + return Url::fromRoute('<front>'); + } + } + /** * Logs a message about the deleted entity. */ diff --git a/core/lib/Drupal/Core/Form/ConfirmFormBase.php b/core/lib/Drupal/Core/Form/ConfirmFormBase.php index 28966900e53674e928381e524c96ae2804cd4468..03c7acd242860472947846a3234d9158097e7286 100644 --- a/core/lib/Drupal/Core/Form/ConfirmFormBase.php +++ b/core/lib/Drupal/Core/Form/ConfirmFormBase.php @@ -54,6 +54,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { $form['actions']['submit'] = array( '#type' => 'submit', '#value' => $this->getConfirmText(), + '#button_type' => 'primary', ); $form['actions']['cancel'] = ConfirmFormHelper::buildCancelLink($this, $this->getRequest()); diff --git a/core/modules/aggregator/src/Form/FeedDeleteForm.php b/core/modules/aggregator/src/Form/FeedDeleteForm.php index 2acab06d1d1b15a5679324545fba39123f04af6c..f353a8c22069f2032f3575cf0e876bea37981bfc 100644 --- a/core/modules/aggregator/src/Form/FeedDeleteForm.php +++ b/core/modules/aggregator/src/Form/FeedDeleteForm.php @@ -22,6 +22,13 @@ public function getCancelUrl() { return new Url('aggregator.admin_overview'); } + /** + * {@inheritdoc} + */ + protected function getRedirectUrl() { + return $this->getCancelUrl(); + } + /** * {@inheritdoc} */ diff --git a/core/modules/comment/src/Form/DeleteForm.php b/core/modules/comment/src/Form/DeleteForm.php index c754bfefe0c3f6626b466933b82e658f7cd9d16f..cfeaa53cd2702115ea850ea4fd510438092f9d68 100644 --- a/core/modules/comment/src/Form/DeleteForm.php +++ b/core/modules/comment/src/Form/DeleteForm.php @@ -8,7 +8,6 @@ namespace Drupal\comment\Form; use Drupal\Core\Entity\ContentEntityDeleteForm; -use Drupal\Core\Form\FormStateInterface; /** * Provides the comment delete confirmation form. @@ -23,6 +22,13 @@ public function getCancelUrl() { return $this->entity->get('entity_id')->entity->urlInfo(); } + /** + * {@inheritdoc} + */ + protected function getRedirectUrl() { + return $this->getCancelUrl(); + } + /** * {@inheritdoc} */ diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module index 280d4d626dfbb7d3a0b3a5d8346223015d5d2702..24f8f18ffd1f95334f4269a56f3a8786eedc8ea9 100644 --- a/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -127,6 +127,9 @@ function content_translation_entity_type_alter(array &$entity_types) { if (!$entity_type->get('content_translation_metadata')) { $entity_type->set('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadataWrapper'); } + if (!$entity_type->getFormClass('content_translation_deletion')) { + $entity_type->setFormClass('content_translation_deletion', '\Drupal\content_translation\Form\ContentTranslationDeleteForm'); + } $translation = $entity_type->get('translation'); if (!$translation || !isset($translation['content_translation'])) { @@ -277,8 +280,10 @@ function content_translation_form_alter(array &$form, FormStateInterface $form_s return; } $entity = $form_object->getEntity(); + $op = $form_object->getOperation(); - if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && count($entity->getTranslationLanguages()) > 1) { + // Let the content translation handler alter the content entity edit form. + if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && count($entity->getTranslationLanguages()) > 1 && ($op == 'edit' || $op == 'default')) { $controller = \Drupal::entityManager()->getHandler($entity->getEntityTypeId(), 'translation'); $controller->entityFormAlter($form, $form_state, $entity); diff --git a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php index aa01d674e044f21eebdfdc0bd6d3e76206580777..9d2e66472f889e5f876304d6e5f8841b073ac712 100644 --- a/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php +++ b/core/modules/content_translation/src/Access/ContentTranslationManageAccessCheck.php @@ -73,13 +73,28 @@ public function __construct(EntityManagerInterface $manager, LanguageManagerInte public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, $source = NULL, $target = NULL, $language = NULL, $entity_type_id = NULL) { /* @var \Drupal\Core\Entity\ContentEntityInterface $entity */ if ($entity = $route_match->getParameter($entity_type_id)) { + $operation = $route->getRequirement('_access_content_translation_manage'); + $language = $this->languageManager->getLanguage($language) ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); + $entity_type = $this->entityManager->getDefinition($entity_type_id); + + if (in_array($operation, ['update', 'delete'])) { + // Translation operations cannot be performed on the default + // translation. + if ($language->getId() == $entity->getUntranslated()->language()->getId()) { + return AccessResult::forbidden()->cacheUntilEntityChanges($entity); + } + // Editors have no access to the translation operations, as entity + // access already grants them an equal or greater access level. + $templates = ['update' => 'edit-form', 'delete' => 'delete-form']; + if ($entity->access($operation) && $entity_type->hasLinkTemplate($templates[$operation])) { + return AccessResult::forbidden()->cachePerPermissions(); + } + } if ($account->hasPermission('translate any entity')) { return AccessResult::allowed()->cachePerPermissions(); } - $operation = $route->getRequirement('_access_content_translation_manage'); - /* @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */ $handler = $this->entityManager->getHandler($entity->getEntityTypeId(), 'translation'); @@ -98,9 +113,8 @@ public function access(Route $route, RouteMatchInterface $route_match, AccountIn return AccessResult::allowedIf($is_new_translation)->cachePerPermissions()->cacheUntilEntityChanges($entity) ->andIf($handler->getTranslationAccess($entity, $operation)); - case 'update': case 'delete': - $language = $this->languageManager->getLanguage($language) ?: $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); + case 'update': $has_translation = isset($languages[$language->getId()]) && $language->getId() != $entity->getUntranslated()->language()->getId() && isset($translations[$language->getId()]); diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php index 0bd26f4d7a3f16ce9b442eda064c56823d20adf4..76e05bd46e1c2df6c7522e89a8f9de37ba959195 100644 --- a/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -319,12 +319,13 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En break; } } + $access = $this->getTranslationAccess($entity, 'delete')->isAllowed() || ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form')); $form['actions']['delete_translation'] = array( '#type' => 'submit', '#value' => t('Delete translation'), '#weight' => $weight, '#submit' => array(array($this, 'entityFormDeleteTranslation')), - '#access' => $this->getTranslationAccess($entity, 'delete')->isAllowed(), + '#access' => $access, ); } @@ -607,13 +608,20 @@ function entityFormDelete($form, FormStateInterface $form_state) { * Takes care of content translation deletion. */ function entityFormDeleteTranslation($form, FormStateInterface $form_state) { + /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */ $form_object = $form_state->getFormObject(); + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $form_object->getEntity(); $entity_type_id = $entity->getEntityTypeId(); - $form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, array( - $entity_type_id => $entity->id(), - 'language' => $form_object->getFormLangcode($form_state), - )); + if ($entity->access('delete') && $this->entityType->hasLinkTemplate('delete-form')) { + $form_state->setRedirectUrl($entity->urlInfo('delete-form')); + } + else { + $form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, [ + $entity_type_id => $entity->id(), + 'language' => $form_object->getFormLangcode($form_state), + ]); + } } /** diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php index d00dee24a41b251e8b87ffd815fcc957e617a8e3..bae6a940e19b835225d7dd392b750ac25e58f29c 100644 --- a/core/modules/content_translation/src/Controller/ContentTranslationController.php +++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php @@ -71,10 +71,12 @@ public function prepareTranslation(ContentEntityInterface $entity, LanguageInter * Array of page elements to render. */ public function overview(RouteMatchInterface $route_match, $entity_type_id = NULL) { + /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */ $entity = $route_match->getParameter($entity_type_id); $account = $this->currentUser(); $handler = $this->entityManager()->getHandler($entity_type_id, 'translation'); $manager = $this->manager; + $entity_type = $entity->getEntityType(); $languages = $this->languageManager()->getLanguages(); $original = $entity->getUntranslated()->language()->getId(); @@ -164,7 +166,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL // If the user is allowed to edit the entity we point the edit link to // the entity form, otherwise if we are not dealing with the original // language we point the link to the translation form. - if ($entity->access('update')) { + if ($entity->access('update') && $entity_type->hasLinkTemplate('edit-form')) { $links['edit']['url'] = $entity->urlInfo('edit-form'); $links['edit']['language'] = $language; } @@ -190,7 +192,14 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL } else { $source_name = isset($languages[$source]) ? $languages[$source]->getName() : $this->t('n/a'); - if ($handler->getTranslationAccess($entity, 'delete')->isAllowed()) { + if ($entity->access('delete') && $entity_type->hasLinkTemplate('delete-form')) { + $links['delete'] = array( + 'title' => $this->t('Delete'), + 'url' => $entity->urlInfo('delete-form'), + 'language' => $language, + ); + } + elseif ($handler->getTranslationAccess($entity, 'delete')->isAllowed()) { $links['delete'] = array( 'title' => $this->t('Delete'), 'url' => $delete_url, diff --git a/core/modules/content_translation/src/Form/ContentTranslationDeleteForm.php b/core/modules/content_translation/src/Form/ContentTranslationDeleteForm.php index a17479a8993cac0a541c76ba2e75d444b4910523..0a6c589292b6c5d038581ff7f702bce96f4d1570 100644 --- a/core/modules/content_translation/src/Form/ContentTranslationDeleteForm.php +++ b/core/modules/content_translation/src/Form/ContentTranslationDeleteForm.php @@ -7,29 +7,14 @@ namespace Drupal\content_translation\Form; -use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Entity\ContentEntityDeleteForm; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Language\LanguageInterface; -use Drupal\Core\Url; /** * Delete translation form for content_translation module. */ -class ContentTranslationDeleteForm extends ConfirmFormBase { - - /** - * The entity whose translation is being deleted. - * - * @var \Drupal\Core\Entity\EntityInterface - */ - protected $entity; - - /** - * The language of the translation being deleted. - * - * @var \Drupal\Core\Language\LanguageInterface - */ - protected $language; +class ContentTranslationDeleteForm extends ContentEntityDeleteForm { /** * {@inheritdoc} @@ -41,43 +26,11 @@ public function getFormId() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state, $entity_type_id = NULL, LanguageInterface $language = NULL) { - $this->entity = $this->getRouteMatch()->getParameter($entity_type_id); - $this->language = $language; + public function buildForm(array $form, FormStateInterface $form_state, LanguageInterface $language = NULL) { + if ($language) { + $form_state->set('langcode', $language->getId()); + } return parent::buildForm($form, $form_state); } - /** - * {@inheritdoc} - */ - public function getConfirmText() { - return $this->t('Delete'); - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return $this->t('Are you sure you want to delete the @language translation of %label?', array('@language' => $this->language->getName(), '%label' => $this->entity->label())); - } - - /** - * {@inheritdoc} - */ - public function getCancelUrl() { - return $this->entity->urlInfo('drupal:content-translation-overview'); - } - - /** - * {@inheritdoc} - */ - public function submitForm(array &$form, FormStateInterface $form_state) { - // Remove the translated values. - $this->entity = $this->entity->getUntranslated(); - $this->entity->removeTranslation($this->language->getId()); - $this->entity->save(); - - $form_state->setRedirectUrl($this->getCancelUrl()); - } - } diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php index d9d672c9707cd6317e9de50769b318f28c6e71df..1aa35b93ef13214dfbb35979cb1e64d31365ef00 100644 --- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php +++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php @@ -141,7 +141,7 @@ protected function alterRoutes(RouteCollection $collection) { $route = new Route( $path . '/delete/{language}', array( - '_form' => '\Drupal\content_translation\Form\ContentTranslationDeleteForm', + '_entity_form' => $entity_type_id . '.content_translation_deletion', 'language' => NULL, '_title' => 'Delete', 'entity_type_id' => $entity_type_id, diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php index 3c1bd787fb3901c74daa7424eccec885a023d229..bf781fb6dde9198482b2787746d98ad52b105b1c 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationUITestBase.php @@ -253,16 +253,23 @@ protected function doTestAuthoringInfo() { */ protected function doTestTranslationDeletion() { // Confirm and delete a translation. + $this->drupalLogin($this->translator); $langcode = 'fr'; $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); - $url = $entity->urlInfo('edit-form', array('language' => ConfigurableLanguage::load($langcode))); + $language = ConfigurableLanguage::load($langcode); + $url = $entity->urlInfo('edit-form', array('language' => $language)); $this->drupalPostForm($url, array(), t('Delete translation')); - $this->drupalPostForm(NULL, array(), t('Delete')); + $this->drupalPostForm(NULL, array(), t('Delete @language translation', array('@language' => $language->getName()))); $entity = entity_load($this->entityTypeId, $this->entityId, TRUE); if ($this->assertTrue(is_object($entity), 'Entity found')) { $translations = $entity->getTranslationLanguages(); $this->assertTrue(count($translations) == 2 && empty($translations[$langcode]), 'Translation successfully deleted.'); } + + // Check that the translator cannot delete the original translation. + $args = [$this->entityTypeId => $entity->id(), 'language' => 'en']; + $this->drupalGet(Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, $args)); + $this->assertResponse(403); } /** diff --git a/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php index 62daf7557e015fa20a9397a031a54ebc33d386e6..3de797ec5fdfb778ca4f9a6af15ad749fcf69db5 100644 --- a/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php +++ b/core/modules/content_translation/src/Tests/ContentTranslationWorkflowsTest.php @@ -7,6 +7,7 @@ namespace Drupal\content_translation\Tests; +use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Url; use Drupal\user\UserInterface; @@ -72,16 +73,37 @@ protected function setupEntity() { */ function testWorkflows() { // Test workflows for the editor. - $expected_status = array('edit' => 200, 'overview' => 403, 'add_translation' => 403, 'edit_translation' => 403); - $this->assertWorkflows($this->editor, $expected_status); + $expected_status = [ + 'edit' => 200, + 'delete' => 200, + 'overview' => 403, + 'add_translation' => 403, + 'edit_translation' => 403, + 'delete_translation' => 403, + ]; + $this->doTestWorkflows($this->editor, $expected_status); // Test workflows for the translator. - $expected_status = array('edit' => 403, 'overview' => 200, 'add_translation' => 200, 'edit_translation' => 200); - $this->assertWorkflows($this->translator, $expected_status); + $expected_status = [ + 'edit' => 403, + 'delete' => 403, + 'overview' => 200, + 'add_translation' => 200, + 'edit_translation' => 200, + 'delete_translation' => 200, + ]; + $this->doTestWorkflows($this->translator, $expected_status); // Test workflows for the admin. - $expected_status = array('edit' => 200, 'overview' => 200, 'add_translation' => 200, 'edit_translation' => 200); - $this->assertWorkflows($this->administrator, $expected_status); + $expected_status = [ + 'edit' => 200, + 'delete' => 200, + 'overview' => 200, + 'add_translation' => 200, + 'edit_translation' => 403, + 'delete_translation' => 403, + ]; + $this->doTestWorkflows($this->administrator, $expected_status); // Check that translation permissions allow the associated operations. $ops = array('create' => t('Add'), 'update' => t('Edit'), 'delete' => t('Delete')); @@ -111,30 +133,35 @@ function testWorkflows() { * The an associative array with the operation name as key and the expected * status as value. */ - protected function assertWorkflows(UserInterface $user, $expected_status) { + protected function doTestWorkflows(UserInterface $user, $expected_status) { $default_langcode = $this->langcodes[0]; $languages = $this->container->get('language_manager')->getLanguages(); - $args = array('@user_label' => $user->getUsername()); + $args = ['@user_label' => $user->getUsername()]; + $options = ['language' => $languages[$default_langcode], 'absolute' => TRUE]; $this->drupalLogin($user); // Check whether the user is allowed to access the entity form in edit mode. - $options = array('language' => $languages[$default_langcode], 'absolute' => TRUE); $edit_url = $this->entity->urlInfo('edit-form', $options); $this->drupalGet($edit_url, $options); - $this->assertResponse($expected_status['edit'], format_string('The @user_label has the expected edit access.', $args)); + $this->assertResponse($expected_status['edit'], SafeMarkup::format('The @user_label has the expected edit access.', $args)); + + // Check whether the user is allowed to access the entity delete form. + $delete_url = $this->entity->urlInfo('delete-form', $options); + $this->drupalGet($delete_url, $options); + $this->assertResponse($expected_status['delete'], SafeMarkup::format('The @user_label has the expected delete access.', $args)); // Check whether the user is allowed to access the translation overview. $langcode = $this->langcodes[1]; - $options = array('language' => $languages[$langcode], 'absolute' => TRUE); + $options['language'] = $languages[$langcode]; $translations_url = $this->entity->url('drupal:content-translation-overview', $options); $this->drupalGet($translations_url); - $this->assertResponse($expected_status['overview'], format_string('The @user_label has the expected translation overview access.', $args)); + $this->assertResponse($expected_status['overview'], SafeMarkup::format('The @user_label has the expected translation overview access.', $args)); // Check whether the user is allowed to create a translation. $add_translation_url = Url::fromRoute('content_translation.translation_add_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options); if ($expected_status['add_translation'] == 200) { $this->clickLink('Add'); - $this->assertUrl($add_translation_url->toString(), array(), 'The translation overview points to the translation form when creating translations.'); + $this->assertUrl($add_translation_url->toString(), [], 'The translation overview points to the translation form when creating translations.'); // Check that the translation form does not contain shared elements for // translators. if ($expected_status['edit'] == 403) { @@ -144,13 +171,12 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { else { $this->drupalGet($add_translation_url); } - $this->assertResponse($expected_status['add_translation'], format_string('The @user_label has the expected translation creation access.', $args)); + $this->assertResponse($expected_status['add_translation'], SafeMarkup::format('The @user_label has the expected translation creation access.', $args)); // Check whether the user is allowed to edit a translation. $langcode = $this->langcodes[2]; $options['language'] = $languages[$langcode]; $edit_translation_url = Url::fromRoute('content_translation.translation_edit_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); - $options = ['language' => $languages[$langcode], 'absolute' => TRUE]; if ($expected_status['edit_translation'] == 200) { $this->drupalGet($translations_url); $editor = $expected_status['edit'] == 200; @@ -158,7 +184,6 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { if ($editor) { $this->clickLink('Edit', 2); // An editor should be pointed to the entity form in multilingual mode. - // We need a new expected edit path with a new language. $expected_edit_path = $this->entity->url('edit-form', $options); $this->assertUrl($expected_edit_path, [], 'The translation overview points to the edit form for editors when editing translations.'); @@ -166,7 +191,7 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { else { $this->clickLink('Edit'); // While a translator should be pointed to the translation form. - $this->assertUrl($edit_translation_url->toString(), array(), 'The translation overview points to the translation form for translators when editing translations.'); + $this->assertUrl($edit_translation_url->toString(), [], 'The translation overview points to the translation form for translators when editing translations.'); // Check that the translation form does not contain shared elements. $this->assertNoSharedElements(); } @@ -174,7 +199,35 @@ protected function assertWorkflows(UserInterface $user, $expected_status) { else { $this->drupalGet($edit_translation_url); } - $this->assertResponse($expected_status['edit_translation'], format_string('The @user_label has the expected translation creation access.', $args)); + $this->assertResponse($expected_status['edit_translation'], SafeMarkup::format('The @user_label has the expected translation edit access.', $args)); + + // Check whether the user is allowed to delete a translation. + $langcode = $this->langcodes[2]; + $options['language'] = $languages[$langcode]; + $delete_translation_url = Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options); + if ($expected_status['delete_translation'] == 200) { + $this->drupalGet($translations_url); + $editor = $expected_status['delete'] == 200; + + if ($editor) { + $this->clickLink('Delete', 2); + // An editor should be pointed to the entity deletion form in + // multilingual mode. We need a new expected delete path with a new + // language. + $expected_delete_path = $this->entity->url('delete-form', $options); + $this->assertUrl($expected_delete_path, [], 'The translation overview points to the delete form for editors when deleting translations.'); + } + else { + $this->clickLink('Delete'); + // While a translator should be pointed to the translation deletion + // form. + $this->assertUrl($delete_translation_url->toString(), [], 'The translation overview points to the translation deletion form for translators when deleting translations.'); + } + } + else { + $this->drupalGet($delete_translation_url); + } + $this->assertResponse($expected_status['delete_translation'], SafeMarkup::format('The @user_label has the expected translation deletion access.', $args)); } /** diff --git a/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php b/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php index 0487d08352b083db04856a99087845d9c7990c46..512e73544e0d9de00dfd2f879cb51a880248ab05 100644 --- a/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php +++ b/core/modules/content_translation/tests/src/Unit/Access/ContentTranslationManageAccessCheckTest.php @@ -47,13 +47,17 @@ public function testCreateAccess() { // Set the mock language manager. $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); $language_manager->expects($this->at(0)) + ->method('getLanguage') + ->with($this->equalTo($source)) + ->will($this->returnValue(new Language(array('id' => 'en')))); + $language_manager->expects($this->at(1)) ->method('getLanguages') ->will($this->returnValue(array('en' => array(), 'it' => array()))); - $language_manager->expects($this->at(1)) + $language_manager->expects($this->at(2)) ->method('getLanguage') ->with($this->equalTo($source)) ->will($this->returnValue(new Language(array('id' => 'en')))); - $language_manager->expects($this->at(2)) + $language_manager->expects($this->at(3)) ->method('getLanguage') ->with($this->equalTo($target)) ->will($this->returnValue(new Language(array('id' => 'it')))); diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php index dcc416dd6ae36e58fbc8b6226bfee4ab25be9a79..e75d075f2fb9ec98f5f8908629d82f1f055e984f 100644 --- a/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php @@ -22,6 +22,13 @@ public function getCancelUrl() { return new Url('entity.menu.edit_form', array('menu' => $this->entity->getMenuName())); } + /** + * {@inheritdoc} + */ + protected function getRedirectUrl() { + return $this->getCancelUrl(); + } + /** * {@inheritdoc} */ diff --git a/core/modules/node/src/Form/NodeDeleteForm.php b/core/modules/node/src/Form/NodeDeleteForm.php index 16c86e6b367e17bb4041f5e82565916d024d6863..0e22c82fea132006988f54c2b020e9e702cb52c0 100644 --- a/core/modules/node/src/Form/NodeDeleteForm.php +++ b/core/modules/node/src/Form/NodeDeleteForm.php @@ -8,7 +8,6 @@ namespace Drupal\node\Form; use Drupal\Core\Entity\ContentEntityDeleteForm; -use Drupal\Core\Form\FormStateInterface; /** * Provides a form for deleting a node. @@ -18,13 +17,34 @@ class NodeDeleteForm extends ContentEntityDeleteForm { /** * {@inheritdoc} */ - public function submitForm(array &$form, FormStateInterface $form_state) { - $this->entity->delete(); - $this->logger('content')->notice('@type: deleted %title.', array('@type' => $this->entity->bundle(), '%title' => $this->entity->label())); + protected function getDeletionMessage() { + /** @var \Drupal\node\NodeInterface $entity */ + $entity = $this->getEntity(); + $node_type_storage = $this->entityManager->getStorage('node_type'); - $node_type = $node_type_storage->load($this->entity->bundle())->label(); - drupal_set_message(t('@type %title has been deleted.', array('@type' => $node_type, '%title' => $this->entity->label()))); - $form_state->setRedirect('<front>'); + $node_type = $node_type_storage->load($entity->bundle())->label(); + + if (!$entity->isDefaultTranslation()) { + return $this->t('@language translation of the @type %label has been deleted.', [ + '@language' => $entity->language()->getName(), + '@type' => $node_type, + '%label' => $entity->label(), + ]); + } + + return $this->t('The @type %title has been deleted.', array( + '@type' => $node_type, + '%title' => $this->getEntity()->label(), + )); + } + + /** + * {@inheritdoc} + */ + protected function logDeletionMessage() { + /** @var \Drupal\node\NodeInterface $entity */ + $entity = $this->getEntity(); + $this->logger('content')->notice('@type: deleted %title.', ['@type' => $entity->getType(), '%title' => $entity->label()]); } } diff --git a/core/modules/shortcut/src/Form/ShortcutDeleteForm.php b/core/modules/shortcut/src/Form/ShortcutDeleteForm.php index 2b0ebb2222e26c49e437419841272c2945cda4d8..b3864cd5dff6c9c4f4f592b09252860ed4c6f626 100644 --- a/core/modules/shortcut/src/Form/ShortcutDeleteForm.php +++ b/core/modules/shortcut/src/Form/ShortcutDeleteForm.php @@ -31,4 +31,11 @@ public function getCancelUrl() { )); } + /** + * {@inheritdoc} + */ + protected function getRedirectUrl() { + return $this->getCancelUrl(); + } + } diff --git a/core/modules/system/src/Tests/Entity/EntityFormTest.php b/core/modules/system/src/Tests/Entity/EntityFormTest.php index 307b69a2dbce4249d95add7bd67fbaeb121b983d..e4365ebe977b11b8555bf742826c329b9b4904e0 100644 --- a/core/modules/system/src/Tests/Entity/EntityFormTest.php +++ b/core/modules/system/src/Tests/Entity/EntityFormTest.php @@ -7,6 +7,7 @@ namespace Drupal\system\Tests\Entity; +use Drupal\language\Entity\ConfigurableLanguage; use Drupal\simpletest\WebTestBase; /** @@ -27,6 +28,9 @@ protected function setUp() { parent::setUp(); $web_user = $this->drupalCreateUser(array('administer entity_test content')); $this->drupalLogin($web_user); + + // Add a language. + ConfigurableLanguage::createFromLangcode('ro')->save(); } /** @@ -39,6 +43,16 @@ function testFormCRUD() { } } + /** + * Tests basic multilingual form CRUD functionality. + */ + public function testMultilingualFormCRUD() { + // All entity variations have to have the same results. + foreach (entity_test_entity_types(ENTITY_TEST_TYPES_MULTILINGUAL) as $entity_type) { + $this->doTestMultilingualFormCRUD($entity_type); + } + } + /** * Tests hook_entity_form_display_alter(). * @@ -84,13 +98,55 @@ protected function doTestFormCRUD($entity_type) { $this->assertFalse($entity, format_string('%entity_type: Entity not found in the database.', array('%entity_type' => $entity_type))); } + /** + * Executes the multilingual form CRUD tests for the given entity type ID. + * + * @param string $entity_type_id + * The ID of entity type to run the tests with. + */ + protected function doTestMultilingualFormCRUD($entity_type_id) { + $name1 = $this->randomMachineName(8); + $name1_ro = $this->randomMachineName(9); + $name2_ro = $this->randomMachineName(11); + + $edit = array( + 'name[0][value]' => $name1, + 'field_test_text[0][value]' => $this->randomMachineName(16), + ); + + $this->drupalPostForm($entity_type_id . '/add', $edit, t('Save')); + $entity = $this->loadEntityByName($entity_type_id, $name1); + $this->assertTrue($entity, format_string('%entity_type: Entity found in the database.', array('%entity_type' => $entity_type_id))); + + // Add a translation to the newly created entity without using the Content + // translation module. + $entity->addTranslation('ro', ['name' => $name1_ro])->save(); + $translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro'); + $this->assertEqual($translated_entity->name->value, $name1_ro, format_string('%entity_type: The translation has been added.', array('%entity_type' => $entity_type_id))); + + $edit['name[0][value]'] = $name2_ro; + $this->drupalPostForm('ro/' . $entity_type_id . '/manage/' . $entity->id(), $edit, t('Save')); + $translated_entity = $this->loadEntityByName($entity_type_id, $name1)->getTranslation('ro'); + $this->assertTrue($translated_entity, format_string('%entity_type: Modified translation found in the database.', array('%entity_type' => $entity_type_id))); + $this->assertEqual($translated_entity->name->value, $name2_ro, format_string('%entity_type: The name of the translation has been modified.', array('%entity_type' => $entity_type_id))); + + $this->drupalGet('ro/' . $entity_type_id . '/manage/' . $entity->id()); + $this->clickLink(t('Delete')); + $this->drupalPostForm(NULL, array(), t('Delete Romanian translation')); + $entity = $this->loadEntityByName($entity_type_id, $name1); + $this->assertNotNull($entity, format_string('%entity_type: The original entity still exists.', array('%entity_type' => $entity_type_id))); + $this->assertFalse($entity->hasTranslation('ro'), format_string('%entity_type: Entity translation does not exist anymore.', array('%entity_type' => $entity_type_id))); + } + /** * Loads a test entity by name always resetting the storage cache. */ protected function loadEntityByName($entity_type, $name) { // Always load the entity from the database to ensure that changes are // correctly picked up. - $this->container->get('entity.manager')->getStorage($entity_type)->resetCache(); - return current(entity_load_multiple_by_properties($entity_type, array('name' => $name))); + $entity_storage = $this->container->get('entity.manager')->getStorage($entity_type); + $entity_storage->resetCache(); + $entities = $entity_storage->loadByProperties(array('name' => $name)); + return $entities ? current($entities) : NULL; } } diff --git a/core/modules/taxonomy/src/Form/TermDeleteForm.php b/core/modules/taxonomy/src/Form/TermDeleteForm.php index 1552fd3622db99f50de67035195336b1e0718be5..17019465429838b9bd1f5898ac61e3acf4ddad12 100644 --- a/core/modules/taxonomy/src/Form/TermDeleteForm.php +++ b/core/modules/taxonomy/src/Form/TermDeleteForm.php @@ -19,24 +19,17 @@ class TermDeleteForm extends ContentEntityDeleteForm { /** * {@inheritdoc} */ - public function getFormId() { - return 'taxonomy_term_confirm_delete'; - } - - /** - * {@inheritdoc} - */ - public function getQuestion() { - return $this->t('Are you sure you want to delete the term %title?', array('%title' => $this->entity->getName())); + public function getCancelUrl() { + // The cancel URL is the vocabulary collection, terms have no global + // list page. + return new Url('entity.taxonomy_vocabulary.collection'); } /** * {@inheritdoc} */ - public function getCancelUrl() { - // The cancel URL is the vocabulary collection, terms have no global - // list page. - return new Url('entity.taxonomy_vocabulary.collection'); + protected function getRedirectUrl() { + return $this->getCancelUrl(); } /** @@ -59,12 +52,15 @@ protected function getDeletionMessage() { public function submitForm(array &$form, FormStateInterface $form_state) { parent::submitForm($form, $form_state); - $storage = $this->entityManager->getStorage('taxonomy_vocabulary'); - $vocabulary = $storage->load($this->entity->bundle()); - - // @todo Move to storage https://www.drupal.org/node/1988712. - taxonomy_check_vocabulary_hierarchy($vocabulary, array('tid' => $this->entity->id())); + /** @var \Drupal\Core\Entity\ContentEntityInterface $term */ + $term = $this->getEntity(); + if ($term->isDefaultTranslation()) { + $storage = $this->entityManager->getStorage('taxonomy_vocabulary'); + $vocabulary = $storage->load($this->entity->bundle()); + // @todo Move to storage http://drupal.org/node/1988712 + taxonomy_check_vocabulary_hierarchy($vocabulary, array('tid' => $term->id())); + } } } diff --git a/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php b/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php index f6c7480b78e327bb6bab9b20db8307104e9063bf..00df7896ad7f3b9badd59a8aa620a1b00b7ad028 100644 --- a/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php +++ b/core/modules/taxonomy/src/Tests/VocabularyPermissionsTest.php @@ -51,7 +51,7 @@ function testVocabularyPermissionsTaxonomyTerm() { // Delete the vocabulary. $this->drupalGet('taxonomy/term/' . $term->id() . '/delete'); - $this->assertRaw(t('Are you sure you want to delete the term %name?', array('%name' => $edit['name[0][value]'])), 'Delete taxonomy term form opened successfully.'); + $this->assertRaw(t('Are you sure you want to delete the @entity-type %label?', array('@entity-type' => 'taxonomy term', '%label' => $edit['name[0][value]'])), 'Delete taxonomy term form opened successfully.'); // Confirm deletion. $this->drupalPostForm(NULL, NULL, t('Delete')); @@ -98,7 +98,7 @@ function testVocabularyPermissionsTaxonomyTerm() { // Delete the vocabulary. $this->drupalGet('taxonomy/term/' . $term->id() . '/delete'); - $this->assertRaw(t('Are you sure you want to delete the term %name?', array('%name' => $term->getName())), 'Delete taxonomy term form opened successfully.'); + $this->assertRaw(t('Are you sure you want to delete the @entity-type %label?', array('@entity-type' => 'taxonomy term', '%label' => $term->getName())), 'Delete taxonomy term form opened successfully.'); // Confirm deletion. $this->drupalPostForm(NULL, NULL, t('Delete'));