diff --git a/core/lib/Drupal/Core/Access/AccessDependentInterface.php b/core/lib/Drupal/Core/Access/AccessDependentInterface.php index 6604ab4bf8..a8386282e9 100644 --- a/core/lib/Drupal/Core/Access/AccessDependentInterface.php +++ b/core/lib/Drupal/Core/Access/AccessDependentInterface.php @@ -17,7 +17,7 @@ * To check the access to the dependency the object implementing this interface * can use code like this: * @code - * $accessible->getAccessDependee()->access($op, $account, TRUE); + * $accessible->getAccessDependency()->access($op, $account, TRUE); * @endcode */ interface AccessDependentInterface { @@ -26,7 +26,9 @@ * Sets the access dependency. * * @param \Drupal\Core\Access\AccessibleInterface $access_dependency - * The access dependency. + * The object upon which access depends. + * + * @return $this */ public function setAccessDependency(AccessibleInterface $access_dependency); diff --git a/core/lib/Drupal/Core/Access/AccessDependentTrait.php b/core/lib/Drupal/Core/Access/AccessDependentTrait.php index 0401736953..94f2c93d95 100644 --- a/core/lib/Drupal/Core/Access/AccessDependentTrait.php +++ b/core/lib/Drupal/Core/Access/AccessDependentTrait.php @@ -19,7 +19,7 @@ */ public function setAccessDependency(AccessibleInterface $access_dependency) { $this->accessDependency = $access_dependency; - + return $this; } /** diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install index dac00b159d..75180fc985 100644 --- a/core/modules/block_content/block_content.install +++ b/core/modules/block_content/block_content.install @@ -160,8 +160,8 @@ function block_content_update_8601() { $view = \Drupal::configFactory()->getEditable('views.view.block_content'); $base_table = 'block_content_field_data'; // Make sure the view's 'base_table' is correct and the View is not new. The - // default view could have been deleted and possibly the view ID could be used - // for a different view. + // default view may have been replaced by a completely different view with the + //same ID. if ($view->get('base_table') !== $base_table || $view->isNew()) { return; } diff --git a/core/modules/block_content/block_content.module b/core/modules/block_content/block_content.module index 463a746b05..38683b80f0 100644 --- a/core/modules/block_content/block_content.module +++ b/core/modules/block_content/block_content.module @@ -130,11 +130,9 @@ function block_content_add_body_field($block_type_id, $label = 'Body') { function block_content_query_entity_reference_alter(AlterableInterface $query) { if ($query instanceof SelectInterface && $query->getMetaData('entity_type') === 'block_content' && $query->hasTag('block_content_access')) { $data_table = \Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable(); - if (array_key_exists($data_table, $query->getTables())) { - if (!_block_content_has_reusable_condition($query->conditions())) { - // If no reusable condition create a condition set to TRUE. - $query->condition("$data_table.reusable", TRUE); - } + if (array_key_exists($data_table, $query->getTables()) && !_block_content_has_reusable_condition($query->conditions())) { + // If no reusable condition create a condition set to TRUE. + $query->condition("$data_table.reusable", TRUE); } } } @@ -152,11 +150,9 @@ function _block_content_has_reusable_condition(array $condition) { // If this is a condition group call this function recursively for each nested // condition until a condition is found that return TRUE. if (isset($condition['#conjunction'])) { - foreach ($condition as $nested_condition) { - if (is_array($nested_condition)) { - if (_block_content_has_reusable_condition($nested_condition)) { - return TRUE; - } + foreach (array_filter($condition, 'is_array') as $nested_condition) { + if (_block_content_has_reusable_condition($nested_condition)) { + return TRUE; } } return FALSE; @@ -168,6 +164,8 @@ function _block_content_has_reusable_condition(array $condition) { } $field_parts = explode('.', $field); $data_table = $data_table = \Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable(); + // With nested conditions the data table may have a suffix at the end like + // 'block_content_field_data_2'. return strpos($field_parts[0], $data_table) === 0 && $field_parts[1] === 'reusable'; } return FALSE; diff --git a/core/modules/block_content/src/BlockContentAccessControlHandler.php b/core/modules/block_content/src/BlockContentAccessControlHandler.php index 0e408d92c2..2ad950f4d0 100644 --- a/core/modules/block_content/src/BlockContentAccessControlHandler.php +++ b/core/modules/block_content/src/BlockContentAccessControlHandler.php @@ -33,9 +33,9 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter } $dependency = $entity->getAccessDependency(); if (empty($dependency)) { - return AccessResult::forbidden("Non-reusable blocks must set an access dependency for access control.")->addCacheableDependency($dependency); + return AccessResult::forbidden("Non-reusable blocks must set an access dependency for access control."); } - $access->andIf($dependency->access($operation, $account, TRUE)); + $access->andIf($dependency->access($operation, $account, TRUE))->addCacheableDependency($access); } return $access; } diff --git a/core/modules/block_content/src/BlockContentInterface.php b/core/modules/block_content/src/BlockContentInterface.php index f8b2bd2436..50fabdfb1a 100644 --- a/core/modules/block_content/src/BlockContentInterface.php +++ b/core/modules/block_content/src/BlockContentInterface.php @@ -2,6 +2,7 @@ namespace Drupal\block_content; +use Drupal\Core\Access\AccessDependentInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; use Drupal\Core\Entity\EntityPublishedInterface; @@ -10,7 +11,7 @@ /** * Provides an interface defining a custom block entity. */ -interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface { +interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface, AccessDependentInterface { /** * Returns the block revision log message. diff --git a/core/modules/block_content/src/BlockContentListBuilder.php b/core/modules/block_content/src/BlockContentListBuilder.php index 7a4bdfc4c8..88545e09b4 100644 --- a/core/modules/block_content/src/BlockContentListBuilder.php +++ b/core/modules/block_content/src/BlockContentListBuilder.php @@ -28,4 +28,20 @@ public function buildRow(EntityInterface $entity) { return $row + parent::buildRow($entity); } + /** + * {@inheritdoc} + */ + protected function getEntityIds() { + $query = $this->getStorage()->getQuery() + ->sort($this->entityType->getKey('id')); + + $query->condition('reusable', TRUE); + + // Only add the pager if a limit is specified. + if ($this->limit) { + $query->pager($this->limit); + } + return $query->execute(); + } + } diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index e382811106..2ca5bd1f7d 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -77,7 +77,7 @@ * caching. * See https://www.drupal.org/node/2284917#comment-9132521 for more information. */ -class BlockContent extends EditorialContentEntityBase implements BlockContentInterface, AccessDependentInterface { +class BlockContent extends EditorialContentEntityBase implements BlockContentInterface { use AccessDependentTrait; @@ -122,7 +122,9 @@ public function getTheme() { */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - static::invalidateBlockPluginCache(); + if ($this->isReusable() || (isset($this->original) && $this->original->isReusable())) { + static::invalidateBlockPluginCache(); + } } /** @@ -130,7 +132,14 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { */ public static function postDelete(EntityStorageInterface $storage, array $entities) { parent::postDelete($storage, $entities); - static::invalidateBlockPluginCache(); + /** @var \Drupal\block_content\BlockContentInterface $block */ + foreach ($entities as $block) { + if ($block->isReusable()) { + // If any deleted blocks are reusable clear the block cache. + static::invalidateBlockPluginCache(); + return; + } + } } /** diff --git a/core/modules/block_content/tests/src/Functional/BlockContentListTest.php b/core/modules/block_content/tests/src/Functional/BlockContentListTest.php index 9a26f1c407..8919c05d00 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentListTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentListTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\block_content\Functional; +use Drupal\block_content\Entity\BlockContent; + /** * Tests the listing of custom blocks. * @@ -104,6 +106,19 @@ public function testListing() { // Confirm that the empty text is displayed. $this->assertText(t('There are no custom blocks yet.')); + + $block_content = BlockContent::create([ + 'info' => 'Non-reusable block', + 'type' => 'basic', + 'reusable' => FALSE, + ]); + $block_content->save(); + + $this->drupalGet('admin/structure/block/block-content'); + // Confirm that the empty text is displayed. + $this->assertSession()->pageTextContains('There are no custom blocks yet.'); + // Confirm the non-reusable block is not on the page. + $this->assertSession()->pageTextNotContains('Non-reusable block'); } } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php index 1c623be82e..9c0f5fe29e 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php @@ -2,6 +2,8 @@ namespace Drupal\Tests\block_content\Functional; +use Drupal\block_content\Entity\BlockContent; + /** * Tests the Views-powered listing of custom blocks. * @@ -112,6 +114,19 @@ public function testListing() { // Confirm that the empty text is displayed. $this->assertText('There are no custom blocks available.'); $this->assertLink('custom block'); + + $block_content = BlockContent::create([ + 'info' => 'Non-reusable block', + 'type' => 'basic', + 'reusable' => FALSE, + ]); + $block_content->save(); + + $this->drupalGet('admin/structure/block/block-content'); + // Confirm that the empty text is displayed. + $this->assertSession()->pageTextContains('There are no custom blocks available.'); + // Confirm the non-reusable block is not on the page. + $this->assertSession()->pageTextNotContains('Non-reusable block'); } } diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php index b503d4f703..600fc75fab 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php @@ -30,7 +30,7 @@ public function setUp() { } /** - * Tests that reusable blocks only are derived. + * Tests that only reusable blocks are derived. */ public function testReusableBlocksOnlyAreDerived() { // Create a block content type.