3 namespace Drupal\diff\Controller;
5 use Drupal\Core\Entity\ContentEntityInterface;
6 use Symfony\Component\HttpFoundation\RequestStack;
7 use Symfony\Component\DependencyInjection\ContainerInterface;
8 use Drupal\Component\Utility\UrlHelper;
9 use Drupal\Core\Entity\EntityStorageInterface;
11 use Drupal\Core\Routing\RouteMatchInterface;
13 use Drupal\Core\Controller\ControllerBase;
14 use Drupal\diff\DiffLayoutManager;
15 use Drupal\diff\DiffEntityComparison;
18 * Base class for controllers that return responses on entity revision routes.
20 class PluginRevisionController extends ControllerBase {
23 * Wrapper object for writing/reading configuration from diff.plugins.yml.
25 * @var \Drupal\Core\Config\ImmutableConfig
30 * The diff entity comparison service.
32 * @var \Drupal\diff\DiffEntityComparison
34 protected $entityComparison;
37 * The field diff layout plugin manager service.
39 * @var \Drupal\diff\DiffLayoutManager
41 protected $diffLayoutManager;
46 * @var \Symfony\Component\HttpFoundation\RequestStack
48 protected $requestStack;
51 * Constructs a PluginRevisionController object.
53 * @param \Drupal\diff\DiffEntityComparison $entity_comparison
54 * The diff entity comparison service.
55 * @param \Drupal\diff\DiffLayoutManager $diff_layout_manager
56 * The diff layout service.
57 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
60 public function __construct(DiffEntityComparison $entity_comparison, DiffLayoutManager $diff_layout_manager, RequestStack $request_stack) {
61 $this->config = $this->config('diff.settings');
62 $this->diffLayoutManager = $diff_layout_manager;
63 $this->entityComparison = $entity_comparison;
64 $this->requestStack = $request_stack;
70 public static function create(ContainerInterface $container) {
72 $container->get('diff.entity_comparison'),
73 $container->get('plugin.manager.diff.layout'),
74 $container->get('request_stack')
79 * Get all the revision ids of given entity id.
81 * @param \Drupal\Core\Entity\EntityStorageInterface $storage
82 * The entity storage manager.
83 * @param int $entity_id
84 * The entity to find revisions of.
89 public function getRevisionIds(EntityStorageInterface $storage, $entity_id) {
90 $result = $storage->getQuery()
92 ->condition($storage->getEntityType()->getKey('id'), $entity_id)
94 $result_array = array_keys($result);
100 * Returns a table which shows the differences between two entity revisions.
102 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
104 * @param \Drupal\Core\Entity\ContentEntityInterface $left_revision
106 * @param \Drupal\Core\Entity\ContentEntityInterface $right_revision
107 * The right revision.
108 * @param string $filter
109 * If $filter == 'raw' raw text is compared (including html tags)
110 * If filter == 'raw-plain' markdown function is applied to the text before comparison.
113 * Table showing the diff between the two entity revisions.
115 public function compareEntityRevisions(RouteMatchInterface $route_match, ContentEntityInterface $left_revision, ContentEntityInterface $right_revision, $filter) {
116 $entity_type_id = $left_revision->getEntityTypeId();
117 /** @var \Drupal\Core\Entity\EntityInterface $entity */
118 $entity = $route_match->getParameter($entity_type_id);
120 $entity_type_id = $entity->getEntityTypeId();
121 $storage = $this->entityTypeManager()->getStorage($entity_type_id);
122 // Get language from the entity context.
123 $langcode = $entity->language()->getId();
125 // Get left and right revision in current language.
126 $left_revision = $left_revision->getTranslation($langcode);
127 $right_revision = $right_revision->getTranslation($langcode);
130 // Filter revisions of current translation and where the translation is
132 foreach ($this->getRevisionIds($storage, $entity->id()) as $revision_id) {
133 /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
134 $revision = $storage->loadRevision($revision_id);
135 if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
136 $revisions_ids[] = $revision_id;
141 '#title' => $this->t('Changes to %title', ['%title' => $entity->label()]),
143 '#prefix' => '<header class="diff-header">',
144 '#suffix' => '</header>',
147 '#prefix' => '<div class="diff-controls">',
148 '#suffix' => '</div>',
152 // Build the navigation links.
153 $build['header']['diff_navigation'] = $this->buildRevisionsNavigation($entity, $revisions_ids, $left_revision->getRevisionId(), $right_revision->getRevisionId(), $filter);
155 // Build the layout filter.
156 $build['controls']['diff_layout'] = [
158 '#title' => $this->t('Layout'),
159 '#wrapper_attributes' => ['class' => 'diff-controls__item'],
160 'filter' => $this->buildLayoutNavigation($entity, $left_revision->getRevisionId(), $right_revision->getRevisionId(), $filter),
163 // Perform comparison only if both entity revisions loaded successfully.
164 if ($left_revision != FALSE && $right_revision != FALSE) {
165 // Build the diff comparison with the plugin.
166 if ($plugin = $this->diffLayoutManager->createInstance($filter)) {
167 $build = array_merge_recursive($build, $plugin->build($left_revision, $right_revision, $entity));
168 $build['diff']['#prefix'] = '<div class="diff-responsive-table-wrapper">';
169 $build['diff']['#suffix'] = '<div>';
170 $build['diff']['#attributes']['class'][] = 'diff-responsive-table';
174 $build['#attached']['library'][] = 'diff/diff.general';
179 * Builds a navigation dropdown button between the layout plugins.
181 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
182 * The entity to be compared.
183 * @param int $left_revision_id
184 * Revision id of the left revision.
185 * @param int $right_revision_id
186 * Revision id of the right revision.
187 * @param string $active_filter
193 protected function buildLayoutNavigation(ContentEntityInterface $entity, $left_revision_id, $right_revision_id, $active_filter) {
195 $layouts = $this->diffLayoutManager->getPluginOptions();
196 foreach ($layouts as $key => $value) {
197 $links[$key] = array(
199 'url' => $this->diffRoute($entity, $left_revision_id, $right_revision_id, $key),
203 // Set as the first element the current filter.
204 $filter = $links[$active_filter];
205 unset($links[$active_filter]);
206 array_unshift($links, $filter);
209 '#type' => 'operations',
217 * Creates navigation links between the previous changes and the new ones.
219 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
220 * The entity to be compared.
221 * @param array $revision_ids
223 * @param int $left_revision_id
224 * Revision id of the left revision.
225 * @param int $right_revision_id
226 * Revision id of the right revision.
227 * @param string $filter
231 * The revision navigation links.
233 protected function buildRevisionsNavigation(ContentEntityInterface $entity, array $revision_ids, $left_revision_id, $right_revision_id, $filter) {
234 $revisions_count = count($revision_ids);
235 $layout_options = &drupal_static(__FUNCTION__);
236 if (!isset($layout_options)) {
237 $layout_options = UrlHelper::filterQueryParameters($this->requestStack->getCurrentRequest()->query->all(), ['page']);
239 // If there are only 2 revision return an empty row.
240 if ($revisions_count == 2) {
244 $left_link = $right_link = '';
247 '#title' => $this->t('Navigation'),
248 '#wrapper_attributes' => ['class' => 'diff-navigation'],
251 // Find the previous revision.
252 while ($left_revision_id > $revision_ids[$i]) {
256 // Build the left link.
257 $left_link = Link::fromTextAndUrl($this->t('Previous change'), $this->diffRoute($entity, $revision_ids[$i - 1], $left_revision_id, $filter, $layout_options))->toString();
261 '#markup' => $left_link,
262 '#prefix' => '<div class="diff-navigation__link prev-link">',
263 '#suffix' => '</div>',
265 // Find the next revision.
267 while ($i < $revisions_count && $right_revision_id >= $revision_ids[$i]) {
270 if ($revisions_count != $i && $revision_ids[$i - 1] != $revision_ids[$revisions_count - 1]) {
271 // Build the right link.
272 $right_link = Link::fromTextAndUrl($this->t('Next change'), $this->diffRoute($entity, $right_revision_id, $revision_ids[$i], $filter, $layout_options))->toString();
274 $element['right'] = [
276 '#markup' => $right_link,
277 '#prefix' => '<div class="diff-navigation__link next-link">',
278 '#suffix' => '</div>',
285 * Creates an url object for diff.
287 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
288 * The entity to be compared.
289 * @param int $left_revision_id
290 * Revision id of the left revision.
291 * @param int $right_revision_id
292 * Revision id of the right revision.
293 * @param string $layout
294 * (optional) The filter/layout added to the route.
295 * @param array $layout_options
296 * (optional) The layout options provided by the selected layout.
298 * @return \Drupal\Core\Url
301 public static function diffRoute(ContentEntityInterface $entity, $left_revision_id, $right_revision_id, $layout = NULL, array $layout_options = NULL) {
302 $entity_type_id = $entity->getEntityTypeId();
303 // @todo Remove the diff.revisions_diff route so we avoid adding extra cases.
304 if ($entity->getEntityTypeId() == 'node') {
305 $route_name = 'diff.revisions_diff';
308 $route_name = "entity.$entity_type_id.revisions_diff";
310 $route_parameters = [
311 $entity_type_id => $entity->id(),
312 'left_revision' => $left_revision_id,
313 'right_revision' => $right_revision_id,
316 $route_parameters['filter'] = $layout;
319 if ($layout_options) {
321 'query' => $layout_options,
324 return Url::fromRoute($route_name, $route_parameters, $options);