From 2ff30c7cc9c5118599e7d6554160b71e4ef5d5b3 Mon Sep 17 00:00:00 2001 From: Owen Bush <ojb@ukhhf.co.uk> Date: Tue, 14 May 2019 16:37:21 -0600 Subject: [PATCH] Enable translation and revisioning for eventseries and eventinstance --- .../src/Form/RegistrantForm.php | 2 +- recurring_events.links.action.yml | 1 - recurring_events.links.task.yml | 20 +- recurring_events.permissions.yml | 10 + recurring_events.routing.yml | 4 +- src/Controller/EventController.php | 79 ------ src/Controller/EventInstanceController.php | 222 ++++++++++++++++ src/Controller/EventSeriesController.php | 236 ++++++++++++++++++ src/Entity/EventInstance.php | 94 +++---- src/Entity/EventSeries.php | 94 +++---- src/EventInstanceHtmlRouteProvider.php | 163 ++++++++++++ src/EventInstanceStorage.php | 58 +++++ src/EventInstanceStorageInterface.php | 61 +++++ src/EventInstanceTranslationHandler.php | 14 ++ src/EventSeriesHtmlRouteProvider.php | 163 ++++++++++++ src/EventSeriesStorage.php | 58 +++++ src/EventSeriesStorageInterface.php | 61 +++++ src/EventSeriesTranslationHandler.php | 14 ++ src/Form/EventInstanceForm.php | 21 +- src/Form/EventInstanceRevisionDeleteForm.php | 122 +++++++++ src/Form/EventInstanceRevisionRevertForm.php | 149 +++++++++++ ...tInstanceRevisionRevertTranslationForm.php | 115 +++++++++ src/Form/EventSeriesDeleteForm.php | 2 +- src/Form/EventSeriesForm.php | 17 +- src/Form/EventSeriesRevisionDeleteForm.php | 122 +++++++++ src/Form/EventSeriesRevisionRevertForm.php | 149 +++++++++++ ...entSeriesRevisionRevertTranslationForm.php | 115 +++++++++ 27 files changed, 1981 insertions(+), 185 deletions(-) delete mode 100644 src/Controller/EventController.php create mode 100644 src/Controller/EventInstanceController.php create mode 100644 src/Controller/EventSeriesController.php create mode 100644 src/EventInstanceHtmlRouteProvider.php create mode 100644 src/EventInstanceStorage.php create mode 100644 src/EventInstanceStorageInterface.php create mode 100644 src/EventInstanceTranslationHandler.php create mode 100644 src/EventSeriesHtmlRouteProvider.php create mode 100644 src/EventSeriesStorage.php create mode 100644 src/EventSeriesStorageInterface.php create mode 100644 src/EventSeriesTranslationHandler.php create mode 100644 src/Form/EventInstanceRevisionDeleteForm.php create mode 100644 src/Form/EventInstanceRevisionRevertForm.php create mode 100644 src/Form/EventInstanceRevisionRevertTranslationForm.php create mode 100644 src/Form/EventSeriesRevisionDeleteForm.php create mode 100644 src/Form/EventSeriesRevisionRevertForm.php create mode 100644 src/Form/EventSeriesRevisionRevertTranslationForm.php diff --git a/modules/recurring_events_registration/src/Form/RegistrantForm.php b/modules/recurring_events_registration/src/Form/RegistrantForm.php index 40988816..18569fcd 100644 --- a/modules/recurring_events_registration/src/Form/RegistrantForm.php +++ b/modules/recurring_events_registration/src/Form/RegistrantForm.php @@ -87,7 +87,7 @@ class RegistrantForm extends ContentEntityForm { } /** - * Construct a EventSeriesForm. + * Construct an RegistrantForm. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. diff --git a/recurring_events.links.action.yml b/recurring_events.links.action.yml index 9f3ca0df..dd54450e 100644 --- a/recurring_events.links.action.yml +++ b/recurring_events.links.action.yml @@ -3,4 +3,3 @@ entity.field_inheritance.add_form: title: 'Add Field inheritance' appears_on: - entity.field_inheritance.collection - diff --git a/recurring_events.links.task.yml b/recurring_events.links.task.yml index 2b584d41..e5dbecb5 100644 --- a/recurring_events.links.task.yml +++ b/recurring_events.links.task.yml @@ -17,14 +17,21 @@ eventseries.delete_form: route_name: entity.eventseries.delete_form base_route: entity.eventseries.canonical title: Delete - weight: + weight: 10 + +# Event Series revisions link. +entity.eventseries.version_history: + route_name: entity.eventseries.version_history + base_route: entity.eventseries.canonical + title: 'Revisions' + weight: 11 # Event Series clone link. eventseries.clone_form: route_name: entity.eventseries.clone_form base_route: entity.eventseries.canonical title: Clone - weight: 11 + weight: 12 ### Event Instance local links. @@ -47,12 +54,19 @@ eventinstance.delete_form: title: Delete weight: 10 +# Event Instance revisions link. +entity.eventinstance.version_history: + route_name: entity.eventinstance.version_history + base_route: entity.eventinstance.canonical + title: 'Revisions' + weight: 11 + # Event Instance clone link. eventinstance.clone_form: route_name: entity.eventinstance.clone_form base_route: entity.eventinstance.canonical title: Clone - weight: 11 + weight: 12 # Event Series settings admin page. entity.eventseries.settings: diff --git a/recurring_events.permissions.yml b/recurring_events.permissions.yml index c01b3e97..e2be5ba2 100644 --- a/recurring_events.permissions.yml +++ b/recurring_events.permissions.yml @@ -23,6 +23,11 @@ delete own eventseries entity: clone eventseries entity: title: 'Clone eventseries entity' description: 'Clone existing event series entities' +view all eventseries revisions: + title: 'View all eventseries revisions' +revert all eventseries revisions: + title: 'Revert all eventseries revisions' + description: 'Role requires permission <em>view eventseries revisions</em> and <em>edit rights</em> for eventseries entities in question or <em>administer eventseries entities</em>.' administer eventseries entity: title: 'Administer eventseries entity' description: 'Make changes to the event series entity type.' @@ -50,6 +55,11 @@ delete own eventinstance entity: clone eventinstance entity: title: 'Clone eventinstance entity' description: 'Clone existing event instance entities' +view all eventinstance revisions: + title: 'View all eventinstance revisions' +revert all eventinstance revisions: + title: 'Revert all eventinstance revisions' + description: 'Role requires permission <em>view eventinstance revisions</em> and <em>edit rights</em> for eventinstance entities in question or <em>administer eventinstance entities</em>.' administer eventinstance entity: title: 'Administer eventinstance entity' description: 'Make changes to the event instance entity type.' diff --git a/recurring_events.routing.yml b/recurring_events.routing.yml index e923ec6a..b054bd01 100644 --- a/recurring_events.routing.yml +++ b/recurring_events.routing.yml @@ -147,7 +147,7 @@ events.admin.overview: path: '/admin/structure/events' defaults: _title: 'Events Management' - _controller: '\Drupal\recurring_events\Controller\EventController::adminPage' + _controller: '\Drupal\recurring_events\Controller\EventSeriesController::adminPage' requirements: _permission: 'access administration pages' @@ -156,7 +156,7 @@ events.admin.content: path: '/admin/content/events' defaults: _title: 'Events' - _controller: '\Drupal\recurring_events\Controller\EventController::contentPage' + _controller: '\Drupal\recurring_events\Controller\EventSeriesController::contentPage' requirements: _permission: 'access administration pages' diff --git a/src/Controller/EventController.php b/src/Controller/EventController.php deleted file mode 100644 index 3c71ae50..00000000 --- a/src/Controller/EventController.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -namespace Drupal\recurring_events\Controller; - -use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\Datetime\DateFormatterInterface; -use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Render\RendererInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; -use Drupal\system\SystemManager; - -/** - * The EventController class. - */ -class EventController extends ControllerBase implements ContainerInjectionInterface { - - /** - * The date formatter service. - * - * @var \Drupal\Core\Datetime\DateFormatterInterface - */ - protected $dateFormatter; - - /** - * The renderer service. - * - * @var \Drupal\Core\Render\RendererInterface - */ - protected $renderer; - - /** - * System Manager Service. - * - * @var \Drupal\system\SystemManager - */ - protected $systemManager; - - /** - * Constructs a EventController object. - * - * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter - * The date formatter service. - * @param \Drupal\Core\Render\RendererInterface $renderer - * The renderer service. - * @param \Drupal\system\SystemManager $systemManager - * System manager service. - */ - public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, SystemManager $systemManager) { - $this->dateFormatter = $date_formatter; - $this->renderer = $renderer; - $this->systemManager = $systemManager; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('date.formatter'), - $container->get('renderer'), - $container->get('system.manager') - ); - } - - /** - * The page callback for the admin overview page. - */ - public function adminPage() { - return $this->systemManager->getBlockContents(); - } - - /** - * The page callback for the admin content page. - */ - public function contentPage() { - return $this->systemManager->getBlockContents(); - } - -} diff --git a/src/Controller/EventInstanceController.php b/src/Controller/EventInstanceController.php new file mode 100644 index 00000000..e09e0971 --- /dev/null +++ b/src/Controller/EventInstanceController.php @@ -0,0 +1,222 @@ +<?php + +namespace Drupal\recurring_events\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Render\RendererInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\system\SystemManager; +use Drupal\recurring_events\EventInterface; +use Drupal\Component\Utility\Xss; +use Drupal\Core\Url; + +/** + * The EventInstanceController class. + */ +class EventInstanceController extends ControllerBase implements ContainerInjectionInterface { + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * System Manager Service. + * + * @var \Drupal\system\SystemManager + */ + protected $systemManager; + + /** + * Constructs a EventInstanceController object. + * + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + * @param \Drupal\system\SystemManager $systemManager + * System manager service. + */ + public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, SystemManager $systemManager) { + $this->dateFormatter = $date_formatter; + $this->renderer = $renderer; + $this->systemManager = $systemManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('date.formatter'), + $container->get('renderer'), + $container->get('system.manager') + ); + } + + /** + * Displays an eventinstance revision. + * + * @param int $eventinstance_revision + * The Default entity revision ID. + * + * @return array + * An array suitable for drupal_render(). + */ + public function revisionShow($eventinstance_revision) { + $eventinstance = $this->entityManager()->getStorage('eventinstance')->loadRevision($eventinstance_revision); + $view_builder = $this->entityManager()->getViewBuilder('eventinstance'); + + return $view_builder->view($eventinstance); + } + + /** + * Page title callback for an eventinstance revision. + * + * @param int $eventinstance_revision + * The Default entity revision ID. + * + * @return string + * The page title. + */ + public function revisionPageTitle($eventinstance_revision) { + $eventinstance = $this->entityManager()->getStorage('eventinstance')->loadRevision($eventinstance_revision); + return $this->t('Revision of %title from %date', ['%title' => $eventinstance->label(), '%date' => format_date($eventinstance->getRevisionCreationTime())]); + } + + /** + * Generates an overview table of older revisions of an eventinstance. + * + * @param \Drupal\recurring_events\EventInterface $eventinstance + * A Default entity object. + * + * @return array + * An array as expected by drupal_render(). + */ + public function revisionOverview(EventInterface $eventinstance) { + $account = $this->currentUser(); + $langcode = $eventinstance->language()->getId(); + $langname = $eventinstance->language()->getName(); + $languages = $eventinstance->getTranslationLanguages(); + $has_translations = (count($languages) > 1); + $eventinstance_storage = $this->entityManager()->getStorage('eventinstance'); + + $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $eventinstance->label()]) : $this->t('Revisions for %title', ['%title' => $eventinstance->label()]); + $header = [$this->t('Revision'), $this->t('Operations')]; + + $revert_permission = (($account->hasPermission("revert all eventinstance revisions") || $account->hasPermission('administer eventinstance entities'))); + $delete_permission = (($account->hasPermission("delete all eventinstance revisions") || $account->hasPermission('administer eventinstance entities'))); + + $rows = []; + + $vids = $eventinstance_storage->revisionIds($eventinstance); + + $latest_revision = TRUE; + + foreach (array_reverse($vids) as $vid) { + /** @var \Drupal\recurring_events\EventInterface $revision */ + $revision = $eventinstance_storage->loadRevision($vid); + // Only show revisions that are affected by the language that is being + // displayed. + if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { + $username = [ + '#theme' => 'username', + '#account' => $revision->getRevisionUser(), + ]; + + // Use revision link to link to revisions that are not active. + $date = \Drupal::service('date.formatter')->format($revision->getRevisionCreationTime(), 'short'); + if ($vid != $eventinstance->getRevisionId()) { + $link = $this->l($date, new Url('entity.eventinstance.revision', ['eventinstance' => $eventinstance->id(), 'eventinstance_revision' => $vid])); + } + else { + $link = $eventinstance->link($date); + } + + $row = []; + $column = [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', + '#context' => [ + 'date' => $link, + 'username' => \Drupal::service('renderer')->renderPlain($username), + 'message' => ['#markup' => $revision->getRevisionLogMessage(), '#allowed_tags' => Xss::getHtmlTagList()], + ], + ], + ]; + // @todo Simplify once https://www.drupal.org/node/2334319 lands. + $this->renderer->addCacheableDependency($column['data'], $username); + $row[] = $column; + + if ($latest_revision) { + $row[] = [ + 'data' => [ + '#prefix' => '<em>', + '#markup' => $this->t('Current revision'), + '#suffix' => '</em>', + ], + ]; + foreach ($row as &$current) { + $current['class'] = ['revision-current']; + } + $latest_revision = FALSE; + } + else { + $links = []; + if ($revert_permission) { + $links['revert'] = [ + 'title' => $this->t('Revert'), + 'url' => $has_translations ? + Url::fromRoute('entity.eventinstance.translation_revert', [ + 'eventinstance' => $eventinstance->id(), + 'eventinstance_revision' => $vid, + 'langcode' => $langcode, + ]) : + Url::fromRoute('entity.eventinstance.revision_revert', [ + 'eventinstance' => $eventinstance->id(), + 'eventinstance_revision' => $vid, + ]), + ]; + } + + if ($delete_permission) { + $links['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('entity.eventinstance.revision_delete', ['eventinstance' => $eventinstance->id(), 'eventinstance_revision' => $vid]), + ]; + } + + $row[] = [ + 'data' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; + } + + $rows[] = $row; + } + } + + $build['eventinstance_revisions_table'] = [ + '#theme' => 'table', + '#rows' => $rows, + '#header' => $header, + ]; + + return $build; + } + +} diff --git a/src/Controller/EventSeriesController.php b/src/Controller/EventSeriesController.php new file mode 100644 index 00000000..455f4623 --- /dev/null +++ b/src/Controller/EventSeriesController.php @@ -0,0 +1,236 @@ +<?php + +namespace Drupal\recurring_events\Controller; + +use Drupal\Core\Controller\ControllerBase; +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\Core\Render\RendererInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; +use Drupal\system\SystemManager; +use Drupal\recurring_events\EventInterface; +use Drupal\Component\Utility\Xss; +use Drupal\Core\Url; + +/** + * The EventSeriesController class. + */ +class EventSeriesController extends ControllerBase implements ContainerInjectionInterface { + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * System Manager Service. + * + * @var \Drupal\system\SystemManager + */ + protected $systemManager; + + /** + * Constructs a EventSeriesController object. + * + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + * @param \Drupal\system\SystemManager $systemManager + * System manager service. + */ + public function __construct(DateFormatterInterface $date_formatter, RendererInterface $renderer, SystemManager $systemManager) { + $this->dateFormatter = $date_formatter; + $this->renderer = $renderer; + $this->systemManager = $systemManager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('date.formatter'), + $container->get('renderer'), + $container->get('system.manager') + ); + } + + /** + * The page callback for the admin overview page. + */ + public function adminPage() { + return $this->systemManager->getBlockContents(); + } + + /** + * The page callback for the admin content page. + */ + public function contentPage() { + return $this->systemManager->getBlockContents(); + } + + /** + * Displays an eventseries revision. + * + * @param int $eventseries_revision + * The Default entity revision ID. + * + * @return array + * An array suitable for drupal_render(). + */ + public function revisionShow($eventseries_revision) { + $eventseries = $this->entityManager()->getStorage('eventseries')->loadRevision($eventseries_revision); + $view_builder = $this->entityManager()->getViewBuilder('eventseries'); + + return $view_builder->view($eventseries); + } + + /** + * Page title callback for an eventseries revision. + * + * @param int $eventseries_revision + * The Default entity revision ID. + * + * @return string + * The page title. + */ + public function revisionPageTitle($eventseries_revision) { + $eventseries = $this->entityManager()->getStorage('eventseries')->loadRevision($eventseries_revision); + return $this->t('Revision of %title from %date', ['%title' => $eventseries->label(), '%date' => format_date($eventseries->getRevisionCreationTime())]); + } + + /** + * Generates an overview table of older revisions of an eventseries. + * + * @param \Drupal\recurring_events\EventInterface $eventseries + * A Default entity object. + * + * @return array + * An array as expected by drupal_render(). + */ + public function revisionOverview(EventInterface $eventseries) { + $account = $this->currentUser(); + $langcode = $eventseries->language()->getId(); + $langname = $eventseries->language()->getName(); + $languages = $eventseries->getTranslationLanguages(); + $has_translations = (count($languages) > 1); + $eventseries_storage = $this->entityManager()->getStorage('eventseries'); + + $build['#title'] = $has_translations ? $this->t('@langname revisions for %title', ['@langname' => $langname, '%title' => $eventseries->label()]) : $this->t('Revisions for %title', ['%title' => $eventseries->label()]); + $header = [$this->t('Revision'), $this->t('Operations')]; + + $revert_permission = (($account->hasPermission("revert all eventseries revisions") || $account->hasPermission('administer eventseries entities'))); + $delete_permission = (($account->hasPermission("delete all eventseries revisions") || $account->hasPermission('administer eventseries entities'))); + + $rows = []; + + $vids = $eventseries_storage->revisionIds($eventseries); + + $latest_revision = TRUE; + + foreach (array_reverse($vids) as $vid) { + /** @var \Drupal\recurring_events\EventInterface $revision */ + $revision = $eventseries_storage->loadRevision($vid); + // Only show revisions that are affected by the language that is being + // displayed. + if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { + $username = [ + '#theme' => 'username', + '#account' => $revision->getRevisionUser(), + ]; + + // Use revision link to link to revisions that are not active. + $date = \Drupal::service('date.formatter')->format($revision->getRevisionCreationTime(), 'short'); + if ($vid != $eventseries->getRevisionId()) { + $link = $this->l($date, new Url('entity.eventseries.revision', ['eventseries' => $eventseries->id(), 'eventseries_revision' => $vid])); + } + else { + $link = $eventseries->link($date); + } + + $row = []; + $column = [ + 'data' => [ + '#type' => 'inline_template', + '#template' => '{% trans %}{{ date }} by {{ username }}{% endtrans %}{% if message %}<p class="revision-log">{{ message }}</p>{% endif %}', + '#context' => [ + 'date' => $link, + 'username' => \Drupal::service('renderer')->renderPlain($username), + 'message' => ['#markup' => $revision->getRevisionLogMessage(), '#allowed_tags' => Xss::getHtmlTagList()], + ], + ], + ]; + // @todo Simplify once https://www.drupal.org/node/2334319 lands. + $this->renderer->addCacheableDependency($column['data'], $username); + $row[] = $column; + + if ($latest_revision) { + $row[] = [ + 'data' => [ + '#prefix' => '<em>', + '#markup' => $this->t('Current revision'), + '#suffix' => '</em>', + ], + ]; + foreach ($row as &$current) { + $current['class'] = ['revision-current']; + } + $latest_revision = FALSE; + } + else { + $links = []; + if ($revert_permission) { + $links['revert'] = [ + 'title' => $this->t('Revert'), + 'url' => $has_translations ? + Url::fromRoute('entity.eventseries.translation_revert', [ + 'eventseries' => $eventseries->id(), + 'eventseries_revision' => $vid, + 'langcode' => $langcode, + ]) : + Url::fromRoute('entity.eventseries.revision_revert', [ + 'eventseries' => $eventseries->id(), + 'eventseries_revision' => $vid, + ]), + ]; + } + + if ($delete_permission) { + $links['delete'] = [ + 'title' => $this->t('Delete'), + 'url' => Url::fromRoute('entity.eventseries.revision_delete', ['eventseries' => $eventseries->id(), 'eventseries_revision' => $vid]), + ]; + } + + $row[] = [ + 'data' => [ + '#type' => 'operations', + '#links' => $links, + ], + ]; + } + + $rows[] = $row; + } + } + + $build['eventseries_revisions_table'] = [ + '#theme' => 'table', + '#rows' => $rows, + '#header' => $header, + ]; + + return $build; + } + +} diff --git a/src/Entity/EventInstance.php b/src/Entity/EventInstance.php index 57e74c06..cdb17a97 100644 --- a/src/Entity/EventInstance.php +++ b/src/Entity/EventInstance.php @@ -72,10 +72,11 @@ use Drupal\user\UserInterface; * id = "eventinstance", * label = @Translation("Event Instance entity"), * handlers = { - * "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "storage" = "Drupal\recurring_events\EventInstanceStorage", * "list_builder" = "Drupal\recurring_events\EventInstanceListBuilder", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "views_data" = "Drupal\views\EntityViewsData", + * "translation" = "Drupal\recurring_events\EventInstanceTranslationHandler", * "form" = { * "edit" = "Drupal\recurring_events\Form\EventInstanceForm", * "delete" = "Drupal\recurring_events\Form\EventInstanceDeleteForm", @@ -83,6 +84,9 @@ use Drupal\user\UserInterface; * "clone" = "Drupal\recurring_events\Form\EventInstanceCloneForm", * }, * "access" = "Drupal\recurring_events\EventInstanceAccessControlHandler", + * "route_provider" = { + * "html" = "Drupal\recurring_events\EventInstanceHtmlRouteProvider", + * }, * }, * base_table = "eventinstance", * data_table = "eventinstance_field_data", @@ -109,9 +113,12 @@ use Drupal\user\UserInterface; * "edit-form" = "/events/{eventinstance}/edit", * "delete-form" = "/events/{eventinstance}/delete", * "collection" = "/admin/content/events/instances", + * "clone-form" = "/events/{eventinstance}/clone", * "version-history" = "/events/{eventinstance}/revisions", * "revision" = "/events/{eventinstance}/revisions/{eventinstance_revision}/view", - * "clone-form" = "/events/{eventinstance}/clone", + * "revision_revert" = "/events/{eventinstance}/revisions/{eventinstance_revision}/revert", + * "revision_delete" = "/events/{eventinstance}/revisions/{eventinstance_revision}/delete", + * "translation_revert" = "/events/{eventinstance}/revisions/{eventinstance_revision}/revert/{langcode}", * }, * field_ui_base_route = "eventinstance.settings", * ) @@ -131,7 +138,7 @@ use Drupal\user\UserInterface; * we want. In our case we want to provide access to the standard fields about * creation and changed time stamps. * - * Our interface (see EventSeriesInterface) also exposes the + * Our interface (see EventInterface) also exposes the * EntityOwnerInterface. This allows us to provide methods for setting * and providing ownership information. * @@ -156,6 +163,22 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface ]; } + /** + * {@inheritdoc} + */ + protected function urlRouteParameters($rel) { + $uri_route_parameters = parent::urlRouteParameters($rel); + + if ($rel === 'revision_revert' && $this instanceof RevisionableInterface) { + $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); + } + elseif ($rel === 'revision_delete' && $this instanceof RevisionableInterface) { + $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); + } + + return $uri_route_parameters; + } + /** * {@inheritdoc} */ @@ -178,36 +201,6 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface } } - /** - * {@inheritdoc} - */ - public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { - parent::preSaveRevision($storage, $record); - - if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) { - // If we are updating an existing eventinstance without adding a new - // revision, we need to make sure $entity->revision_log is reset whenever - // it is empty. Therefore, this code allows us to avoid clobbering an - // existing log entry with an empty one. - $record->revision_log = $this->original->revision_log->value; - } - } - - /** - * {@inheritdoc} - */ - public function getRevisionAuthor() { - return $this->getRevisionUser(); - } - - /** - * {@inheritdoc} - */ - public function setRevisionAuthorId($uid) { - $this->setRevisionUserId($uid); - return $this; - } - /** * {@inheritdoc} */ @@ -230,19 +223,6 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface return $this; } - /** - * {@inheritdoc} - */ - public function getChangedTimeAcrossTranslations() { - $changed = $this->getUntranslated()->getChangedTime(); - foreach ($this->getTranslationLanguages(FALSE) as $language) { - $translation_changed = $this->getTranslation($language->getId()) - ->getChangedTime(); - $changed = max($translation_changed, $changed); - } - return $changed; - } - /** * {@inheritdoc} */ @@ -273,6 +253,21 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface return $this; } + /** + * {@inheritdoc} + */ + public function getRevisionAuthor() { + return $this->getRevisionUser(); + } + + /** + * {@inheritdoc} + */ + public function setRevisionAuthorId($uid) { + $this->setRevisionUserId($uid); + return $this; + } + /** * {@inheritdoc} * @@ -379,6 +374,13 @@ class EventInstance extends EditorialContentEntityBase implements EventInterface ]) ->setDisplayConfigurable('form', TRUE); + $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Revision translation affected')) + ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) + ->setReadOnly(TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(TRUE); + return $fields; } diff --git a/src/Entity/EventSeries.php b/src/Entity/EventSeries.php index bffe20a8..e9ac8857 100644 --- a/src/Entity/EventSeries.php +++ b/src/Entity/EventSeries.php @@ -72,10 +72,11 @@ use Drupal\user\UserInterface; * id = "eventseries", * label = @Translation("Event series entity"), * handlers = { - * "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "storage" = "Drupal\recurring_events\EventSeriesStorage", * "list_builder" = "Drupal\recurring_events\EventSeriesListBuilder", * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "views_data" = "Drupal\views\EntityViewsData", + * "translation" = "Drupal\recurring_events\EventSeriesTranslationHandler", * "form" = { * "add" = "Drupal\recurring_events\Form\EventSeriesForm", * "edit" = "Drupal\recurring_events\Form\EventSeriesForm", @@ -83,6 +84,9 @@ use Drupal\user\UserInterface; * "clone" = "Drupal\recurring_events\Form\EventSeriesCloneForm", * }, * "access" = "Drupal\recurring_events\EventSeriesAccessControlHandler", + * "route_provider" = { + * "html" = "Drupal\recurring_events\EventSeriesHtmlRouteProvider", + * }, * }, * base_table = "eventseries", * data_table = "eventseries_field_data", @@ -110,9 +114,12 @@ use Drupal\user\UserInterface; * "edit-form" = "/events/series/{eventseries}/edit", * "delete-form" = "/events/series/{eventseries}/delete", * "collection" = "/admin/content/events/series", + * "clone-form" = "/events/series/{eventseries}/clone", * "version-history" = "/events/series/{eventseries}/revisions", * "revision" = "/events/series/{eventseries}/revisions/{eventseries_revision}/view", - * "clone-form" = "/events/series/{eventseries}/clone", + * "revision_revert" = "/events/series/{eventseries}/revisions/{eventseries_revision}/revert", + * "revision_delete" = "/events/series/{eventseries}/revisions/{eventseries_revision}/delete", + * "translation_revert" = "/events/series/{eventseries}/revisions/{eventseries_revision}/revert/{langcode}", * }, * field_ui_base_route = "eventseries.settings", * ) @@ -131,7 +138,7 @@ use Drupal\user\UserInterface; * we want. In our case we want to provide access to the standard fields about * creation and changed time stamps. * - * Our interface (see EventSeriesInterface) also exposes the + * Our interface (see EventInterface) also exposes the * EntityOwnerInterface. This allows us to provide methods for setting * and providing ownership information. * @@ -156,6 +163,22 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ]; } + /** + * {@inheritdoc} + */ + protected function urlRouteParameters($rel) { + $uri_route_parameters = parent::urlRouteParameters($rel); + + if ($rel === 'revision_revert' && $this instanceof RevisionableInterface) { + $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); + } + elseif ($rel === 'revision_delete' && $this instanceof RevisionableInterface) { + $uri_route_parameters[$this->getEntityTypeId() . '_revision'] = $this->getRevisionId(); + } + + return $uri_route_parameters; + } + /** * {@inheritdoc} */ @@ -178,36 +201,6 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { } } - /** - * {@inheritdoc} - */ - public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { - parent::preSaveRevision($storage, $record); - - if (!$this->isNewRevision() && isset($this->original) && (!isset($record->revision_log) || $record->revision_log === '')) { - // If we are updating an existing event without adding a new - // revision, we need to make sure $entity->revision_log is reset whenever - // it is empty. Therefore, this code allows us to avoid clobbering an - // existing log entry with an empty one. - $record->revision_log = $this->original->revision_log->value; - } - } - - /** - * {@inheritdoc} - */ - public function getRevisionAuthor() { - return $this->getRevisionUser(); - } - - /** - * {@inheritdoc} - */ - public function setRevisionAuthorId($uid) { - $this->setRevisionUserId($uid); - return $this; - } - /** * {@inheritdoc} */ @@ -230,19 +223,6 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { return $this; } - /** - * {@inheritdoc} - */ - public function getChangedTimeAcrossTranslations() { - $changed = $this->getUntranslated()->getChangedTime(); - foreach ($this->getTranslationLanguages(FALSE) as $language) { - $translation_changed = $this->getTranslation($language->getId()) - ->getChangedTime(); - $changed = max($translation_changed, $changed); - } - return $changed; - } - /** * {@inheritdoc} */ @@ -273,6 +253,21 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { return $this; } + /** + * {@inheritdoc} + */ + public function getRevisionAuthor() { + return $this->getRevisionUser(); + } + + /** + * {@inheritdoc} + */ + public function setRevisionAuthorId($uid) { + $this->setRevisionUserId($uid); + return $this; + } + /** * {@inheritdoc} * @@ -469,6 +464,13 @@ class EventSeries extends EditorialContentEntityBase implements EventInterface { ]) ->setDisplayConfigurable('form', TRUE); + $fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean') + ->setLabel(t('Revision translation affected')) + ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.')) + ->setReadOnly(TRUE) + ->setRevisionable(TRUE) + ->setTranslatable(TRUE); + return $fields; } diff --git a/src/EventInstanceHtmlRouteProvider.php b/src/EventInstanceHtmlRouteProvider.php new file mode 100644 index 00000000..af1ceaf8 --- /dev/null +++ b/src/EventInstanceHtmlRouteProvider.php @@ -0,0 +1,163 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider; +use Symfony\Component\Routing\Route; + +/** + * Provides routes for eventinstance entities. + * + * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider + * @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider + */ +class EventInstanceHtmlRouteProvider extends AdminHtmlRouteProvider { + + /** + * {@inheritdoc} + */ + public function getRoutes(EntityTypeInterface $entity_type) { + $collection = parent::getRoutes($entity_type); + + $entity_type_id = $entity_type->id(); + + if ($history_route = $this->getHistoryRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.version_history", $history_route); + } + + if ($revision_route = $this->getRevisionRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision", $revision_route); + } + + if ($revert_route = $this->getRevisionRevertRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision_revert", $revert_route); + } + + if ($delete_route = $this->getRevisionDeleteRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision_delete", $delete_route); + } + + if ($translation_route = $this->getRevisionTranslationRevertRoute($entity_type)) { + $collection->add("{$entity_type_id}.revision_revert_translation_confirm", $translation_route); + } + + return $collection; + } + + /** + * Gets the version history route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getHistoryRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('version-history')) { + $route = new Route($entity_type->getLinkTemplate('version-history')); + $route + ->setDefaults([ + '_title' => "{$entity_type->getLabel()} revisions", + '_controller' => '\Drupal\recurring_events\Controller\EventInstanceController::revisionOverview', + ]) + ->setRequirement('_permission', 'access eventinstance revisions'); + + return $route; + } + } + + /** + * Gets the revision route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getRevisionRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('revision')) { + $route = new Route($entity_type->getLinkTemplate('revision')); + $route + ->setDefaults([ + '_controller' => '\Drupal\recurring_events\Controller\EventInstanceController::revisionShow', + '_title_callback' => '\Drupal\recurring_events\Controller\EventInstanceController::revisionPageTitle', + ]) + ->setRequirement('_permission', 'access eventinstance revisions'); + + return $route; + } + } + + /** + * Gets the 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')) { + $route = new Route($entity_type->getLinkTemplate('revision_revert')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventInstanceRevisionRevertForm', + '_title' => 'Revert to earlier revision', + ]) + ->setRequirement('_permission', 'revert all eventinstance revisions'); + + return $route; + } + } + + /** + * Gets the revision delete route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getRevisionDeleteRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('revision_delete')) { + $route = new Route($entity_type->getLinkTemplate('revision_delete')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventInstanceRevisionDeleteForm', + '_title' => 'Delete earlier revision', + ]) + ->setRequirement('_permission', 'delete all eventinstance revisions'); + + return $route; + } + } + + /** + * Gets the revision translation 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 getRevisionTranslationRevertRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('translation_revert')) { + $route = new Route($entity_type->getLinkTemplate('translation_revert')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventInstanceRevisionRevertTranslationForm', + '_title' => 'Revert to earlier revision of a translation', + ]) + ->setRequirement('_permission', 'revert all eventinstance revisions'); + + return $route; + } + } + +} diff --git a/src/EventInstanceStorage.php b/src/EventInstanceStorage.php new file mode 100644 index 00000000..37abf099 --- /dev/null +++ b/src/EventInstanceStorage.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\Sql\SqlContentEntityStorage; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\recurring_events\EventInterface; + +/** + * Defines the storage handler class for eventinstance entities. + * + * This extends the base storage class, adding required special handling for + * eventinstance entities. + * + * @ingroup recurring_events + */ +class EventInstanceStorage extends SqlContentEntityStorage implements EventInstanceStorageInterface { + + /** + * {@inheritdoc} + */ + public function revisionIds(EventInterface $entity) { + return $this->database->query( + 'SELECT vid FROM {eventinstance_revision} WHERE id=:id ORDER BY vid', + [':id' => $entity->id()] + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function userRevisionIds(AccountInterface $account) { + return $this->database->query( + 'SELECT vid FROM {eventinstance_field_revision} WHERE uid = :uid ORDER BY vid', + [':uid' => $account->id()] + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function countDefaultLanguageRevisions(EventInterface $entity) { + return $this->database->query('SELECT COUNT(*) FROM {eventinstance_field_revision} WHERE id = :id AND default_langcode = 1', [':id' => $entity->id()]) + ->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function clearRevisionsLanguage(LanguageInterface $language) { + return $this->database->update('eventinstance_revision') + ->fields(['langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]) + ->condition('langcode', $language->getId()) + ->execute(); + } + +} diff --git a/src/EventInstanceStorageInterface.php b/src/EventInstanceStorageInterface.php new file mode 100644 index 00000000..2a6e586e --- /dev/null +++ b/src/EventInstanceStorageInterface.php @@ -0,0 +1,61 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\ContentEntityStorageInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\recurring_events\EventInterface; + +/** + * Defines the storage handler class for eventinstance entities. + * + * This extends the base storage class, adding required special handling for + * eventinstance entities. + * + * @ingroup recurring_events + */ +interface EventInstanceStorageInterface extends ContentEntityStorageInterface { + + /** + * Gets a list of eventinstance revision IDs for a specific eventinstance. + * + * @param \Drupal\recurring_events\EventInterface $entity + * The eventinstance entity. + * + * @return int[] + * Eventinstance revision IDs (in ascending order). + */ + public function revisionIds(EventInterface $entity); + + /** + * Gets a list of revision IDs having a given user as eventinstance author. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user entity. + * + * @return int[] + * Eventinstance revision IDs (in ascending order). + */ + public function userRevisionIds(AccountInterface $account); + + /** + * Counts the number of revisions in the default language. + * + * @param \Drupal\recurring_events\EventInterface $entity + * The eventinstance entity. + * + * @return int + * The number of revisions in the default language. + */ + public function countDefaultLanguageRevisions(EventInterface $entity); + + /** + * Unsets the language for all eventinstance with the given language. + * + * @param \Drupal\Core\Language\LanguageInterface $language + * The language object. + */ + public function clearRevisionsLanguage(LanguageInterface $language); + +} diff --git a/src/EventInstanceTranslationHandler.php b/src/EventInstanceTranslationHandler.php new file mode 100644 index 00000000..ce1f6810 --- /dev/null +++ b/src/EventInstanceTranslationHandler.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\content_translation\ContentTranslationHandler; + +/** + * Defines the translation handler for eventinstance. + */ +class EventInstanceTranslationHandler extends ContentTranslationHandler { + + // Override here the needed methods from ContentTranslationHandler. + +} diff --git a/src/EventSeriesHtmlRouteProvider.php b/src/EventSeriesHtmlRouteProvider.php new file mode 100644 index 00000000..6ad9d61b --- /dev/null +++ b/src/EventSeriesHtmlRouteProvider.php @@ -0,0 +1,163 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\EntityTypeInterface; +use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider; +use Symfony\Component\Routing\Route; + +/** + * Provides routes for eventseries entities. + * + * @see \Drupal\Core\Entity\Routing\AdminHtmlRouteProvider + * @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider + */ +class EventSeriesHtmlRouteProvider extends AdminHtmlRouteProvider { + + /** + * {@inheritdoc} + */ + public function getRoutes(EntityTypeInterface $entity_type) { + $collection = parent::getRoutes($entity_type); + + $entity_type_id = $entity_type->id(); + + if ($history_route = $this->getHistoryRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.version_history", $history_route); + } + + if ($revision_route = $this->getRevisionRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision", $revision_route); + } + + if ($revert_route = $this->getRevisionRevertRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision_revert", $revert_route); + } + + if ($delete_route = $this->getRevisionDeleteRoute($entity_type)) { + $collection->add("entity.{$entity_type_id}.revision_delete", $delete_route); + } + + if ($translation_route = $this->getRevisionTranslationRevertRoute($entity_type)) { + $collection->add("{$entity_type_id}.revision_revert_translation_confirm", $translation_route); + } + + return $collection; + } + + /** + * Gets the version history route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getHistoryRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('version-history')) { + $route = new Route($entity_type->getLinkTemplate('version-history')); + $route + ->setDefaults([ + '_title' => "{$entity_type->getLabel()} revisions", + '_controller' => '\Drupal\recurring_events\Controller\EventSeriesController::revisionOverview', + ]) + ->setRequirement('_permission', 'access eventseries revisions'); + + return $route; + } + } + + /** + * Gets the revision route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getRevisionRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('revision')) { + $route = new Route($entity_type->getLinkTemplate('revision')); + $route + ->setDefaults([ + '_controller' => '\Drupal\recurring_events\Controller\EventSeriesController::revisionShow', + '_title_callback' => '\Drupal\recurring_events\Controller\EventSeriesController::revisionPageTitle', + ]) + ->setRequirement('_permission', 'access eventseries revisions'); + + return $route; + } + } + + /** + * Gets the 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')) { + $route = new Route($entity_type->getLinkTemplate('revision_revert')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventSeriesRevisionRevertForm', + '_title' => 'Revert to earlier revision', + ]) + ->setRequirement('_permission', 'revert all eventseries revisions'); + + return $route; + } + } + + /** + * Gets the revision delete route. + * + * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type + * The entity type. + * + * @return \Symfony\Component\Routing\Route|null + * The generated route, if available. + */ + protected function getRevisionDeleteRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('revision_delete')) { + $route = new Route($entity_type->getLinkTemplate('revision_delete')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventSeriesRevisionDeleteForm', + '_title' => 'Delete earlier revision', + ]) + ->setRequirement('_permission', 'delete all eventseries revisions'); + + return $route; + } + } + + /** + * Gets the revision translation 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 getRevisionTranslationRevertRoute(EntityTypeInterface $entity_type) { + if ($entity_type->hasLinkTemplate('translation_revert')) { + $route = new Route($entity_type->getLinkTemplate('translation_revert')); + $route + ->setDefaults([ + '_form' => '\Drupal\recurring_events\Form\EventSeriesRevisionRevertTranslationForm', + '_title' => 'Revert to earlier revision of a translation', + ]) + ->setRequirement('_permission', 'revert all eventseries revisions'); + + return $route; + } + } + +} diff --git a/src/EventSeriesStorage.php b/src/EventSeriesStorage.php new file mode 100644 index 00000000..76f60bb8 --- /dev/null +++ b/src/EventSeriesStorage.php @@ -0,0 +1,58 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\Sql\SqlContentEntityStorage; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\recurring_events\EventInterface; + +/** + * Defines the storage handler class for eventseries entities. + * + * This extends the base storage class, adding required special handling for + * eventseries entities. + * + * @ingroup recurring_events + */ +class EventSeriesStorage extends SqlContentEntityStorage implements EventSeriesStorageInterface { + + /** + * {@inheritdoc} + */ + public function revisionIds(EventInterface $entity) { + return $this->database->query( + 'SELECT vid FROM {eventseries_revision} WHERE id=:id ORDER BY vid', + [':id' => $entity->id()] + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function userRevisionIds(AccountInterface $account) { + return $this->database->query( + 'SELECT vid FROM {eventseries_field_revision} WHERE uid = :uid ORDER BY vid', + [':uid' => $account->id()] + )->fetchCol(); + } + + /** + * {@inheritdoc} + */ + public function countDefaultLanguageRevisions(EventInterface $entity) { + return $this->database->query('SELECT COUNT(*) FROM {eventseries_field_revision} WHERE id = :id AND default_langcode = 1', [':id' => $entity->id()]) + ->fetchField(); + } + + /** + * {@inheritdoc} + */ + public function clearRevisionsLanguage(LanguageInterface $language) { + return $this->database->update('eventseries_revision') + ->fields(['langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED]) + ->condition('langcode', $language->getId()) + ->execute(); + } + +} diff --git a/src/EventSeriesStorageInterface.php b/src/EventSeriesStorageInterface.php new file mode 100644 index 00000000..4d05919b --- /dev/null +++ b/src/EventSeriesStorageInterface.php @@ -0,0 +1,61 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\Core\Entity\ContentEntityStorageInterface; +use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Language\LanguageInterface; +use Drupal\recurring_events\EventInterface; + +/** + * Defines the storage handler class for eventseries entities. + * + * This extends the base storage class, adding required special handling for + * eventseries entities. + * + * @ingroup recurring_events + */ +interface EventSeriesStorageInterface extends ContentEntityStorageInterface { + + /** + * Gets a list of eventseries revision IDs for a specific eventseries. + * + * @param \Drupal\recurring_events\EventInterface $entity + * The eventseries entity. + * + * @return int[] + * Eventseries revision IDs (in ascending order). + */ + public function revisionIds(EventInterface $entity); + + /** + * Gets a list of revision IDs having a given user as eventseries author. + * + * @param \Drupal\Core\Session\AccountInterface $account + * The user entity. + * + * @return int[] + * Eventseries revision IDs (in ascending order). + */ + public function userRevisionIds(AccountInterface $account); + + /** + * Counts the number of revisions in the default language. + * + * @param \Drupal\recurring_events\EventInterface $entity + * The eventseries entity. + * + * @return int + * The number of revisions in the default language. + */ + public function countDefaultLanguageRevisions(EventInterface $entity); + + /** + * Unsets the language for all eventseries with the given language. + * + * @param \Drupal\Core\Language\LanguageInterface $language + * The language object. + */ + public function clearRevisionsLanguage(LanguageInterface $language); + +} diff --git a/src/EventSeriesTranslationHandler.php b/src/EventSeriesTranslationHandler.php new file mode 100644 index 00000000..f2e1d817 --- /dev/null +++ b/src/EventSeriesTranslationHandler.php @@ -0,0 +1,14 @@ +<?php + +namespace Drupal\recurring_events; + +use Drupal\content_translation\ContentTranslationHandler; + +/** + * Defines the translation handler for eventseries. + */ +class EventSeriesTranslationHandler extends ContentTranslationHandler { + + // Override here the needed methods from ContentTranslationHandler. + +} diff --git a/src/Form/EventInstanceForm.php b/src/Form/EventInstanceForm.php index e161317a..e4894729 100644 --- a/src/Form/EventInstanceForm.php +++ b/src/Form/EventInstanceForm.php @@ -33,7 +33,7 @@ class EventInstanceForm extends ContentEntityForm { } /** - * Construct a EventSeriesForm. + * Construct an EventInstanceForm. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. @@ -89,11 +89,22 @@ class EventInstanceForm extends ContentEntityForm { * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { - $form_state->setRedirect('entity.eventinstance.collection'); - parent::save($form, $form_state); - $entity = $this->getEntity(); + // Save as a new revision if requested to do so. + if (!$form_state->isValueEmpty('new_revision') && $form_state->getValue('new_revision') != FALSE) { + $entity->setNewRevision(); + + // If a new revision is created, save the current user as revision author. + $entity->setRevisionCreationTime(REQUEST_TIME); + $entity->setRevisionUserId(\Drupal::currentUser()->id()); + } + else { + $entity->setNewRevision(FALSE); + } + + parent::save($form, $form_state); + if ($entity->isDefaultTranslation()) { $message = t('Event instance of %label has been saved.', [ '%label' => $entity->getEventSeries()->title->value, @@ -106,6 +117,8 @@ class EventInstanceForm extends ContentEntityForm { ]); } $this->messenger->addMessage($message); + + $form_state->setRedirect('entity.eventinstance.canonical', ['eventinstance' => $entity->id()]); } } diff --git a/src/Form/EventInstanceRevisionDeleteForm.php b/src/Form/EventInstanceRevisionDeleteForm.php new file mode 100644 index 00000000..00cd43d8 --- /dev/null +++ b/src/Form/EventInstanceRevisionDeleteForm.php @@ -0,0 +1,122 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a form for deleting a eventinstance revision. + * + * @ingroup recurring_events + */ +class EventInstanceRevisionDeleteForm extends ConfirmFormBase { + + /** + * The eventinstance revision. + * + * @var \Drupal\recurring_events\EventInterface + */ + protected $revision; + + /** + * The eventinstance storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $eventInstanceStorage; + + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $connection; + + /** + * Constructs a new EventInstanceRevisionDeleteForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The entity storage. + * @param \Drupal\Core\Database\Connection $connection + * The database connection. + */ + public function __construct(EntityStorageInterface $entity_storage, Connection $connection) { + $this->eventInstanceStorage = $entity_storage; + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + $entity_manager = $container->get('entity.manager'); + return new static( + $entity_manager->getStorage('eventinstance'), + $container->get('database') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventinstance_revision_delete_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to delete the revision from %revision-date?', ['%revision-date' => format_date($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.eventinstance.version_history', ['eventinstance' => $this->revision->id()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventinstance_revision = NULL) { + $this->revision = $this->eventInstanceStorage->loadRevision($eventinstance_revision); + $form = parent::buildForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->eventInstanceStorage->deleteRevision($this->revision->getRevisionId()); + + $this->logger('content')->notice('eventinstance: deleted %title revision %revision.', ['%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]); + drupal_set_message(t('Revision from %revision-date of eventinstance %title has been deleted.', ['%revision-date' => format_date($this->revision->getRevisionCreationTime()), '%title' => $this->revision->label()])); + $form_state->setRedirect( + 'entity.eventinstance.canonical', + ['eventinstance' => $this->revision->id()] + ); + if ($this->connection->query('SELECT COUNT(DISTINCT vid) FROM {eventinstance_field_revision} WHERE id = :id', [':id' => $this->revision->id()])->fetchField() > 1) { + $form_state->setRedirect( + 'entity.eventinstance.version_history', + ['eventinstance' => $this->revision->id()] + ); + } + } + +} diff --git a/src/Form/EventInstanceRevisionRevertForm.php b/src/Form/EventInstanceRevisionRevertForm.php new file mode 100644 index 00000000..605cc4e5 --- /dev/null +++ b/src/Form/EventInstanceRevisionRevertForm.php @@ -0,0 +1,149 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\recurring_events\EventInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a form for reverting an eventinstance revision. + * + * @ingroup recurring_events + */ +class EventInstanceRevisionRevertForm extends ConfirmFormBase { + + + /** + * The eventinstance revision. + * + * @var \Drupal\recurring_events\EventInterface + */ + protected $revision; + + /** + * The eventinstance storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $eventSeriesStorage; + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * Constructs a new EventInstanceRevisionRevertForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The eventinstance storage. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + */ + public function __construct(EntityStorageInterface $entity_storage, DateFormatterInterface $date_formatter) { + $this->eventSeriesStorage = $entity_storage; + $this->dateFormatter = $date_formatter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('eventinstance'), + $container->get('date.formatter') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventinstance_revision_revert_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.eventinstance.version_history', ['eventinstance' => $this->revision->id()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Revert'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventinstance_revision = NULL) { + $this->revision = $this->eventSeriesStorage->loadRevision($eventinstance_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. + $original_revision_timestamp = $this->revision->getRevisionCreationTime(); + + $this->revision = $this->prepareRevertedRevision($this->revision, $form_state); + $this->revision->revision_log = t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]); + $this->revision->save(); + + $this->logger('content')->notice('eventinstance: reverted %title revision %revision.', ['%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]); + drupal_set_message(t('eventinstance %title has been reverted to the revision from %revision-date.', ['%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)])); + $form_state->setRedirect( + 'entity.eventinstance.version_history', + ['eventinstance' => $this->revision->id()] + ); + } + + /** + * Prepares a revision to be reverted. + * + * @param \Drupal\recurring_events\EventInterface $revision + * The revision to be reverted. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\recurring_events\EventInterface + * The prepared revision ready to be stored. + */ + protected function prepareRevertedRevision(EventInterface $revision, FormStateInterface $form_state) { + $revision->setNewRevision(); + $revision->isDefaultRevision(TRUE); + $revision->setRevisionCreationTime(REQUEST_TIME); + + return $revision; + } + +} diff --git a/src/Form/EventInstanceRevisionRevertTranslationForm.php b/src/Form/EventInstanceRevisionRevertTranslationForm.php new file mode 100644 index 00000000..3df7c5bb --- /dev/null +++ b/src/Form/EventInstanceRevisionRevertTranslationForm.php @@ -0,0 +1,115 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\recurring_events\EventInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides form to revert an eventinstance revision for a single translation. + * + * @ingroup recurring_events + */ +class EventInstanceRevisionRevertTranslationForm extends EventInstanceRevisionRevertForm { + + + /** + * The language to be reverted. + * + * @var string + */ + protected $langcode; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * Constructs a new EventInstanceRevisionRevertTranslationForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The eventinstance storage. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function __construct(EntityStorageInterface $entity_storage, DateFormatterInterface $date_formatter, LanguageManagerInterface $language_manager) { + parent::__construct($entity_storage, $date_formatter); + $this->languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('eventinstance'), + $container->get('date.formatter'), + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventinstance_revision_revert_translation_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to revert @language translation to the revision from %revision-date?', ['@language' => $this->languageManager->getLanguageName($this->langcode), '%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventinstance_revision = NULL, $langcode = NULL) { + $this->langcode = $langcode; + $form = parent::buildForm($form, $form_state, $eventinstance_revision); + + $form['revert_untranslated_fields'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Revert content shared among translations'), + '#default_value' => FALSE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareRevertedRevision(EventInterface $revision, FormStateInterface $form_state) { + $revert_untranslated_fields = $form_state->getValue('revert_untranslated_fields'); + + /** @var \Drupal\recurring_events\EventInterface $default_revision */ + $latest_revision = $this->eventSeriesStorage->load($revision->id()); + $latest_revision_translation = $latest_revision->getTranslation($this->langcode); + + $revision_translation = $revision->getTranslation($this->langcode); + + foreach ($latest_revision_translation->getFieldDefinitions() as $field_name => $definition) { + if ($definition->isTranslatable() || $revert_untranslated_fields) { + $latest_revision_translation->set($field_name, $revision_translation->get($field_name)->getValue()); + } + } + + $latest_revision_translation->setNewRevision(); + $latest_revision_translation->isDefaultRevision(TRUE); + $revision->setRevisionCreationTime(REQUEST_TIME); + + return $latest_revision_translation; + } + +} diff --git a/src/Form/EventSeriesDeleteForm.php b/src/Form/EventSeriesDeleteForm.php index b4ee5425..9589033e 100644 --- a/src/Form/EventSeriesDeleteForm.php +++ b/src/Form/EventSeriesDeleteForm.php @@ -49,7 +49,7 @@ class EventSeriesDeleteForm extends ContentEntityDeleteForm { } /** - * Construct a EventSeriesDeleteForm. + * Construct an EventSeriesDeleteForm. * * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager * The entity manager service. diff --git a/src/Form/EventSeriesForm.php b/src/Form/EventSeriesForm.php index f7f3637a..d266d4b7 100644 --- a/src/Form/EventSeriesForm.php +++ b/src/Form/EventSeriesForm.php @@ -67,7 +67,7 @@ class EventSeriesForm extends ContentEntityForm { } /** - * Construct a EventSeriesForm. + * Construct an EventSeriesForm. * * @param \Drupal\recurring_events\EventCreationService $creation_service * The event creation service. @@ -211,10 +211,21 @@ class EventSeriesForm extends ContentEntityForm { * {@inheritdoc} */ public function save(array $form, FormStateInterface $form_state) { - $form_state->setRedirect('entity.eventseries.collection'); $entity = $this->getEntity(); $original = NULL; + // Save as a new revision if requested to do so. + if (!$form_state->isValueEmpty('new_revision') && $form_state->getValue('new_revision') != FALSE) { + $entity->setNewRevision(); + + // If a new revision is created, save the current user as revision author. + $entity->setRevisionCreationTime(REQUEST_TIME); + $entity->setRevisionUserId(\Drupal::currentUser()->id()); + } + else { + $entity->setNewRevision(FALSE); + } + if (!$entity->isNew()) { $original = $this->storage->loadUnchanged($entity->id()); } @@ -223,6 +234,8 @@ class EventSeriesForm extends ContentEntityForm { '%name' => $entity->title->value, ])); + $form_state->setRedirect('entity.eventseries.canonical', ['eventseries' => $entity->id()]); + $this->creationService->saveEvent($entity, $form_state, $original); parent::save($form, $form_state); } diff --git a/src/Form/EventSeriesRevisionDeleteForm.php b/src/Form/EventSeriesRevisionDeleteForm.php new file mode 100644 index 00000000..7362644e --- /dev/null +++ b/src/Form/EventSeriesRevisionDeleteForm.php @@ -0,0 +1,122 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Database\Connection; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a form for deleting an eventseries revision. + * + * @ingroup recurring_events + */ +class EventSeriesRevisionDeleteForm extends ConfirmFormBase { + + /** + * The eventseries revision. + * + * @var \Drupal\recurring_events\EventInterface + */ + protected $revision; + + /** + * The eventseries storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $eventSeriesStorage; + + /** + * The database connection. + * + * @var \Drupal\Core\Database\Connection + */ + protected $connection; + + /** + * Constructs a new EventSeriesRevisionDeleteForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The entity storage. + * @param \Drupal\Core\Database\Connection $connection + * The database connection. + */ + public function __construct(EntityStorageInterface $entity_storage, Connection $connection) { + $this->eventSeriesStorage = $entity_storage; + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + $entity_manager = $container->get('entity.manager'); + return new static( + $entity_manager->getStorage('eventseries'), + $container->get('database') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventseries_revision_delete_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to delete the revision from %revision-date?', ['%revision-date' => format_date($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.eventseries.version_history', ['eventseries' => $this->revision->id()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Delete'); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventseries_revision = NULL) { + $this->revision = $this->eventSeriesStorage->loadRevision($eventseries_revision); + $form = parent::buildForm($form, $form_state); + + return $form; + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, FormStateInterface $form_state) { + $this->eventSeriesStorage->deleteRevision($this->revision->getRevisionId()); + + $this->logger('content')->notice('eventseries: deleted %title revision %revision.', ['%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]); + drupal_set_message(t('Revision from %revision-date of eventseries %title has been deleted.', ['%revision-date' => format_date($this->revision->getRevisionCreationTime()), '%title' => $this->revision->label()])); + $form_state->setRedirect( + 'entity.eventseries.canonical', + ['eventseries' => $this->revision->id()] + ); + if ($this->connection->query('SELECT COUNT(DISTINCT vid) FROM {eventseries_field_revision} WHERE id = :id', [':id' => $this->revision->id()])->fetchField() > 1) { + $form_state->setRedirect( + 'entity.eventseries.version_history', + ['eventseries' => $this->revision->id()] + ); + } + } + +} diff --git a/src/Form/EventSeriesRevisionRevertForm.php b/src/Form/EventSeriesRevisionRevertForm.php new file mode 100644 index 00000000..8da8d5ab --- /dev/null +++ b/src/Form/EventSeriesRevisionRevertForm.php @@ -0,0 +1,149 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\ConfirmFormBase; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; +use Drupal\recurring_events\EventInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides a form for reverting an eventseries revision. + * + * @ingroup recurring_events + */ +class EventSeriesRevisionRevertForm extends ConfirmFormBase { + + + /** + * The eventseries revision. + * + * @var \Drupal\recurring_events\EventInterface + */ + protected $revision; + + /** + * The eventseries storage. + * + * @var \Drupal\Core\Entity\EntityStorageInterface + */ + protected $eventSeriesStorage; + + /** + * The date formatter service. + * + * @var \Drupal\Core\Datetime\DateFormatterInterface + */ + protected $dateFormatter; + + /** + * Constructs a new EventSeriesRevisionRevertForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The eventseries storage. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + */ + public function __construct(EntityStorageInterface $entity_storage, DateFormatterInterface $date_formatter) { + $this->eventSeriesStorage = $entity_storage; + $this->dateFormatter = $date_formatter; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('eventseries'), + $container->get('date.formatter') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventseries_revision_revert_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to revert to the revision from %revision-date?', ['%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function getCancelUrl() { + return new Url('entity.eventseries.version_history', ['eventseries' => $this->revision->id()]); + } + + /** + * {@inheritdoc} + */ + public function getConfirmText() { + return t('Revert'); + } + + /** + * {@inheritdoc} + */ + public function getDescription() { + return ''; + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventseries_revision = NULL) { + $this->revision = $this->eventSeriesStorage->loadRevision($eventseries_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. + $original_revision_timestamp = $this->revision->getRevisionCreationTime(); + + $this->revision = $this->prepareRevertedRevision($this->revision, $form_state); + $this->revision->revision_log = t('Copy of the revision from %date.', ['%date' => $this->dateFormatter->format($original_revision_timestamp)]); + $this->revision->save(); + + $this->logger('content')->notice('eventseries: reverted %title revision %revision.', ['%title' => $this->revision->label(), '%revision' => $this->revision->getRevisionId()]); + drupal_set_message(t('eventseries %title has been reverted to the revision from %revision-date.', ['%title' => $this->revision->label(), '%revision-date' => $this->dateFormatter->format($original_revision_timestamp)])); + $form_state->setRedirect( + 'entity.eventseries.version_history', + ['eventseries' => $this->revision->id()] + ); + } + + /** + * Prepares a revision to be reverted. + * + * @param \Drupal\recurring_events\EventInterface $revision + * The revision to be reverted. + * @param \Drupal\Core\Form\FormStateInterface $form_state + * The current state of the form. + * + * @return \Drupal\recurring_events\EventInterface + * The prepared revision ready to be stored. + */ + protected function prepareRevertedRevision(EventInterface $revision, FormStateInterface $form_state) { + $revision->setNewRevision(); + $revision->isDefaultRevision(TRUE); + $revision->setRevisionCreationTime(REQUEST_TIME); + + return $revision; + } + +} diff --git a/src/Form/EventSeriesRevisionRevertTranslationForm.php b/src/Form/EventSeriesRevisionRevertTranslationForm.php new file mode 100644 index 00000000..5a194ef1 --- /dev/null +++ b/src/Form/EventSeriesRevisionRevertTranslationForm.php @@ -0,0 +1,115 @@ +<?php + +namespace Drupal\recurring_events\Form; + +use Drupal\Core\Datetime\DateFormatterInterface; +use Drupal\Core\Entity\EntityStorageInterface; +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\recurring_events\EventInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Provides form to revert an eventseries revision for a single translation. + * + * @ingroup recurring_events + */ +class EventSeriesRevisionRevertTranslationForm extends EventSeriesRevisionRevertForm { + + + /** + * The language to be reverted. + * + * @var string + */ + protected $langcode; + + /** + * The language manager. + * + * @var \Drupal\Core\Language\LanguageManagerInterface + */ + protected $languageManager; + + /** + * Constructs a new EventSeriesRevisionRevertTranslationForm. + * + * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage + * The eventseries storage. + * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter + * The date formatter service. + * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager + * The language manager. + */ + public function __construct(EntityStorageInterface $entity_storage, DateFormatterInterface $date_formatter, LanguageManagerInterface $language_manager) { + parent::__construct($entity_storage, $date_formatter); + $this->languageManager = $language_manager; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container) { + return new static( + $container->get('entity.manager')->getStorage('eventseries'), + $container->get('date.formatter'), + $container->get('language_manager') + ); + } + + /** + * {@inheritdoc} + */ + public function getFormId() { + return 'eventseries_revision_revert_translation_confirm'; + } + + /** + * {@inheritdoc} + */ + public function getQuestion() { + return t('Are you sure you want to revert @language translation to the revision from %revision-date?', ['@language' => $this->languageManager->getLanguageName($this->langcode), '%revision-date' => $this->dateFormatter->format($this->revision->getRevisionCreationTime())]); + } + + /** + * {@inheritdoc} + */ + public function buildForm(array $form, FormStateInterface $form_state, $eventseries_revision = NULL, $langcode = NULL) { + $this->langcode = $langcode; + $form = parent::buildForm($form, $form_state, $eventseries_revision); + + $form['revert_untranslated_fields'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Revert content shared among translations'), + '#default_value' => FALSE, + ]; + + return $form; + } + + /** + * {@inheritdoc} + */ + protected function prepareRevertedRevision(EventInterface $revision, FormStateInterface $form_state) { + $revert_untranslated_fields = $form_state->getValue('revert_untranslated_fields'); + + /** @var \Drupal\recurring_events\EventInterface $default_revision */ + $latest_revision = $this->eventSeriesStorage->load($revision->id()); + $latest_revision_translation = $latest_revision->getTranslation($this->langcode); + + $revision_translation = $revision->getTranslation($this->langcode); + + foreach ($latest_revision_translation->getFieldDefinitions() as $field_name => $definition) { + if ($definition->isTranslatable() || $revert_untranslated_fields) { + $latest_revision_translation->set($field_name, $revision_translation->get($field_name)->getValue()); + } + } + + $latest_revision_translation->setNewRevision(); + $latest_revision_translation->isDefaultRevision(TRUE); + $revision->setRevisionCreationTime(REQUEST_TIME); + + return $latest_revision_translation; + } + +} -- GitLab