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)
95 $result_array = array_keys($result);
101 * Returns a table which shows the differences between two entity revisions.
103 * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
105 * @param \Drupal\Core\Entity\ContentEntityInterface $left_revision
107 * @param \Drupal\Core\Entity\ContentEntityInterface $right_revision
108 * The right revision.
109 * @param string $filter
110 * If $filter == 'raw' raw text is compared (including html tags)
111 * If filter == 'raw-plain' markdown function is applied to the text before comparison.
114 * Table showing the diff between the two entity revisions.
116 public function compareEntityRevisions(RouteMatchInterface $route_match, ContentEntityInterface $left_revision, ContentEntityInterface $right_revision, $filter) {
117 $entity_type_id = $left_revision->getEntityTypeId();
118 /** @var \Drupal\Core\Entity\EntityInterface $entity */
119 $entity = $route_match->getParameter($entity_type_id);
121 $entity_type_id = $entity->getEntityTypeId();
122 $storage = $this->entityTypeManager()->getStorage($entity_type_id);
123 // Get language from the entity context.
124 $langcode = $entity->language()->getId();
126 // Get left and right revision in current language.
127 $left_revision = $left_revision->getTranslation($langcode);
128 $right_revision = $right_revision->getTranslation($langcode);
131 // Filter revisions of current translation and where the translation is
133 foreach ($this->getRevisionIds($storage, $entity->id()) as $revision_id) {
134 /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */
135 $revision = $storage->loadRevision($revision_id);
136 if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
137 $revisions_ids[] = $revision_id;
142 '#title' => $this->t('Changes to %title', ['%title' => $entity->label()]),
144 '#prefix' => '<header class="diff-header">',
145 '#suffix' => '</header>',
148 '#prefix' => '<div class="diff-controls">',
149 '#suffix' => '</div>',
153 // Build the navigation links.
154 $build['header']['diff_navigation'] = $this->buildRevisionsNavigation($entity, $revisions_ids, $left_revision->getRevisionId(), $right_revision->getRevisionId(), $filter);
156 // Build the layout filter.
157 $build['controls']['diff_layout'] = [
159 '#title' => $this->t('Layout'),
160 '#wrapper_attributes' => ['class' => 'diff-controls__item'],
161 'filter' => $this->buildLayoutNavigation($entity, $left_revision->getRevisionId(), $right_revision->getRevisionId(), $filter),
164 // Perform comparison only if both entity revisions loaded successfully.
165 if ($left_revision != FALSE && $right_revision != FALSE) {
166 // Build the diff comparison with the plugin.
167 if ($plugin = $this->diffLayoutManager->createInstance($filter)) {
168 $build = array_merge_recursive($build, $plugin->build($left_revision, $right_revision, $entity));
169 $build['diff']['#prefix'] = '<div class="diff-responsive-table-wrapper">';
170 $build['diff']['#suffix'] = '</div>';
171 $build['diff']['#attributes']['class'][] = 'diff-responsive-table';
175 $build['#attached']['library'][] = 'diff/diff.general';
180 * Builds a navigation dropdown button between the layout plugins.
182 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
183 * The entity to be compared.
184 * @param int $left_revision_id
185 * Revision id of the left revision.
186 * @param int $right_revision_id
187 * Revision id of the right revision.
188 * @param string $active_filter
194 protected function buildLayoutNavigation(ContentEntityInterface $entity, $left_revision_id, $right_revision_id, $active_filter) {
196 $layouts = $this->diffLayoutManager->getPluginOptions();
197 foreach ($layouts as $key => $value) {
198 $links[$key] = array(
200 'url' => $this->diffRoute($entity, $left_revision_id, $right_revision_id, $key),
204 // Set as the first element the current filter.
205 $filter = $links[$active_filter];
206 unset($links[$active_filter]);
207 array_unshift($links, $filter);
210 '#type' => 'operations',
218 * Creates navigation links between the previous changes and the new ones.
220 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
221 * The entity to be compared.
222 * @param array $revision_ids
224 * @param int $left_revision_id
225 * Revision id of the left revision.
226 * @param int $right_revision_id
227 * Revision id of the right revision.
228 * @param string $filter
232 * The revision navigation links.
234 protected function buildRevisionsNavigation(ContentEntityInterface $entity, array $revision_ids, $left_revision_id, $right_revision_id, $filter) {
235 $revisions_count = count($revision_ids);
236 $layout_options = &drupal_static(__FUNCTION__);
237 if (!isset($layout_options)) {
238 $layout_options = UrlHelper::filterQueryParameters($this->requestStack->getCurrentRequest()->query->all(), ['page']);
240 // If there are only 2 revision return an empty row.
241 if ($revisions_count == 2) {
245 $left_link = $right_link = '';
248 '#title' => $this->t('Navigation'),
249 '#wrapper_attributes' => ['class' => 'diff-navigation'],
252 // Find the previous revision.
253 while ($left_revision_id > $revision_ids[$i]) {
257 // Build the left link.
258 $left_link = Link::fromTextAndUrl($this->t('Previous change'), $this->diffRoute($entity, $revision_ids[$i - 1], $left_revision_id, $filter, $layout_options))->toString();
262 '#markup' => $left_link,
263 '#prefix' => '<div class="diff-navigation__link prev-link">',
264 '#suffix' => '</div>',
266 // Find the next revision.
268 while ($i < $revisions_count && $right_revision_id >= $revision_ids[$i]) {
271 if ($revisions_count != $i && $revision_ids[$i - 1] != $revision_ids[$revisions_count - 1]) {
272 // Build the right link.
273 $right_link = Link::fromTextAndUrl($this->t('Next change'), $this->diffRoute($entity, $right_revision_id, $revision_ids[$i], $filter, $layout_options))->toString();
275 $element['right'] = [
277 '#markup' => $right_link,
278 '#prefix' => '<div class="diff-navigation__link next-link">',
279 '#suffix' => '</div>',
286 * Creates an url object for diff.
288 * @param \Drupal\Core\Entity\ContentEntityInterface $entity
289 * The entity to be compared.
290 * @param int $left_revision_id
291 * Revision id of the left revision.
292 * @param int $right_revision_id
293 * Revision id of the right revision.
294 * @param string $layout
295 * (optional) The filter/layout added to the route.
296 * @param array $layout_options
297 * (optional) The layout options provided by the selected layout.
299 * @return \Drupal\Core\Url
302 public static function diffRoute(ContentEntityInterface $entity, $left_revision_id, $right_revision_id, $layout = NULL, array $layout_options = NULL) {
303 $entity_type_id = $entity->getEntityTypeId();
304 // @todo Remove the diff.revisions_diff route so we avoid adding extra cases.
305 if ($entity->getEntityTypeId() == 'node') {
306 $route_name = 'diff.revisions_diff';
309 $route_name = "entity.$entity_type_id.revisions_diff";
311 $route_parameters = [
312 $entity_type_id => $entity->id(),
313 'left_revision' => $left_revision_id,
314 'right_revision' => $right_revision_id,
317 $route_parameters['filter'] = $layout;
320 if ($layout_options) {
322 'query' => $layout_options,
325 return Url::fromRoute($route_name, $route_parameters, $options);