PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/modules/views/src/EventSubscriber/ViewsEntitySchemaSubscriber.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 424 lines | 226 code | 54 blank | 144 comment | 56 complexity | 1490b91fdcebff331f8f25ef9538edfd MD5 | raw file
  1. <?php
  2. namespace Drupal\views\EventSubscriber;
  3. use Drupal\Core\Entity\EntityTypeEventSubscriberTrait;
  4. use Drupal\Core\Entity\EntityTypeInterface;
  5. use Drupal\Core\Entity\EntityTypeListenerInterface;
  6. use Drupal\Core\Entity\EntityTypeManagerInterface;
  7. use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
  8. use Drupal\views\ViewEntityInterface;
  9. use Drupal\views\Views;
  10. use Psr\Log\LoggerInterface;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. /**
  13. * Reacts to changes on entity types to update all views entities.
  14. */
  15. class ViewsEntitySchemaSubscriber implements EntityTypeListenerInterface, EventSubscriberInterface {
  16. use EntityTypeEventSubscriberTrait;
  17. /**
  18. * Indicates that a base table got renamed.
  19. */
  20. const BASE_TABLE_RENAME = 0;
  21. /**
  22. * Indicates that a data table got renamed.
  23. */
  24. const DATA_TABLE_RENAME = 1;
  25. /**
  26. * Indicates that a data table got added.
  27. */
  28. const DATA_TABLE_ADDITION = 2;
  29. /**
  30. * Indicates that a data table got removed.
  31. */
  32. const DATA_TABLE_REMOVAL = 3;
  33. /**
  34. * Indicates that a revision table got renamed.
  35. */
  36. const REVISION_TABLE_RENAME = 4;
  37. /**
  38. * Indicates that a revision table got added.
  39. */
  40. const REVISION_TABLE_ADDITION = 5;
  41. /**
  42. * Indicates that a revision table got removed.
  43. */
  44. const REVISION_TABLE_REMOVAL = 6;
  45. /**
  46. * Indicates that a revision data table got renamed.
  47. */
  48. const REVISION_DATA_TABLE_RENAME = 7;
  49. /**
  50. * Indicates that a revision data table got added.
  51. */
  52. const REVISION_DATA_TABLE_ADDITION = 8;
  53. /**
  54. * Indicates that a revision data table got removed.
  55. */
  56. const REVISION_DATA_TABLE_REMOVAL = 9;
  57. /**
  58. * The entity type manager.
  59. *
  60. * @var \Drupal\Core\Entity\EntityTypeManagerInterface
  61. */
  62. protected $entityTypeManager;
  63. /**
  64. * The default logger service.
  65. *
  66. * @var \Psr\Log\LoggerInterface
  67. */
  68. protected $logger;
  69. /**
  70. * Constructs a ViewsEntitySchemaSubscriber.
  71. *
  72. * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
  73. * The entity type manager.
  74. * @param \Psr\Log\LoggerInterface $logger
  75. * A logger instance.
  76. */
  77. public function __construct(EntityTypeManagerInterface $entity_type_manager, LoggerInterface $logger) {
  78. $this->entityTypeManager = $entity_type_manager;
  79. $this->logger = $logger;
  80. }
  81. /**
  82. * {@inheritdoc}
  83. */
  84. public static function getSubscribedEvents() {
  85. return static::getEntityTypeEvents();
  86. }
  87. /**
  88. * {@inheritdoc}
  89. */
  90. public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  91. $changes = [];
  92. // We implement a specific logic for table updates, which is bound to the
  93. // default sql content entity storage.
  94. if (!$this->entityTypeManager->getStorage($entity_type->id()) instanceof SqlContentEntityStorage) {
  95. return;
  96. }
  97. if ($entity_type->getBaseTable() != $original->getBaseTable()) {
  98. $changes[] = static::BASE_TABLE_RENAME;
  99. }
  100. $revision_add = $entity_type->isRevisionable() && !$original->isRevisionable();
  101. $revision_remove = !$entity_type->isRevisionable() && $original->isRevisionable();
  102. $translation_add = $entity_type->isTranslatable() && !$original->isTranslatable();
  103. $translation_remove = !$entity_type->isTranslatable() && $original->isTranslatable();
  104. if ($revision_add) {
  105. $changes[] = static::REVISION_TABLE_ADDITION;
  106. }
  107. elseif ($revision_remove) {
  108. $changes[] = static::REVISION_TABLE_REMOVAL;
  109. }
  110. elseif ($entity_type->isRevisionable() && $entity_type->getRevisionTable() != $original->getRevisionTable()) {
  111. $changes[] = static::REVISION_TABLE_RENAME;
  112. }
  113. if ($translation_add) {
  114. $changes[] = static::DATA_TABLE_ADDITION;
  115. }
  116. elseif ($translation_remove) {
  117. $changes[] = static::DATA_TABLE_REMOVAL;
  118. }
  119. elseif ($entity_type->isTranslatable() && $entity_type->getDataTable() != $original->getDataTable()) {
  120. $changes[] = static::DATA_TABLE_RENAME;
  121. }
  122. if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) {
  123. if ($revision_add || $translation_add) {
  124. $changes[] = static::REVISION_DATA_TABLE_ADDITION;
  125. }
  126. elseif ($entity_type->getRevisionDataTable() != $original->getRevisionDataTable()) {
  127. $changes[] = static::REVISION_DATA_TABLE_RENAME;
  128. }
  129. }
  130. elseif ($original->isRevisionable() && $original->isTranslatable() && ($revision_remove || $translation_remove)) {
  131. $changes[] = static::REVISION_DATA_TABLE_REMOVAL;
  132. }
  133. // Stop here if no changes are needed.
  134. if (empty($changes)) {
  135. return;
  136. }
  137. /** @var \Drupal\views\Entity\View[] $all_views */
  138. $all_views = $this->entityTypeManager->getStorage('view')->loadMultiple(NULL);
  139. foreach ($changes as $change) {
  140. switch ($change) {
  141. case static::BASE_TABLE_RENAME:
  142. $this->baseTableRename($all_views, $entity_type->id(), $original->getBaseTable(), $entity_type->getBaseTable());
  143. break;
  144. case static::DATA_TABLE_RENAME:
  145. $this->dataTableRename($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getDataTable());
  146. break;
  147. case static::DATA_TABLE_ADDITION:
  148. $this->dataTableAddition($all_views, $entity_type, $entity_type->getDataTable(), $entity_type->getBaseTable());
  149. break;
  150. case static::DATA_TABLE_REMOVAL:
  151. $this->dataTableRemoval($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getBaseTable());
  152. break;
  153. case static::REVISION_TABLE_RENAME:
  154. $this->baseTableRename($all_views, $entity_type->id(), $original->getRevisionTable(), $entity_type->getRevisionTable());
  155. break;
  156. case static::REVISION_TABLE_ADDITION:
  157. // If we add revision support we don't have to do anything.
  158. break;
  159. case static::REVISION_TABLE_REMOVAL:
  160. $this->revisionRemoval($all_views, $original);
  161. break;
  162. case static::REVISION_DATA_TABLE_RENAME:
  163. $this->dataTableRename($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionDataTable());
  164. break;
  165. case static::REVISION_DATA_TABLE_ADDITION:
  166. $this->dataTableAddition($all_views, $entity_type, $entity_type->getRevisionDataTable(), $entity_type->getRevisionTable());
  167. break;
  168. case static::REVISION_DATA_TABLE_REMOVAL:
  169. $this->dataTableRemoval($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionTable());
  170. break;
  171. }
  172. }
  173. // Filter the list of views that needs to be updated.
  174. $views_to_update = array_filter($all_views, function (ViewEntityInterface $view) {
  175. return $view->get('_updated') === TRUE;
  176. });
  177. foreach ($views_to_update as $view) {
  178. try {
  179. // All changes done to the views here can be trusted and this might be
  180. // called during updates, when it is not safe to rely on configuration
  181. // containing valid schema. Trust the data and disable schema validation
  182. // and casting.
  183. $view->set('_updated', NULL);
  184. $view->trustData()->save();
  185. }
  186. catch (\Exception $e) {
  187. // In case the view could not be saved, log an error message that the
  188. // view needs to be updated manually instead of failing the entire
  189. // entity update process.
  190. $this->logger->critical("The %view_id view could not be updated automatically while processing an entity schema update for the %entity_type_id entity type.", [
  191. '%view_id' => $view->id(),
  192. '%entity_type_id' => $entity_type->id(),
  193. ]);
  194. }
  195. }
  196. }
  197. /**
  198. * {@inheritdoc}
  199. */
  200. public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
  201. $tables = [
  202. $entity_type->getBaseTable(),
  203. $entity_type->getDataTable(),
  204. $entity_type->getRevisionTable(),
  205. $entity_type->getRevisionDataTable(),
  206. ];
  207. $all_views = $this->entityTypeManager->getStorage('view')->loadMultiple(NULL);
  208. /** @var \Drupal\views\Entity\View $view */
  209. foreach ($all_views as $id => $view) {
  210. // First check just the base table.
  211. if (in_array($view->get('base_table'), $tables)) {
  212. $view->disable();
  213. $view->save();
  214. }
  215. }
  216. }
  217. /**
  218. * Applies a callable onto all handlers of all passed in views.
  219. *
  220. * @param \Drupal\views\Entity\View[] $all_views
  221. * All views entities.
  222. * @param callable $process
  223. * A callable which retrieves a handler config array.
  224. */
  225. protected function processHandlers(array $all_views, callable $process) {
  226. foreach ($all_views as $view) {
  227. foreach (array_keys($view->get('display')) as $display_id) {
  228. $display = &$view->getDisplay($display_id);
  229. foreach (Views::getHandlerTypes() as $handler_type) {
  230. $handler_type = $handler_type['plural'];
  231. if (!isset($display['display_options'][$handler_type])) {
  232. continue;
  233. }
  234. foreach ($display['display_options'][$handler_type] as $id => &$handler_config) {
  235. $process($handler_config, $view);
  236. if ($handler_config === NULL) {
  237. unset($display['display_options'][$handler_type][$id]);
  238. }
  239. }
  240. }
  241. }
  242. }
  243. }
  244. /**
  245. * Updates views if a base table is renamed.
  246. *
  247. * @param \Drupal\views\Entity\View[] $all_views
  248. * All views.
  249. * @param string $entity_type_id
  250. * The entity type ID.
  251. * @param string $old_base_table
  252. * The old base table name.
  253. * @param string $new_base_table
  254. * The new base table name.
  255. */
  256. protected function baseTableRename($all_views, $entity_type_id, $old_base_table, $new_base_table) {
  257. foreach ($all_views as $view) {
  258. if ($view->get('base_table') == $old_base_table) {
  259. $view->set('base_table', $new_base_table);
  260. $view->set('_updated', TRUE);
  261. }
  262. }
  263. $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_base_table, $new_base_table) {
  264. if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_base_table) {
  265. $handler_config['table'] = $new_base_table;
  266. $view->set('_updated', TRUE);
  267. }
  268. });
  269. }
  270. /**
  271. * Updates views if a data table is renamed.
  272. *
  273. * @param \Drupal\views\Entity\View[] $all_views
  274. * All views.
  275. * @param string $entity_type_id
  276. * The entity type ID.
  277. * @param string $old_data_table
  278. * The old data table name.
  279. * @param string $new_data_table
  280. * The new data table name.
  281. */
  282. protected function dataTableRename($all_views, $entity_type_id, $old_data_table, $new_data_table) {
  283. foreach ($all_views as $view) {
  284. if ($view->get('base_table') == $old_data_table) {
  285. $view->set('base_table', $new_data_table);
  286. $view->set('_updated', TRUE);
  287. }
  288. }
  289. $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_data_table, $new_data_table) {
  290. if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_data_table) {
  291. $handler_config['table'] = $new_data_table;
  292. $view->set('_updated', TRUE);
  293. }
  294. });
  295. }
  296. /**
  297. * Updates views if a data table is added.
  298. *
  299. * @param \Drupal\views\Entity\View[] $all_views
  300. * All views.
  301. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  302. * The entity type.
  303. * @param string $new_data_table
  304. * The new data table.
  305. * @param string $base_table
  306. * The base table.
  307. */
  308. protected function dataTableAddition($all_views, EntityTypeInterface $entity_type, $new_data_table, $base_table) {
  309. /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */
  310. $entity_type_id = $entity_type->id();
  311. $storage = $this->entityTypeManager->getStorage($entity_type_id);
  312. $storage->setEntityType($entity_type);
  313. $table_mapping = $storage->getTableMapping();
  314. $data_table_fields = $table_mapping->getFieldNames($new_data_table);
  315. $base_table_fields = $table_mapping->getFieldNames($base_table);
  316. $data_table = $new_data_table;
  317. $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $base_table, $data_table, $base_table_fields, $data_table_fields) {
  318. if (isset($handler_config['entity_type']) && isset($handler_config['entity_field']) && $handler_config['entity_type'] == $entity_type_id) {
  319. // Move all fields which just exists on the data table.
  320. if ($handler_config['table'] == $base_table && in_array($handler_config['entity_field'], $data_table_fields) && !in_array($handler_config['entity_field'], $base_table_fields)) {
  321. $handler_config['table'] = $data_table;
  322. $view->set('_updated', TRUE);
  323. }
  324. }
  325. });
  326. }
  327. /**
  328. * Updates views if a data table is removed.
  329. *
  330. * @param \Drupal\views\Entity\View[] $all_views
  331. * All views.
  332. * @param string $entity_type_id
  333. * The entity type ID.
  334. * @param string $old_data_table
  335. * The name of the previous existing data table.
  336. * @param string $base_table
  337. * The name of the base table.
  338. */
  339. protected function dataTableRemoval($all_views, $entity_type_id, $old_data_table, $base_table) {
  340. // We move back the data table back to the base table.
  341. $this->processHandlers($all_views, function (&$handler_config, ViewEntityInterface $view) use ($entity_type_id, $old_data_table, $base_table) {
  342. if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id) {
  343. if ($handler_config['table'] == $old_data_table) {
  344. $handler_config['table'] = $base_table;
  345. $view->set('_updated', TRUE);
  346. }
  347. }
  348. });
  349. }
  350. /**
  351. * Updates views if revision support is removed.
  352. *
  353. * @param \Drupal\views\Entity\View[] $all_views
  354. * All views.
  355. * @param \Drupal\Core\Entity\EntityTypeInterface $original
  356. * The origin entity type.
  357. */
  358. protected function revisionRemoval($all_views, EntityTypeInterface $original) {
  359. $revision_base_table = $original->getRevisionTable();
  360. $revision_data_table = $original->getRevisionDataTable();
  361. foreach ($all_views as $view) {
  362. if (in_array($view->get('base_table'), [$revision_base_table, $revision_data_table])) {
  363. // Let's disable the views as we no longer support revisions.
  364. $view->setStatus(FALSE);
  365. $view->set('_updated', TRUE);
  366. }
  367. // For any kind of field, let's rely on the broken handler functionality.
  368. }
  369. }
  370. }