PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Controller/GenericRevisionController.php

https://gitlab.com/Drulenium-bot/diff
PHP | 365 lines | 243 code | 29 blank | 93 comment | 46 complexity | 0f2297fe5215d2cdd4aed75c5ea4ceed MD5 | raw file
  1. <?php
  2. namespace Drupal\diff\Controller;
  3. use Drupal\Component\Utility\Xss;
  4. use Drupal\Core\Entity\EntityInterface;
  5. use Drupal\Core\Entity\EntityStorageInterface;
  6. use Drupal\Core\Routing\RouteMatchInterface;
  7. use Drupal\Core\Url;
  8. use Drupal\diff\EntityComparisonBase;
  9. use Drupal\node\NodeInterface;
  10. class GenericRevisionController extends EntityComparisonBase {
  11. /**
  12. * Get all the revision ids of given entity id.
  13. *
  14. * @param $storage
  15. * The entity storage manager.
  16. * @param $entity_id
  17. * The entity to find revisions of.
  18. *
  19. * @return array
  20. */
  21. protected function getVids(EntityStorageInterface $storage, $entity_id) {
  22. $result = $storage->getQuery()
  23. ->allRevisions()
  24. ->condition($storage->getEntityType()->getKey('id'), $entity_id)
  25. ->execute();
  26. $result_array = array_keys($result);
  27. sort($result_array);
  28. return $result_array;
  29. }
  30. /**
  31. * Returns a table which shows the differences between two entity revisions.
  32. *
  33. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  34. * The route match.
  35. * @param \Drupal\Core\Entity\EntityInterface $left_revision
  36. * The left revision
  37. * @param \Drupal\Core\Entity\EntityInterface $right_revision
  38. * The right revision.
  39. * @param string $filter
  40. * If $filter == 'raw' raw text is compared (including html tags)
  41. * If filter == 'raw-plain' markdown function is applied to the text before comparison.
  42. *
  43. * @return array
  44. * Table showing the diff between the two entity revisions.
  45. */
  46. public function compareEntityRevisions(RouteMatchInterface $route_match, EntityInterface $left_revision, EntityInterface $right_revision, $filter) {
  47. $entity_type_id = $left_revision->getEntityTypeId();
  48. $entity = $route_match->getParameter($entity_type_id);
  49. $diff_rows = array();
  50. $build = array(
  51. '#title' => $this->t('Revisions for %title', array('%title' => $entity->label())),
  52. );
  53. if (!in_array($filter, array('raw', 'raw-plain'))) {
  54. $filter = 'raw';
  55. }
  56. elseif ($filter == 'raw-plain') {
  57. $filter = 'raw_plain';
  58. }
  59. $entity_type_id = $entity->getEntityTypeId();
  60. $storage = $this->entityTypeManager()->getStorage($entity_type_id);
  61. // Get language from the entity context.
  62. $langcode = $entity->language()->getId();
  63. // Get left and right revision in current language.
  64. $left_revision = $left_revision->getTranslation($langcode);
  65. $right_revision = $right_revision->getTranslation($langcode);
  66. $vids = [];
  67. // Filter revisions of current translation and where the translation is
  68. // affected.
  69. foreach ($this->getVids($storage, $entity->id()) as $vid) {
  70. $revision = $storage->loadRevision($vid);
  71. if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
  72. $vids[] = $vid;
  73. }
  74. }
  75. $diff_rows[] = $this->buildRevisionsNavigation($entity, $vids, $left_revision->getRevisionId(), $right_revision->getRevisionId());
  76. $diff_rows[] = $this->buildMarkdownNavigation($entity, $left_revision->getRevisionId(), $right_revision->getRevisionId(), $filter);
  77. $diff_header = $this->buildTableHeader($left_revision, $right_revision);
  78. // Perform comparison only if both entity revisions loaded successfully.
  79. if ($left_revision != FALSE && $right_revision != FALSE) {
  80. $fields = $this->compareRevisions($left_revision, $right_revision);
  81. $entity_base_fields = $this->entityManager()->getBaseFieldDefinitions($entity_type_id);
  82. // Check to see if we need to display certain fields or not based on
  83. // selected view mode display settings.
  84. foreach ($fields as $field_name => $field) {
  85. // If we are dealing with entities only compare those fields
  86. // set as visible from the selected view mode.
  87. $view_mode = $this->config->get('content_type_settings.' . $entity->bundle() . '.view_mode');
  88. // If no view mode is selected use the default view mode.
  89. if ($view_mode == NULL) {
  90. $view_mode = 'default';
  91. }
  92. list(, $field_machine_name) = explode('.', $field_name);
  93. $visible = entity_get_display($entity_type_id, $entity->bundle(), $view_mode)->getComponent($field_machine_name);
  94. if ($visible == NULL && !array_key_exists($field_name, $entity_base_fields)) {
  95. unset($fields[$field_name]);
  96. }
  97. }
  98. // Build the diff rows for each field and append the field rows
  99. // to the table rows.
  100. foreach ($fields as $field) {
  101. $field_label_row = '';
  102. if (!empty($field['#name'])) {
  103. $field_label_row = array(
  104. 'data' => $this->t('Changes to %name', array('%name' => $field['#name'])),
  105. 'colspan' => 4,
  106. 'class' => array('field-name'),
  107. );
  108. }
  109. $field_diff_rows = $this->getRows(
  110. $field['#states'][$filter]['#left'],
  111. $field['#states'][$filter]['#right']
  112. );
  113. // Add the field label to the table only if there are changes to that field.
  114. if (!empty($field_diff_rows) && !empty($field_label_row)) {
  115. $diff_rows[] = array($field_label_row);
  116. }
  117. // Add field diff rows to the table rows.
  118. $diff_rows = array_merge($diff_rows, $field_diff_rows);
  119. }
  120. // Add the CSS for the diff.
  121. $build['#attached']['library'][] = 'diff/diff.general';
  122. $theme = $this->config->get('general_settings.theme');
  123. if ($theme) {
  124. if ($theme == 'default') {
  125. $build['#attached']['library'][] = 'diff/diff.default';
  126. }
  127. elseif ($theme == 'github') {
  128. $build['#attached']['library'][] = 'diff/diff.github';
  129. }
  130. }
  131. // If the setting could not be loaded or is missing use the default theme.
  132. elseif ($theme == NULL) {
  133. $build['#attached']['library'][] = 'diff/diff.github';
  134. }
  135. $build['diff'] = array(
  136. '#type' => 'table',
  137. '#header' => $diff_header,
  138. '#rows' => $diff_rows,
  139. '#empty' => $this->t('No visible changes'),
  140. '#attributes' => array(
  141. 'class' => array('diff'),
  142. ),
  143. );
  144. if ($entity->hasLinkTemplate('version-history')) {
  145. $build['back'] = array(
  146. '#type' => 'link',
  147. '#attributes' => array(
  148. 'class' => array(
  149. 'button',
  150. 'diff-button',
  151. ),
  152. ),
  153. '#title' => $this->t('Back to Revision Overview'),
  154. '#url' => Url::fromRoute("entity.$entity_type_id.version_history", [$entity_type_id => $entity->id()]),
  155. );
  156. }
  157. return $build;
  158. }
  159. else {
  160. // @todo When task 'Convert drupal_set_message() to a service' (2278383)
  161. // will be merged use the corresponding service instead.
  162. drupal_set_message($this->t('Selected @label revisions could not be loaded.', ['@label' => $entity->getEntityType()->getLabel()]), 'error');
  163. }
  164. }
  165. /**
  166. * Build the header for the diff table.
  167. *
  168. * @param \Drupal\Core\Entity\EntityInterface $left_revision
  169. * Revision from the left hand side.
  170. * @param \Drupal\Core\Entity\EntityInterface $right_revision
  171. * Revision from the right hand side.
  172. *
  173. * @return array
  174. * Header for Diff table.
  175. */
  176. protected function buildTableHeader(EntityInterface $left_revision, EntityInterface $right_revision) {
  177. $entity_type_id = $left_revision->getEntityTypeId();
  178. $revisions = array($left_revision, $right_revision);
  179. $header = array();
  180. foreach ($revisions as $revision) {
  181. if ($revision instanceof EntityRevisionLogInterface || $revision instanceof NodeInterface) {
  182. $revision_log = $this->nonBreakingSpace;
  183. if ($revision instanceof EntityRevisionLogInterface) {
  184. $revision_log = Xss::filter($revision->getRevisionLogMessage());
  185. }
  186. elseif ($revision instanceof NodeInterface) {
  187. $revision_log = $revision->revision_log->value;
  188. }
  189. $username = array(
  190. '#theme' => 'username',
  191. '#account' => $revision->uid->entity,
  192. );
  193. $revision_date = $this->date->format($revision->getRevisionCreationTime(), 'short');
  194. $route_name = $entity_type_id != 'node' ? "entity.$entity_type_id.revisions_diff": 'entity.node.revision';
  195. $revision_link = $this->t($revision_log . '@date', array(
  196. '@date' => $this->l($revision_date, Url::fromRoute($route_name, array(
  197. $entity_type_id => $revision->id(),
  198. $entity_type_id . '_revision' => $revision->getRevisionId(),
  199. ))),
  200. ));
  201. }
  202. else {
  203. $revision_link = $this->l($revision->label(), $revision->toUrl('revision'));
  204. }
  205. // @todo When theming think about where in the table to integrate this
  206. // link to the revision user. There is some issue about multi-line headers
  207. // for theme table.
  208. // $header[] = array(
  209. // 'data' => $this->t('by' . '!username', array('!username' => drupal_render($username))),
  210. // 'colspan' => 1,
  211. // );
  212. $header[] = array(
  213. 'data' => array('#markup' => $this->nonBreakingSpace),
  214. 'colspan' => 1,
  215. );
  216. $header[] = array(
  217. 'data' => array('#markup' => $revision_link),
  218. 'colspan' => 1,
  219. );
  220. }
  221. return $header;
  222. }
  223. /**
  224. * Returns the navigation row for diff table.
  225. */
  226. protected function buildRevisionsNavigation(EntityInterface $entity, $vids, $left_vid, $right_vid) {
  227. $revisions_count = count($vids);
  228. $i = 0;
  229. $row = array();
  230. // Find the previous revision.
  231. while ($left_vid > $vids[$i]) {
  232. $i += 1;
  233. }
  234. if ($i != 0) {
  235. // Second column.
  236. $row[] = array(
  237. 'data' => $this->l(
  238. $this->t('< Previous difference'), $this->diffRoute($entity, $vids[$i - 1], $left_vid)
  239. ),
  240. 'colspan' => 2,
  241. 'class' => 'rev-navigation',
  242. );
  243. }
  244. else {
  245. // Second column.
  246. $row[] = $this->nonBreakingSpace;
  247. }
  248. // Third column.
  249. $row[] = $this->nonBreakingSpace;
  250. // Find the next revision.
  251. $i = 0;
  252. while ($i < $revisions_count && $right_vid >= $vids[$i]) {
  253. $i += 1;
  254. }
  255. if ($revisions_count != $i && $vids[$i - 1] != $vids[$revisions_count - 1]) {
  256. // Forth column.
  257. $row[] = array(
  258. 'data' => $this->l(
  259. $this->t('Next difference >'), $this->diffRoute($entity, $right_vid, $vids[$i])
  260. ),
  261. 'colspan' => 2,
  262. 'class' => 'rev-navigation',
  263. );
  264. }
  265. else {
  266. // Forth column.
  267. $row[] = $this->nonBreakingSpace;
  268. }
  269. // If there are only 2 revision return an empty row.
  270. if ($revisions_count == 2) {
  271. return array();
  272. }
  273. else {
  274. return $row;
  275. }
  276. }
  277. /**
  278. * Builds a table row with navigation between raw and raw-plain formats.
  279. */
  280. protected function buildMarkdownNavigation(EntityInterface $entity, $left_vid, $right_vid, $active_filter) {
  281. $links['raw'] = array(
  282. 'title' => $this->t('Standard'),
  283. 'url' => $this->diffRoute($entity, $left_vid, $right_vid),
  284. );
  285. $links['raw_plain'] = array(
  286. 'title' => $this->t('Markdown'),
  287. 'url' => $this->diffRoute($entity, $left_vid, $right_vid, 'raw-plain'),
  288. );
  289. // Set as the first element the current filter.
  290. $filter = $links[$active_filter];
  291. unset($links[$active_filter]);
  292. array_unshift($links, $filter);
  293. $row[] = array(
  294. 'data' => array(
  295. '#type' => 'operations',
  296. '#links' => $links,
  297. ),
  298. 'colspan' => 4,
  299. );
  300. return $row;
  301. }
  302. /**
  303. * Creates an url object for diff.
  304. *
  305. * @param \Drupal\Core\Entity\EntityInterface $entity
  306. * The entity to be compared.
  307. * @param $left_vid
  308. * Vid of the left revision.
  309. * @param $right_vid
  310. * Vid of the right revision.
  311. * @param $filter
  312. * (optional) The filter added to the route.
  313. *
  314. * @return \Drupal\Core\Url
  315. * The URL object.
  316. */
  317. protected function diffRoute(EntityInterface $entity, $left_vid, $right_vid, $filter = NULL) {
  318. $entity_type_id = $entity->getEntityTypeId();
  319. // @todo Remove the diff.revisions_diff route so we avoid adding extra cases.
  320. if ($entity->getEntityTypeId() == 'node') {
  321. $route_name = 'diff.revisions_diff';
  322. }
  323. else {
  324. $route_name = "entity.$entity_type_id.revisions_diff";
  325. }
  326. $route_parameters = [
  327. $entity_type_id => $entity->id(),
  328. 'left_revision' => $left_vid,
  329. 'right_revision' => $right_vid,
  330. ];
  331. if ($filter) {
  332. $route_parameters['filter'] = $filter;
  333. }
  334. return Url::fromRoute($route_name, $route_parameters);
  335. }
  336. }