diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install index c6ada4c7d5..578ecd7b52 100644 --- a/core/modules/block_content/block_content.install +++ b/core/modules/block_content/block_content.install @@ -7,7 +7,7 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\StringTranslation\TranslatableMarkup; -use Drupal\Core\Database\Database; +use Drupal\block_content\BlockContentInterface; /** * Implements hook_update_dependencies(). @@ -164,48 +164,15 @@ function block_content_update_8600() { ->setInitialValue(NULL); $update_manager->installFieldStorageDefinition('parent_entity_id', 'block_content', 'block_content', $parent_entity_id); -} -/** - * Implements hook_schema(). - */ -function block_content_schema() { - $schema['block_content_delete'] = [ - 'description' => 'Tracks block_content entities to delete.', - 'fields' => [ - 'block_content_id' => [ - 'description' => 'Block Content entity ID.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - ], - ], - 'primary key' => ['block_content_id'], - 'indexes' => [ - 'block_content_id' => ['block_content_id'], - ], - ]; - return $schema; -} + $parent_status = BaseFieldDefinition::create('integer') + ->setLabel(t('Parent status')) + ->setDescription(t('The status parent entity if any.')) + ->setTranslatable(FALSE) + ->setRevisionable(FALSE) + ->setDefaultValue(BlockContentInterface::PARENT_NONE) + ->setInitialValue(BlockContentInterface::PARENT_NONE); -/** - * Adds 'block_content_delete' table. - */ -function block_content_update_8601() { - $block_content_delete = [ - 'description' => 'Tracks block_content entities to delete.', - 'fields' => [ - 'block_content_id' => [ - 'description' => 'Block Content entity ID.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - ], - ], - 'primary key' => ['block_content_id'], - 'indexes' => [ - 'block_content_id' => ['block_content_id'], - ], - ]; - Database::getConnection()->schema()->createTable('block_content_delete', $block_content_delete); + $update_manager->installFieldStorageDefinition('parent_status', 'block_content', 'block_content', $parent_status); } + diff --git a/core/modules/block_content/block_content.module b/core/modules/block_content/block_content.module index cef9b4c575..f6b53ce9ab 100644 --- a/core/modules/block_content/block_content.module +++ b/core/modules/block_content/block_content.module @@ -12,7 +12,6 @@ use Drupal\Core\Database\Query\SelectInterface; use Drupal\Core\Database\Query\AlterableInterface; use Drupal\Core\Database\Query\ConditionInterface; -use Drupal\block_content\BlockContentWithParentDeleter; /** * Implements hook_help(). @@ -179,16 +178,16 @@ function _block_content_has_parent_entity_condition(array $condition) { * Implements hook_entity_delete(). */ function block_content_entity_delete(EntityInterface $entity) { - /** @var \Drupal\block_content\BlockContentWithParentDeleter $block_deleter */ - $block_deleter = \Drupal::service('class_resolver')->getInstanceFromDefinition(BlockContentWithParentDeleter::class); - $block_deleter->handleEntityDelete($entity); + /** @var \Drupal\block_content\BlockContentStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage('block_content'); + $storage->setParentStatus($entity->getEntityTypeId(), $entity->id(), \Drupal\block_content\BlockContentInterface::PARENT_DELETED); } /** * Implements hook_cron(). */ function block_content_cron() { - /** @var \Drupal\block_content\BlockContentWithParentDeleter $block_deleter */ - $block_deleter = \Drupal::service('class_resolver')->getInstanceFromDefinition(BlockContentWithParentDeleter::class); - $block_deleter->removeUnused(); + /** @var \Drupal\block_content\BlockContentStorageInterface $storage */ + $storage = \Drupal::entityTypeManager()->getStorage('block_content'); + $storage->deleteBlocksWithParentDeleted(); } diff --git a/core/modules/block_content/src/BlockContentAccessControlHandler.php b/core/modules/block_content/src/BlockContentAccessControlHandler.php index 6bdbaa4faf..d932429b44 100644 --- a/core/modules/block_content/src/BlockContentAccessControlHandler.php +++ b/core/modules/block_content/src/BlockContentAccessControlHandler.php @@ -3,50 +3,16 @@ namespace Drupal\block_content; use Drupal\Core\Access\AccessResult; -use Drupal\Core\Database\Connection; -use Drupal\Core\Entity\EntityHandlerInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityAccessControlHandler; -use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Session\AccountInterface; -use Symfony\Component\DependencyInjection\ContainerInterface; /** * Defines the access control handler for the custom block entity type. * * @see \Drupal\block_content\Entity\BlockContent */ -class BlockContentAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface { - - /** - * The database connection. - * - * @var \Drupal\Core\Database\Connection - */ - protected $database; - - /** - * Constructs a BlockContentAccessControlHandler instance. - * - * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type - * The entity type definition. - * @param \Drupal\Core\Database\Connection $database - * The database connection. - */ - public function __construct(EntityTypeInterface $entity_type, Connection $database) { - parent::__construct($entity_type); - $this->database = $database; - } - - /** - * {@inheritdoc} - */ - public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { - return new static( - $entity_type, - $container->get('database') - ); - } +class BlockContentAccessControlHandler extends EntityAccessControlHandler { /** * {@inheritdoc} @@ -61,7 +27,7 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter } /** @var \Drupal\block_content\BlockContentInterface $entity */ if ($entity->hasParentEntity()) { - if ($this->isParentDeleted($entity)) { + if ($entity->get('parent_status')->value === BlockContentInterface::PARENT_DELETED) { // If the blocks parent has been deleted then access is forbidden. This // must be checked before loading the parent entity because if the // parent entity type allows arbitrary IDs then the entity type ID could @@ -81,21 +47,4 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter return $access; } - /** - * Checks if blocks parent has been deleted. - * - * @param \Drupal\block_content\BlockContentInterface $block - * The block content entity. - * - * @return bool - * TRUE if the 'block_content' entity parent entity has been deleted - * otherwise FALSE. - */ - protected function isParentDeleted(BlockContentInterface $block) { - $query = $this->database->select('block_content_delete') - ->fields('block_content_delete', ['block_content_id']); - $query->condition('block_content_id', $block->id()); - return !empty($query->execute()->fetchCol()); - } - } diff --git a/core/modules/block_content/src/BlockContentInterface.php b/core/modules/block_content/src/BlockContentInterface.php index 16d427561e..45e239f153 100644 --- a/core/modules/block_content/src/BlockContentInterface.php +++ b/core/modules/block_content/src/BlockContentInterface.php @@ -13,6 +13,21 @@ */ interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface { + /** + * Denotes that the block has no parent. + */ + const PARENT_NONE = 0; + + /** + * Denotes that the block has an active parent. + */ + const PARENT_ACTIVE = 1; + + /** + * Denotes that the block has a parent that has been deleted. + */ + const PARENT_DELETED = 2; + /** * Returns the block revision log message. * diff --git a/core/modules/block_content/src/BlockContentStorage.php b/core/modules/block_content/src/BlockContentStorage.php new file mode 100644 index 0000000000..02b2942a53 --- /dev/null +++ b/core/modules/block_content/src/BlockContentStorage.php @@ -0,0 +1,37 @@ +entityManager->getDefinition('block_content')->getDataTable(); + $this->database->update($datatable) + ->fields(['parent_status' => $parent_status]) + ->condition('parent_entity_type', $parent_entity_type) + ->condition('parent_entity_id', $parent_id) + ->execute(); + } + + /** + * {@inheritdoc} + */ + public function deleteBlocksWithParentDeleted($limit = 100) { + $query = $this->getQuery(); + $query->condition('parent_status', BlockContentInterface::PARENT_DELETED) + ->range(0, $limit)->execute(); + $blocks = $this->loadMultiple($query->execute()); + $this->delete($blocks); + } +} diff --git a/core/modules/block_content/src/BlockContentStorageInterface.php b/core/modules/block_content/src/BlockContentStorageInterface.php new file mode 100644 index 0000000000..aef73bfa30 --- /dev/null +++ b/core/modules/block_content/src/BlockContentStorageInterface.php @@ -0,0 +1,32 @@ +storage = $entityTypeManager->getStorage('block_content'); - $this->database = $database; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity_type.manager'), - $container->get('database') - ); - } - - /** - * Handles reacting to a deleting a parent entity. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * The parent entity. - * - * @throws \Exception - */ - public function handleEntityDelete(EntityInterface $entity) { - $query = $this->storage->getQuery(); - $query->condition('parent_entity_id', $entity->id()); - $query->condition('parent_entity_type', $entity->getEntityTypeId()); - $block_ids = $query->execute(); - - $query = $this->database->insert('block_content_delete') - ->fields(['block_content_id']); - foreach ($block_ids as $block_id) { - $query->values([$block_id]); - } - $query->execute(); - } - - /** - * Removes unused block content entities. - * - * @param int $limit - * The maximum number of block content entities to remove. - * - * @throws \Drupal\Core\Entity\EntityStorageException - */ - public function removeUnused($limit = 100) { - $query = $this->database->select('block_content_delete') - ->range(0, $limit) - ->fields('block_content_delete', ['block_content_id']); - $block_content_ids = $query->execute()->fetchCol(); - foreach ($block_content_ids as $block_content_id) { - if ($block = $this->storage->load($block_content_id)) { - $block->delete(); - } - } - $this->database->delete('block_content_delete') - ->condition('block_content_id', $block_content_ids, 'IN') - ->execute(); - } - -} diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 2a83477036..1644a213b3 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -25,7 +25,7 @@ * ), * bundle_label = @Translation("Custom block type"), * handlers = { - * "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage", + * "storage" = "Drupal\block_content\BlockContentStorage", * "access" = "Drupal\block_content\BlockContentAccessControlHandler", * "list_builder" = "Drupal\block_content\BlockContentListBuilder", * "view_builder" = "Drupal\block_content\BlockContentViewBuilder", @@ -228,6 +228,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setDefaultValue(NULL) ->setInitialValue(NULL); + $fields['parent_status'] = BaseFieldDefinition::create('integer') + ->setLabel(t('Parent status')) + ->setDescription(t('The status parent entity if any.')) + ->setTranslatable(FALSE) + ->setRevisionable(FALSE) + ->setDefaultValue(BlockContentInterface::PARENT_NONE) + ->setInitialValue(BlockContentInterface::PARENT_NONE); + return $fields; } @@ -324,6 +332,7 @@ protected static function invalidateBlockPluginCache() { public function setParentEntity(EntityInterface $parent_entity) { $this->set('parent_entity_type', $parent_entity->getEntityTypeId()); $this->set('parent_entity_id', $parent_entity->id()); + $this->set('parent_status', BlockContentInterface::PARENT_ACTIVE); return $this; } @@ -343,6 +352,7 @@ public function getParentEntity() { public function removeParentEntity() { $this->set('parent_entity_type', NULL); $this->set('parent_entity_id', NULL); + $this->set('parent_status', BlockContentInterface::PARENT_NONE); return $this; } diff --git a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php index edf82bc46a..3dd08a3ad5 100644 --- a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php +++ b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php @@ -47,7 +47,7 @@ public function getDerivativeDefinitions($base_plugin_definition) { $block_contents = $this->blockContentStorage->loadMultiple($block_ids); // Reset the discovered definitions. $this->derivatives = []; - /* @var $block_content \Drupal\block_content\Entity\BlockContent */ + /** @var $block_content \Drupal\block_content\Entity\BlockContent */ foreach ($block_contents as $block_content) { $this->derivatives[$block_content->uuid()] = $base_plugin_definition; $this->derivatives[$block_content->uuid()]['admin_label'] = $block_content->label(); diff --git a/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php b/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php index 13b2df05b1..0b6f42ebbc 100644 --- a/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php +++ b/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\block_content\Functional\Rest; +use Drupal\block_content\BlockContentInterface; use Drupal\block_content\Entity\BlockContent; use Drupal\block_content\Entity\BlockContentType; use Drupal\Core\Cache\Cache; @@ -94,6 +95,11 @@ protected function getExpectedNormalizedEntity() { ], 'parent_entity_type' => [], 'parent_entity_id' => [], + 'parent_status' => [ + [ + 'value' => BlockContentInterface::PARENT_NONE, + ], + ], 'type' => [ [ 'target_id' => 'basic', diff --git a/core/modules/block_content/tests/src/Functional/Update/BlockContentParentEntityUpdateTest.php b/core/modules/block_content/tests/src/Functional/Update/BlockContentParentEntityUpdateTest.php index 7a2ab3034c..e7a92d3cef 100644 --- a/core/modules/block_content/tests/src/Functional/Update/BlockContentParentEntityUpdateTest.php +++ b/core/modules/block_content/tests/src/Functional/Update/BlockContentParentEntityUpdateTest.php @@ -33,7 +33,7 @@ public function testParentFieldsAddition() { $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); // Delete custom block library view. - View::load('block_content')->delete(); + $this->config('views.view.block_content')->delete(); // Install the test module with the 'block_content' view with an extra // display with overridden filters. This extra display should also have the // 'has_parent' filter added so that it does not expose fields with parents diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php index dededb611a..6f4c5d282a 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php @@ -68,6 +68,13 @@ public function testBlocksWithParentsNotDerived() { // Ensure the block content with a parent is not provided a derivative block // plugin. $this->assertFalse($block_manager->hasDefinition($plugin_id)); + + $block_content->removeParentEntity(); + $block_content->save(); + + // Ensure the block content is provided a derivative block + // plugin after the parent is removed. + $this->assertTrue($block_manager->hasDefinition($plugin_id)); } } diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentParentDeleteTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentParentDeleteTest.php index 2bc5292e74..61f8390e1b 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentParentDeleteTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentParentDeleteTest.php @@ -41,7 +41,6 @@ public function setUp() { $this->installSchema('system', ['sequence']); $this->installSchema('system', ['sequences']); $this->installSchema('user', ['users_data']); - $this->installSchema('block_content', ['block_content_delete']); $this->installEntitySchema('user'); $this->installEntitySchema('block_content'); @@ -98,7 +97,7 @@ public function testDeleteParent() { $this->assertNotEmpty($block_storage->load($block_content->id())); $this->assertEmpty($block_storage->load($block_content_with_parent->id())); - // Test a parent entity type without a datatable. + // Test a parent config entity type. $config_entity = $this->entityTypeManager->getStorage('config_test')->create([ 'id' => 'test_entity', 'label' => 'Test config entity',