View source
<?php
namespace Drupal\moderation_sidebar\Controller;
use Drupal\Core\Access\AccessResult;
use Drupal\Component\Utility\Xss;
use Drupal\content_moderation\ModerationInformation;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Entity\RevisionLogInterface;
use Drupal\Core\Entity\TranslatableRevisionableStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Link;
use Drupal\Core\Menu\LocalTaskManager;
use Drupal\Core\Menu\LocalTaskManagerInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Url;
use Drupal\moderation_sidebar\Form\QuickTransitionForm;
use Drupal\node\NodeInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
class ModerationSidebarController extends ControllerBase {
protected $moderationInformation;
protected $request;
protected $dateFormatter;
protected $moduleHandler;
protected $localTaskManager;
public function __construct(ModerationInformation $moderation_information, RequestStack $request_stack, DateFormatterInterface $date_formatter, ModuleHandlerInterface $module_handler, LocalTaskManagerInterface $local_task_manager) {
$this->moderationInformation = $moderation_information;
$this->request = $request_stack
->getCurrentRequest();
$this->dateFormatter = $date_formatter;
$this->moduleHandler = $module_handler;
$this->localTaskManager = $local_task_manager;
}
public static function create(ContainerInterface $container) {
$moderation_info = $container
->get('content_moderation.moderation_information');
$request_stack = $container
->get('request_stack');
$attributes = $request_stack
->getCurrentRequest()->attributes;
$entity = $attributes
->has('node') ? $attributes
->get('node') : $attributes
->get('entity');
$fake_request_stack = new RequestStack();
$current_request = $container
->get('request_stack')
->getCurrentRequest();
$request = Request::create($entity
->toUrl()
->getInternalPath(), 'GET', [], [], [], $current_request->server
->all(), NULL);
$router = $container
->get('router');
$router
->matchRequest($request);
$fake_request_stack
->push($request);
$route_match = new CurrentRouteMatch($fake_request_stack);
$local_task_manager = new LocalTaskManager($container
->get('http_kernel.controller.argument_resolver'), $fake_request_stack, $route_match, $container
->get('router.route_provider'), $container
->get('module_handler'), $container
->get('cache.discovery'), $container
->get('language_manager'), $container
->get('access_manager'), $container
->get('current_user'));
return new static($moderation_info, $request_stack, $container
->get('date.formatter'), $container
->get('module_handler'), $local_task_manager);
}
public function sideBar(ContentEntityInterface $entity) {
$langcode = $entity
->language()
->getId();
$entity_type_id = $entity
->getEntityTypeId();
$storage = \Drupal::entityTypeManager()
->getStorage($entity_type_id);
$is_latest = TRUE;
if ($storage instanceof TranslatableRevisionableStorageInterface) {
$latest_revision_id = $storage
->getLatestTranslationAffectedRevisionId($entity
->id(), $langcode);
if ($latest_revision_id < $entity
->getRevisionId()) {
$entity = $storage
->load($entity
->id())
->getTranslation($langcode);
$is_latest = TRUE;
}
elseif ($latest_revision_id == $entity
->getRevisionId()) {
$is_latest = TRUE;
}
else {
$is_latest = FALSE;
}
}
$build = [
'#theme' => 'moderation_sidebar_container',
'#attributes' => [
'class' => [
'moderation-sidebar-container',
],
],
];
$build['info'] = [
'#theme' => 'moderation_sidebar_info',
'#state' => $this
->getStateLabel($entity),
];
if ($entity instanceof RevisionLogInterface) {
if ($user = $entity
->getRevisionUser()) {
$build['info']['#revision_author'] = $user
->getDisplayName();
$build['info']['#revision_author_link'] = $user
->toLink()
->toRenderable();
}
if ($time = (int) $entity
->getRevisionCreationTime()) {
$time_pretty = $this
->getPrettyTime($time);
$build['info']['#revision_time'] = $time;
$build['info']['#revision_time_pretty'] = $time_pretty;
}
}
$build['actions'] = [
'#type' => 'container',
'#attributes' => [
'class' => [
'moderation-sidebar-actions',
],
],
];
$build['actions']['secondary'] = [];
if ($this->moderationInformation
->isModeratedEntity($entity)) {
if ($this->moderationInformation
->hasPendingRevision($entity) && $entity
->isDefaultRevision()) {
$build['actions']['primary']['view_latest'] = [
'#title' => $this
->t('View existing draft'),
'#type' => 'link',
'#url' => Url::fromRoute("entity.{$entity_type_id}.latest_version", [
$entity_type_id => $entity
->id(),
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
];
}
if ($this->moderationInformation
->hasPendingRevision($entity) && !$entity
->isDefaultRevision()) {
$build['actions']['primary']['view_default'] = [
'#title' => $this
->t('View live content'),
'#type' => 'link',
'#url' => $entity
->toLink()
->getUrl(),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
];
}
if ($entity
->access('update')) {
$build['actions']['primary']['edit'] = [
'#title' => !$this->moderationInformation
->hasPendingRevision($entity) ? $this
->t('Edit content') : $this
->t('Edit draft'),
'#type' => 'link',
'#url' => $entity
->toLink(NULL, 'edit-form')
->getUrl(),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
];
}
if ($entity
->isDefaultRevision() && $entity
->access('delete')) {
$build['actions']['primary']['delete'] = [
'#title' => $this
->t('Delete content'),
'#type' => 'link',
'#url' => $entity
->toLink(NULL, 'delete-form')
->getUrl(),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
'button--danger',
],
],
'#weight' => 1,
];
}
if ($entity_type_id === 'node' && \Drupal::service('access_check.node.revision')
->checkAccess($entity, \Drupal::currentUser()
->getAccount())) {
$build['actions']['secondary']['version_history'] = [
'#title' => $this
->t('Show revisions'),
'#type' => 'link',
'#url' => Url::fromRoute('moderation_sidebar.node.version_history', [
'node' => $entity
->id(),
], [
'query' => [
'latest' => $is_latest,
],
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
'use-ajax',
],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
];
}
if ($this
->moduleHandler()
->moduleExists('content_translation') && \Drupal::service('content_translation.manager')
->isSupported($entity_type_id)) {
$build['actions']['secondary']['translate'] = [
'#title' => $this
->t('Translate'),
'#type' => 'link',
'#url' => Url::fromRoute($is_latest ? 'moderation_sidebar.translate_latest' : 'moderation_sidebar.translate', [
'entity_type' => $entity_type_id,
'entity' => $entity
->id(),
], [
'query' => [
'latest' => $is_latest,
],
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
'use-ajax',
],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
];
}
if ($is_latest) {
$build['actions']['primary']['quick_draft_form'] = $this
->formBuilder()
->getForm(QuickTransitionForm::class, $entity);
}
}
$build['actions']['secondary'] += $this
->getLocalTasks($entity);
$this->moduleHandler
->alter('moderation_sidebar', $build, $entity);
return $build;
}
public function title(ContentEntityInterface $entity) {
return $entity
->label();
}
public function revisionOverview(NodeInterface $node) {
$langcode = $node
->language()
->getId();
$node_storage = $this
->entityTypeManager()
->getStorage('node');
$result = $node_storage
->getQuery()
->allRevisions()
->condition($node
->getEntityType()
->getKey('id'), $node
->id())
->sort($node
->getEntityType()
->getKey('revision'), 'DESC')
->execute();
$build = $this
->getBackButton($node);
$count = 0;
foreach (array_keys($result) as $vid) {
if ($count >= 5) {
break;
}
$revision = $node_storage
->loadRevision($vid);
if ($revision
->hasTranslation($langcode) && $revision
->getTranslation($langcode)
->isRevisionTranslationAffected()) {
$user = $revision
->getRevisionUser();
$message = !empty($revision->revision_log->value) ? $revision->revision_log->value : $this
->t('No message');
$time = $revision->revision_timestamp->value;
$pretty_time = $this
->getPrettyTime($revision->revision_timestamp->value);
if ($vid != $node
->getRevisionId()) {
$link = new Link($pretty_time, new Url('entity.node.revision', [
'node' => $node
->id(),
'node_revision' => $vid,
]));
}
else {
$link = $node
->toLink($pretty_time);
}
$build[] = [
'#theme' => 'moderation_sidebar_revision',
'#revision_message' => [
'#markup' => $message,
'#allowed_tags' => Xss::getHtmlTagList(),
],
'#revision_time' => $time,
'#revision_time_pretty' => $pretty_time,
'#revision_author' => $user
->getDisplayName(),
'#revision_author_link' => $user
->toLink()
->toRenderable(),
'#revision_link' => $link,
];
++$count;
}
}
$build[] = [
'#title' => $this
->t('View all revisions'),
'#type' => 'link',
'#url' => Url::fromRoute('entity.node.version_history', [
'node' => $node
->id(),
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
];
return $build;
}
public function translateOverview(ContentEntityInterface $entity) {
$entity_type = $entity
->getEntityType();
$entity_type_id = $entity
->getEntityTypeId();
$bundle = $entity
->bundle();
$account = $this
->currentUser();
if (!\Drupal::moduleHandler()
->moduleExists('content_translation') || !\Drupal::service('content_translation.manager')
->isSupported($entity_type_id)) {
throw new AccessDeniedHttpException();
}
$langcode = $entity
->language()
->getId();
$entity_type_id = $entity
->getEntityTypeId();
$storage = \Drupal::entityTypeManager()
->getStorage($entity_type_id);
if ($storage instanceof TranslatableRevisionableStorageInterface) {
$latest_revision_id = $storage
->getLatestTranslationAffectedRevisionId($entity
->id(), $langcode);
if ($latest_revision_id < $entity
->getRevisionId()) {
$entity = $storage
->load($entity
->id())
->getTranslation($langcode);
}
}
$build = $this
->getBackButton($entity);
$can_create = $account
->hasPermission('translate any entity');
if (!$can_create) {
$granularity = $entity_type
->getPermissionGranularity();
$permission = $granularity === 'bundle' ? "translate {$bundle} {$entity_type_id}" : "translate {$entity_type_id}";
$can_create = $account
->hasPermission($permission);
}
$languages = $this
->languageManager()
->getLanguages();
$translation_languages = $entity
->getTranslationLanguages();
if ($this
->languageManager()
->isMultilingual()) {
$translatable = FALSE;
foreach ($entity
->getFieldDefinitions() as $instance) {
if ($instance
->isTranslatable()) {
$translatable = TRUE;
break;
}
}
$translations = [];
foreach ($languages as $language) {
$langcode = $language
->getId();
if ($langcode === $entity
->language()
->getId()) {
continue;
}
if ($storage instanceof TranslatableRevisionableStorageInterface) {
$latest_revision = NULL;
$latest_revision_id = $storage
->getLatestTranslationAffectedRevisionId($entity
->id(), $langcode);
if ($latest_revision_id && ($latest_revision = $storage
->loadRevision($latest_revision_id))) {
$latest_revision = $latest_revision
->getTranslation($langcode);
}
}
else {
$latest_revision = $storage
->loadRevision($storage
->getLatestRevisionId($entity
->id()))
->getTranslation($langcode);
}
$latest_translation = FALSE;
$entity_has_translation = array_key_exists($langcode, $translation_languages);
if (!$entity_has_translation && $latest_revision && !$latest_revision
->wasDefaultRevision()) {
$latest_translation = TRUE;
}
if ($entity_has_translation || $latest_translation) {
$translation = !$latest_translation ? $entity
->getTranslation($langcode) : $latest_revision;
$translation_links = [
'#title' => $this
->t('View'),
'#type' => 'link',
'#url' => $translation
->toUrl($latest_translation ? 'latest-version' : 'canonical'),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
'title' => $this
->t('View: @label', [
'@label' => $translation
->label(),
]),
],
];
if ($translation
->access('edit', $account)) {
$translation_links[] = [
'#title' => $this
->t('Edit'),
'#type' => 'link',
'#url' => $translation
->toUrl('edit-form'),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
'title' => $this
->t('Edit: @label', [
'@label' => $translation
->label(),
]),
],
];
}
if ($latest_revision
->getRevisionId() != $translation
->getRevisionId() && $latest_revision->moderation_state->value == 'draft') {
$translation_links[] = [
'#title' => $this
->t('View existing draft'),
'#type' => 'link',
'#url' => $translation
->toUrl('latest-version'),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
'title' => $this
->t('View: @label', [
'@label' => $latest_revision
->label(),
]),
],
];
}
$translations[] = [
'language' => $language
->getName(),
'state' => $this
->getStateLabel($translation),
'links' => $translation_links,
];
}
elseif ($can_create && $translatable) {
$translations[] = [
'language' => $language
->getName(),
'links' => [
[
'#title' => $this
->t('Create translation'),
'#type' => 'link',
'#url' => Url::fromRoute("entity.{$entity_type_id}.content_translation_add", [
'source' => $entity
->getUntranslated()
->language()
->getId(),
'target' => $language
->getId(),
$entity_type_id => $entity
->id(),
], [
'language' => $language,
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
],
],
];
}
}
$build[] = [
'#theme' => 'moderation_sidebar_translations',
'#current_language' => $entity
->language()
->getName(),
'#translations' => $translations,
'#view_all' => [
'#title' => $this
->t('View all translations'),
'#type' => 'link',
'#url' => Url::fromRoute('entity.node.content_translation_overview', [
'node' => $entity
->id(),
]),
'#attributes' => [
'class' => [
'moderation-sidebar-link',
'button',
],
],
],
];
}
return $build;
}
protected function getModerationState(ContentEntityInterface $entity) {
$state_id = $entity->moderation_state
->get(0)
->getValue()['value'];
$workflow = $this->moderationInformation
->getWorkFlowForEntity($entity);
return $workflow
->getTypePlugin()
->getState($state_id);
}
protected function getLocalTasks(ContentEntityInterface $entity) {
$tasks = $this->localTaskManager
->getLocalTasks("entity.{$entity->getEntityTypeId()}.canonical", 0);
$tabs = [];
if (isset($tasks['tabs']) && !empty($tasks['tabs'])) {
foreach ($tasks['tabs'] as $name => $tab) {
$duplicated_tab = preg_match('/^.*(canonical|edit_form|delete_form|latest_version_tab|entity\\.node\\.version_history|content_translation_overview)$/', $name);
if (!$this->moderationInformation
->isModeratedEntity($entity) || !$duplicated_tab) {
$attributes = [];
if (isset($tab['#link']['localized_options']['attributes'])) {
$attributes = $tab['#link']['localized_options']['attributes'];
}
$attributes['class'][] = 'moderation-sidebar-link';
$attributes['class'][] = 'button';
$tabs[$name] = [
'#title' => $tab['#link']['title'],
'#type' => 'link',
'#url' => $tab['#link']['url'],
'#attributes' => $attributes,
'#access' => isset($tab['#access']) ? $tab['#access'] : AccessResult::neutral(),
];
}
}
}
return $tabs;
}
protected function getPrettyTime($time) {
$too_old = strtotime('-1 month');
if ($time > $too_old) {
$diff = $this->dateFormatter
->formatTimeDiffSince($time, [
'granularity' => 1,
]);
$time_pretty = $this
->t('@diff ago', [
'@diff' => $diff,
]);
}
else {
$date = date('m/d/Y - h:i A', $time);
$time_pretty = $this
->t('on @date', [
'@date' => $date,
]);
}
return $time_pretty;
}
protected function getBackButton(ContentEntityInterface $entity) {
$params = [
'entity' => $entity
->id(),
'entity_type' => $entity
->getEntityTypeId(),
];
if (\Drupal::request()
->get('latest')) {
$back_url = Url::fromRoute('moderation_sidebar.sidebar_latest', $params);
}
else {
$back_url = Url::fromRoute('moderation_sidebar.sidebar', $params);
}
$build = [
'#type' => 'container',
'#attributes' => [
'class' => [
'moderation-sidebar-container',
],
],
[
'#title' => $this
->t('← Back'),
'#type' => 'link',
'#url' => $back_url,
'#attributes' => [
'class' => [
'use-ajax',
'moderation-sidebar-back-button',
],
'data-dialog-type' => 'dialog',
'data-dialog-renderer' => 'off_canvas',
],
],
];
return $build;
}
protected function getStateLabel(ContentEntityInterface $entity) {
if ($this->moderationInformation
->isModeratedEntity($entity)) {
$state = $this
->getModerationState($entity);
$state_label = $state
->label();
}
elseif ($entity instanceof EntityPublishedInterface) {
$state_label = $entity
->isPublished() ? $this
->t('Published') : $this
->t('Unpublished');
}
else {
$state_label = $this
->t('Published');
}
return $state_label;
}
}