Chris@0: 'entity.manager']; Chris@0: Chris@0: /** Chris@0: * Indicates that a base table got renamed. Chris@0: */ Chris@0: const BASE_TABLE_RENAME = 0; Chris@0: Chris@0: /** Chris@0: * Indicates that a data table got renamed. Chris@0: */ Chris@0: const DATA_TABLE_RENAME = 1; Chris@0: Chris@0: /** Chris@0: * Indicates that a data table got added. Chris@0: */ Chris@0: const DATA_TABLE_ADDITION = 2; Chris@0: Chris@0: /** Chris@0: * Indicates that a data table got removed. Chris@0: */ Chris@0: const DATA_TABLE_REMOVAL = 3; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision table got renamed. Chris@0: */ Chris@0: const REVISION_TABLE_RENAME = 4; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision table got added. Chris@0: */ Chris@0: const REVISION_TABLE_ADDITION = 5; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision table got removed. Chris@0: */ Chris@0: const REVISION_TABLE_REMOVAL = 6; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision data table got renamed. Chris@0: */ Chris@0: const REVISION_DATA_TABLE_RENAME = 7; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision data table got added. Chris@0: */ Chris@0: const REVISION_DATA_TABLE_ADDITION = 8; Chris@0: Chris@0: /** Chris@0: * Indicates that a revision data table got removed. Chris@0: */ Chris@0: const REVISION_DATA_TABLE_REMOVAL = 9; Chris@0: Chris@0: /** Chris@18: * The entity type manager. Chris@0: * Chris@18: * @var \Drupal\Core\Entity\EntityTypeManagerInterface Chris@0: */ Chris@18: protected $entityTypeManager; Chris@0: Chris@0: /** Chris@0: * Constructs a ViewsEntitySchemaSubscriber. Chris@0: * Chris@18: * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager Chris@18: * The entity type manager. Chris@0: */ Chris@18: public function __construct(EntityTypeManagerInterface $entity_type_manager) { Chris@18: $this->entityTypeManager = $entity_type_manager; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public static function getSubscribedEvents() { Chris@0: return static::getEntityTypeEvents(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { Chris@0: $changes = []; Chris@0: Chris@0: // We implement a specific logic for table updates, which is bound to the Chris@0: // default sql content entity storage. Chris@18: if (!$this->entityTypeManager->getStorage($entity_type->id()) instanceof SqlContentEntityStorage) { Chris@0: return; Chris@0: } Chris@0: Chris@0: if ($entity_type->getBaseTable() != $original->getBaseTable()) { Chris@0: $changes[] = static::BASE_TABLE_RENAME; Chris@0: } Chris@0: Chris@0: $revision_add = $entity_type->isRevisionable() && !$original->isRevisionable(); Chris@0: $revision_remove = !$entity_type->isRevisionable() && $original->isRevisionable(); Chris@0: $translation_add = $entity_type->isTranslatable() && !$original->isTranslatable(); Chris@0: $translation_remove = !$entity_type->isTranslatable() && $original->isTranslatable(); Chris@0: Chris@0: if ($revision_add) { Chris@0: $changes[] = static::REVISION_TABLE_ADDITION; Chris@0: } Chris@0: elseif ($revision_remove) { Chris@0: $changes[] = static::REVISION_TABLE_REMOVAL; Chris@0: } Chris@0: elseif ($entity_type->isRevisionable() && $entity_type->getRevisionTable() != $original->getRevisionTable()) { Chris@0: $changes[] = static::REVISION_TABLE_RENAME; Chris@0: } Chris@0: Chris@0: if ($translation_add) { Chris@0: $changes[] = static::DATA_TABLE_ADDITION; Chris@0: } Chris@0: elseif ($translation_remove) { Chris@0: $changes[] = static::DATA_TABLE_REMOVAL; Chris@0: } Chris@0: elseif ($entity_type->isTranslatable() && $entity_type->getDataTable() != $original->getDataTable()) { Chris@0: $changes[] = static::DATA_TABLE_RENAME; Chris@0: } Chris@0: Chris@0: if ($entity_type->isRevisionable() && $entity_type->isTranslatable()) { Chris@0: if ($revision_add || $translation_add) { Chris@0: $changes[] = static::REVISION_DATA_TABLE_ADDITION; Chris@0: } Chris@0: elseif ($entity_type->getRevisionDataTable() != $original->getRevisionDataTable()) { Chris@0: $changes[] = static::REVISION_DATA_TABLE_RENAME; Chris@0: } Chris@0: } Chris@0: elseif ($original->isRevisionable() && $original->isTranslatable() && ($revision_remove || $translation_remove)) { Chris@0: $changes[] = static::REVISION_DATA_TABLE_REMOVAL; Chris@0: } Chris@0: Chris@0: // Stop here if no changes are needed. Chris@0: if (empty($changes)) { Chris@0: return; Chris@0: } Chris@0: Chris@0: /** @var \Drupal\views\Entity\View[] $all_views */ Chris@18: $all_views = $this->entityTypeManager->getStorage('view')->loadMultiple(NULL); Chris@0: Chris@0: foreach ($changes as $change) { Chris@0: switch ($change) { Chris@0: case static::BASE_TABLE_RENAME: Chris@0: $this->baseTableRename($all_views, $entity_type->id(), $original->getBaseTable(), $entity_type->getBaseTable()); Chris@0: break; Chris@0: case static::DATA_TABLE_RENAME: Chris@0: $this->dataTableRename($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getDataTable()); Chris@0: break; Chris@0: case static::DATA_TABLE_ADDITION: Chris@0: $this->dataTableAddition($all_views, $entity_type, $entity_type->getDataTable(), $entity_type->getBaseTable()); Chris@0: break; Chris@0: case static::DATA_TABLE_REMOVAL: Chris@0: $this->dataTableRemoval($all_views, $entity_type->id(), $original->getDataTable(), $entity_type->getBaseTable()); Chris@0: break; Chris@0: case static::REVISION_TABLE_RENAME: Chris@0: $this->baseTableRename($all_views, $entity_type->id(), $original->getRevisionTable(), $entity_type->getRevisionTable()); Chris@0: break; Chris@0: case static::REVISION_TABLE_ADDITION: Chris@0: // If we add revision support we don't have to do anything. Chris@0: break; Chris@0: case static::REVISION_TABLE_REMOVAL: Chris@0: $this->revisionRemoval($all_views, $original); Chris@0: break; Chris@0: case static::REVISION_DATA_TABLE_RENAME: Chris@0: $this->dataTableRename($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionDataTable()); Chris@0: break; Chris@0: case static::REVISION_DATA_TABLE_ADDITION: Chris@0: $this->dataTableAddition($all_views, $entity_type, $entity_type->getRevisionDataTable(), $entity_type->getRevisionTable()); Chris@0: break; Chris@0: case static::REVISION_DATA_TABLE_REMOVAL: Chris@0: $this->dataTableRemoval($all_views, $entity_type->id(), $original->getRevisionDataTable(), $entity_type->getRevisionTable()); Chris@0: break; Chris@0: } Chris@0: } Chris@0: Chris@0: foreach ($all_views as $view) { Chris@0: // All changes done to the views here can be trusted and this might be Chris@0: // called during updates, when it is not safe to rely on configuration Chris@0: // containing valid schema. Trust the data and disable schema validation Chris@0: // and casting. Chris@0: $view->trustData()->save(); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function onEntityTypeDelete(EntityTypeInterface $entity_type) { Chris@0: $tables = [ Chris@0: $entity_type->getBaseTable(), Chris@0: $entity_type->getDataTable(), Chris@0: $entity_type->getRevisionTable(), Chris@0: $entity_type->getRevisionDataTable(), Chris@0: ]; Chris@0: Chris@18: $all_views = $this->entityTypeManager->getStorage('view')->loadMultiple(NULL); Chris@0: /** @var \Drupal\views\Entity\View $view */ Chris@0: foreach ($all_views as $id => $view) { Chris@0: Chris@0: // First check just the base table. Chris@0: if (in_array($view->get('base_table'), $tables)) { Chris@0: $view->disable(); Chris@0: $view->save(); Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Applies a callable onto all handlers of all passed in views. Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views entities. Chris@0: * @param callable $process Chris@0: * A callable which retrieves a handler config array. Chris@0: */ Chris@0: protected function processHandlers(array $all_views, callable $process) { Chris@0: foreach ($all_views as $view) { Chris@0: foreach (array_keys($view->get('display')) as $display_id) { Chris@0: $display = &$view->getDisplay($display_id); Chris@0: foreach (Views::getHandlerTypes() as $handler_type) { Chris@0: $handler_type = $handler_type['plural']; Chris@0: if (!isset($display['display_options'][$handler_type])) { Chris@0: continue; Chris@0: } Chris@0: foreach ($display['display_options'][$handler_type] as $id => &$handler_config) { Chris@0: $process($handler_config); Chris@0: if ($handler_config === NULL) { Chris@0: unset($display['display_options'][$handler_type][$id]); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates views if a base table is renamed. Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views. Chris@0: * @param string $entity_type_id Chris@0: * The entity type ID. Chris@0: * @param string $old_base_table Chris@0: * The old base table name. Chris@0: * @param string $new_base_table Chris@0: * The new base table name. Chris@0: */ Chris@0: protected function baseTableRename($all_views, $entity_type_id, $old_base_table, $new_base_table) { Chris@0: foreach ($all_views as $view) { Chris@0: if ($view->get('base_table') == $old_base_table) { Chris@0: $view->set('base_table', $new_base_table); Chris@0: } Chris@0: } Chris@0: Chris@0: $this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_base_table, $new_base_table) { Chris@0: if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_base_table) { Chris@0: $handler_config['table'] = $new_base_table; Chris@0: } Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates views if a data table is renamed. Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views. Chris@0: * @param string $entity_type_id Chris@0: * The entity type ID. Chris@0: * @param string $old_data_table Chris@0: * The old data table name. Chris@0: * @param string $new_data_table Chris@0: * The new data table name. Chris@0: */ Chris@0: protected function dataTableRename($all_views, $entity_type_id, $old_data_table, $new_data_table) { Chris@0: foreach ($all_views as $view) { Chris@0: if ($view->get('base_table') == $old_data_table) { Chris@0: $view->set('base_table', $new_data_table); Chris@0: } Chris@0: } Chris@0: Chris@0: $this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_data_table, $new_data_table) { Chris@0: if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id && $handler_config['table'] == $old_data_table) { Chris@0: $handler_config['table'] = $new_data_table; Chris@0: } Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates views if a data table is added. Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views. Chris@0: * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type Chris@0: * The entity type. Chris@0: * @param string $new_data_table Chris@0: * The new data table. Chris@0: * @param string $base_table Chris@0: * The base table. Chris@0: */ Chris@0: protected function dataTableAddition($all_views, EntityTypeInterface $entity_type, $new_data_table, $base_table) { Chris@0: /** @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage $storage */ Chris@0: $entity_type_id = $entity_type->id(); Chris@18: $storage = $this->entityTypeManager->getStorage($entity_type_id); Chris@0: $storage->setEntityType($entity_type); Chris@0: $table_mapping = $storage->getTableMapping(); Chris@0: $data_table_fields = $table_mapping->getFieldNames($new_data_table); Chris@0: $base_table_fields = $table_mapping->getFieldNames($base_table); Chris@0: Chris@0: $data_table = $new_data_table; Chris@0: Chris@0: $this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $base_table, $data_table, $base_table_fields, $data_table_fields) { Chris@0: if (isset($handler_config['entity_type']) && isset($handler_config['entity_field']) && $handler_config['entity_type'] == $entity_type_id) { Chris@0: // Move all fields which just exists on the data table. Chris@0: if ($handler_config['table'] == $base_table && in_array($handler_config['entity_field'], $data_table_fields) && !in_array($handler_config['entity_field'], $base_table_fields)) { Chris@0: $handler_config['table'] = $data_table; Chris@0: } Chris@0: } Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates views if a data table is removed. Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views. Chris@0: * @param string $entity_type_id Chris@0: * The entity type ID. Chris@0: * @param string $old_data_table Chris@0: * The name of the previous existing data table. Chris@0: * @param string $base_table Chris@0: * The name of the base table. Chris@0: */ Chris@0: protected function dataTableRemoval($all_views, $entity_type_id, $old_data_table, $base_table) { Chris@0: // We move back the data table back to the base table. Chris@0: $this->processHandlers($all_views, function (array &$handler_config) use ($entity_type_id, $old_data_table, $base_table) { Chris@0: if (isset($handler_config['entity_type']) && $handler_config['entity_type'] == $entity_type_id) { Chris@0: if ($handler_config['table'] == $old_data_table) { Chris@0: $handler_config['table'] = $base_table; Chris@0: } Chris@0: } Chris@0: }); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Updates views if revision support is removed Chris@0: * Chris@0: * @param \Drupal\views\Entity\View[] $all_views Chris@0: * All views. Chris@0: * @param \Drupal\Core\Entity\EntityTypeInterface $original Chris@0: * The origin entity type. Chris@0: */ Chris@0: protected function revisionRemoval($all_views, EntityTypeInterface $original) { Chris@0: $revision_base_table = $original->getRevisionTable(); Chris@0: $revision_data_table = $original->getRevisionDataTable(); Chris@0: Chris@0: foreach ($all_views as $view) { Chris@0: if (in_array($view->get('base_table'), [$revision_base_table, $revision_data_table])) { Chris@0: // Let's disable the views as we no longer support revisions. Chris@0: $view->setStatus(FALSE); Chris@0: } Chris@0: Chris@0: // For any kind of field, let's rely on the broken handler functionality. Chris@0: } Chris@0: } Chris@0: Chris@0: }