diff --git a/src/Form/RevisionRevertForm.php b/src/Form/RevisionRevertForm.php new file mode 100644 index 0000000000000000000000000000000000000000..de2e049a50aec18a043f84690f1bba2b55a799da --- /dev/null +++ b/src/Form/RevisionRevertForm.php @@ -0,0 +1,173 @@ +<?php + +/** + * @file + * Contains \Drupal\entity\Form\RevisionRevertForm. + */ + +namespace Drupal\entity\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityTypeBundleInfoInterface; +use Drupal\Core\Entity\RevisionableInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\entity\Revision\EntityRevisionLogInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\Request; + +class RevisionRevertForm extends ConfirmFormBase { + + /** + * The entity revision. + * + * @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface|\Drupal\entity\Revision\EntityRevisionLogInterface + */ + protected $revision; + + /** + * The date formatter. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * The entity bundle information. + * + * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface + */ + protected $bundleInformation; + + /** + * Creates a new RevisionRevertForm instance. + * + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter. + * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_information + * The bundle information. + */ + public function __construct(DateFormatterInterface $date_formatter, EntityTypeBundleInfoInterface $bundle_information) { + $this->dateFormatter = $date_formatter; + $this->bundleInformation = $bundle_information; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('date.formatter'), + $container->get('entity_type.bundle.info') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'entity_revision_revert_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + if ($this->revision instanceof EntityRevisionLogInterface) { + return $this->t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionLogMessage())]); + } + return $this->t('Are you sure you want to revert the revision?'); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + if ($this->revision->getEntityType()->hasLinkTemplate('version-history')) { + return $this->revision->toUrl('version-history'); + } + return $this->revision->toUrl(); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Revert'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $_entity_revision = NULL, Request $request = NULL) { + $this->revision = $_entity_revision; + $form = parent::buildForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + // The revision timestamp will be updated when the revision is saved. Keep + // the original one for the confirmation message. + + $this->revision = $this->prepareRevision($this->revision); + if ($this->revision instanceof EntityRevisionLogInterface) { + $original_revision_timestamp = $this->revision->getRevisionCreationTime(); + + $this->revision->setRevisionLogMessage($this->t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)])); + drupal_set_message(t('@type %title has been reverted to the revision from %revision-date.', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)])); + } + else { + drupal_set_message(t('@type %title has been reverted', ['@type' => $this->getBundleLabel($this->revision), '%title' => $this->revision->label()])); + } + + $this->revision->save(); + + $this->logger('content')->notice('@type: reverted %title revision %revision.', ['@type' => $this->revision->bundle(), '%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]); + $form_state->setRedirect( + "entity.{$this->revision->getEntityTypeId()}.version_history", + [$this->revision->getEntityTypeId() => $this->revision->id()] + ); + } + + /** + * Prepares a revision to be reverted. + * + * @param \Drupal\Core\Entity\RevisionableInterface $revision + * The revision to be reverted. + * + * @return \Drupal\Core\Entity\RevisionableInterface + * The prepared revision ready to be stored. + */ + protected function prepareRevision(RevisionableInterface $revision) { + $revision->setNewRevision(); + $revision->isDefaultRevision(TRUE); + + return $revision; + } + + /** + * Returns a bundle label. + * + * @param \Drupal\Core\Entity\RevisionableInterface $revision + * The entity revision. + * + * @return string + */ + protected function getBundleLabel(RevisionableInterface $revision) { + /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\RevisionableInterface $revision */ + $bundle_info = $this->bundleInformation->getBundleInfo($revision->getEntityTypeId()); + return $bundle_info[$revision->bundle()]['label']; + } + +} diff --git a/src/Revision/RevisionableContentEntityBase.php b/src/Revision/RevisionableContentEntityBase.php new file mode 100644 index 0000000000000000000000000000000000000000..2b6ae620e3bea787a511879385da47451e1f4e99 --- /dev/null +++ b/src/Revision/RevisionableContentEntityBase.php @@ -0,0 +1,34 @@ +<?php + +/** + * @file + * Contains \Drupal\entity\Revision\RevisionableContentEntityBase. + */ + +namespace Drupal\entity\Revision; + +use Drupal\Core\Entity\ContentEntityBase; + +/** + * Provides an entity class with revisions. + */ +abstract class RevisionableContentEntityBase extends ContentEntityBase { + + /** + * {@inheritdoc} + */ + protected function urlRouteParameters($rel) { + $uri_route_parameters = []; + + if ($rel != 'collection') { + // The entity ID is needed as a route parameter. + $uri_route_parameters[$this->getEntityTypeId()] = $this->id(); + } + if (strpos($this->getEntityType()->getLinkTemplate($rel), $this->getEntityTypeId() . '_revision') !== FALSE) { + $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); + } + + return $uri_route_parameters; + } + +} diff --git a/src/Routing/RevisionRouteProvider.php b/src/Routing/RevisionRouteProvider.php index f9e6576542eb80bf2b6df6028acf64f239e8a77e..d539c6150b6083964ad09fcb87f688ff1f2b223b 100644 --- a/src/Routing/RevisionRouteProvider.php +++ b/src/Routing/RevisionRouteProvider.php @@ -26,6 +26,9 @@ class RevisionRouteProvider implements EntityRouteProviderInterface { if ($view_route = $this->getRevisionViewRoute($entity_type)) { $collection->add("entity.$entity_type_id.revision", $view_route); } + if ($view_route = $this->getRevisionRevertRoute($entity_type)) { + $collection->add("entity.$entity_type_id.revision_revert_form", $view_route); + } return $collection; } @@ -62,4 +65,36 @@ class RevisionRouteProvider implements EntityRouteProviderInterface { } } + /** + * Gets the entity revision revert route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getRevisionRevertRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('revision-revert-form')) { + $entity_type_id = $entity_type->id(); + $route = new Route($entity_type->getLinkTemplate('revision-revert-form')); + $route->addDefaults([ + '_form' => '\Drupal\entity\Form\RevisionRevertForm', + 'title' => 'Revert to earlier revision', + ]); + $route->addRequirements([ + '_entity_access_revision' => "$entity_type_id.update", + ]); + $route->setOption('parameters', [ + $entity_type->id() => [ + 'type' => 'entity:' . $entity_type->id(), + ], + $entity_type->id() . '_revision' => [ + 'type' => 'entity_revision:' . $entity_type->id(), + ], + ]); + return $route; + } + } + } diff --git a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php index 267e2cef2bdc1148b754b60a96b9f5765e07e623..d49e0bdcedfc0ca2842ad47eceb773446b0811c6 100644 --- a/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php +++ b/tests/modules/entity_module_test/src/Entity/EnhancedEntity.php @@ -7,11 +7,11 @@ namespace Drupal\entity_module_test\Entity; -use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; use Drupal\entity\EntityKeysFieldsTrait; use Drupal\entity\Revision\EntityRevisionLogTrait; +use Drupal\entity\Revision\RevisionableContentEntityBase; /** * Provides a test entity which uses all the capabilities of entity module. @@ -27,6 +27,7 @@ use Drupal\entity\Revision\EntityRevisionLogTrait; * "delete" = "\Drupal\Core\Entity\EntityDeleteForm", * }, * "route_provider" = { + * "html" = "\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider", * "revision" = "\Drupal\entity\Routing\RevisionRouteProvider", * "create" = "\Drupal\entity\Routing\CreateHtmlRouteProvider", * }, @@ -47,12 +48,14 @@ use Drupal\entity\Revision\EntityRevisionLogTrait; * links = { * "add-page" = "/entity_test_enhanced/add", * "add-form" = "/entity_test_enhanced/add/{type}", + * "canonical" = "/entity_test_enhanced/{entity_test_enhanced}", * "revision" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/view", + * "revision-revert-form" = "/entity_test_enhanced/{entity_test_enhanced}/revisions/{entity_test_enhanced_revision}/revert", * }, * bundle_entity_type = "entity_test_enhanced_bundle" * ) */ -class EnhancedEntity extends ContentEntityBase { +class EnhancedEntity extends RevisionableContentEntityBase { use EntityRevisionLogTrait; use EntityKeysFieldsTrait; diff --git a/tests/src/Kernel/RevisionBasicUITest.php b/tests/src/Kernel/RevisionBasicUITest.php index b716c66b51182e95fec0e62e9407f423f7dc2ae3..efb6f75ddb43ed663b3bd3f5adf5478354962ce6 100644 --- a/tests/src/Kernel/RevisionBasicUITest.php +++ b/tests/src/Kernel/RevisionBasicUITest.php @@ -80,4 +80,33 @@ class RevisionBasicUITest extends KernelTestBase { $this->assertContains('rev 2', $response->getContent()); } + public function testRevisionRevert() { + $entity = EnhancedEntity::create([ + 'name' => 'rev 1', + 'type' => 'entity_test_enhance', + ]); + $entity->save(); + $entity->name->value = 'rev 2'; + $entity->setNewRevision(TRUE); + $entity->isDefaultRevision(TRUE); + $entity->save(); + + $role = Role::create(['id' => 'test_role']); + $role->grantPermission('administer entity_test_enhanced'); + $role->grantPermission('revert all entity_test_enhanced revisions'); + $role->save(); + + $user = User::create([ + 'name' => 'Test user', + ]); + $user->addRole($role->id()); + \Drupal::service('account_switcher')->switchTo($user); + + /** @var \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel */ + $http_kernel = \Drupal::service('http_kernel'); + $request = Request::create($entity->url('revision-revert-form')); + $response = $http_kernel->handle($request); + $this->assertEquals(200, $response->getStatusCode()); + } + }