diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install index fab15b4f88..e8ee49260e 100644 --- a/core/modules/block_content/block_content.install +++ b/core/modules/block_content/block_content.install @@ -140,17 +140,27 @@ function block_content_update_8400() { } /** - * Add 'reusable' field to 'block_content' entities. + * Add parent entity fields to 'block_content' entities. */ function block_content_update_8600() { - $reusable = BaseFieldDefinition::create('boolean') - ->setLabel(t('Reusable')) - ->setDescription(t('A boolean indicating whether this block is reusable.')) + $update_manager = \Drupal::entityDefinitionUpdateManager(); + $parent_entity_type = BaseFieldDefinition::create('string') + ->setLabel(t('Parent entity type')) + ->setDescription(t('The parent entity type.')) ->setTranslatable(FALSE) ->setRevisionable(FALSE) - ->setDefaultValue(TRUE) - ->setInitialValue(TRUE); + ->setDefaultValue('') + ->setInitialValue(''); - \Drupal::entityDefinitionUpdateManager() - ->installFieldStorageDefinition('reusable', 'block_content', 'block_content', $reusable); + $update_manager->installFieldStorageDefinition('parent_entity_type', 'block_content', 'block_content', $parent_entity_type); + + $parent_entity_id = BaseFieldDefinition::create('string') + ->setLabel(t('Parent ID')) + ->setDescription(t('The parent entity ID.')) + ->setTranslatable(FALSE) + ->setRevisionable(FALSE) + ->setDefaultValue('') + ->setInitialValue(''); + + $update_manager->installFieldStorageDefinition('parent_entity_id', 'block_content', 'block_content', $parent_entity_id); } diff --git a/core/modules/block_content/block_content.module b/core/modules/block_content/block_content.module index 71e1ee44d6..31435c176b 100644 --- a/core/modules/block_content/block_content.module +++ b/core/modules/block_content/block_content.module @@ -115,43 +115,43 @@ function block_content_add_body_field($block_type_id, $label = 'Body') { * Alters any 'entity_reference' query where the entity type is * 'block_content' and the query has the tag 'block_content_access'. * - * These queries should only return reusable blocks unless a condition on - * reusable is explicitly set. + * These queries should only return with no parent blocks unless a condition on + * parent is explicitly set. * - * Since block_content entities can be set to be non-reusable they should by + * Since block_content entities can be set to be have a parent they should by * default not be selectable as entity reference values. A module can still * create a instance of * \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface - * that will will allow selection of non-reusable blocks by explicitly setting - * a condition on the reusable field. + * that will will allow selection of blocks with parents by explicitly setting + * a condition on either parent_entity_id or parent_entity_type fields. * * @see \Drupal\block_content\BlockContentAccessControlHandler */ 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()) && !_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_parent_entity_condition($query->conditions())) { + // If no parent entity condition create a condition. + $query->isNull("$data_table.parent_entity_type"); } } } /** - * Utility function to find nested conditions using the reusable field. + * Utility function to find nested conditions using the parent entity fields. * * @param array $condition * The condition or condition group to check. * * @return bool - * Whether the conditions contain any condition using the reusable field. + * Whether conditions contain any condition using the parent entity fields. */ -function _block_content_has_reusable_condition(array $condition) { +function _block_content_has_parent_entity_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 (array_filter($condition, 'is_array') as $nested_condition) { - if (_block_content_has_reusable_condition($nested_condition)) { + if (_block_content_has_parent_entity_condition($nested_condition)) { return TRUE; } } @@ -160,13 +160,15 @@ function _block_content_has_reusable_condition(array $condition) { if (isset($condition['field'])) { $field = $condition['field']; if (is_object($field) && $field instanceof ConditionInterface) { - return _block_content_has_reusable_condition($field->conditions()); + return _block_content_has_parent_entity_condition($field->conditions()); } $field_parts = explode('.', $field); - $data_table = $data_table = \Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable(); + $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'; + if (strpos($field_parts[0], $data_table) === 0) { + return $field_parts[1] === 'parent_entity_id' || $field_parts[1] === 'parent_entity_type'; + } } return FALSE; } diff --git a/core/modules/block_content/block_content.post_update.php b/core/modules/block_content/block_content.post_update.php index 927e588068..d71f5758b2 100644 --- a/core/modules/block_content/block_content.post_update.php +++ b/core/modules/block_content/block_content.post_update.php @@ -6,9 +6,9 @@ */ /** - * Adds 'reusable filter to a Custom Block views. + * Adds 'has_parent' filter to Custom Block views. */ -function block_content_post_update_add_views_reusable_filter() { +function block_content_post_update_add_views_parent_filter() { $config_factory = \Drupal::configFactory(); $data_table = \Drupal::entityTypeManager() ->getDefinition('block_content') @@ -21,17 +21,17 @@ function block_content_post_update_add_views_reusable_filter() { } foreach ($view->get('display') as $display_name => $display) { // Update the default display and displays that have overridden filters. - if (!isset($display['display_options']['filters']['reusable']) && + if (!isset($display['display_options']['filters']['has_parent']) && ($display_name === 'default' || isset($display['display_options']['filters']))) { // Save off the base part of the config path we are updating. - $base = "display.$display_name.display_options.filters.reusable"; - $view->set("$base.id", 'reusable') - ->set("$base.plugin_id", 'boolean') + $base = "display.$display_name.display_options.filters.has_parent"; + $view->set("$base.id", 'has_parent') + ->set("$base.plugin_id", 'boolean_string') ->set("$base.table", $data_table) - ->set("$base.field", "reusable") - ->set("$base.value", "1") + ->set("$base.field", "has_parent") + ->set("$base.value", '0') ->set("$base.entity_type", "block_content") - ->set("$base.entity_field", "reusable"); + ->set("$base.entity_field", "parent_entity_type"); } } $view->save(); diff --git a/core/modules/block_content/config/optional/views.view.block_content.yml b/core/modules/block_content/config/optional/views.view.block_content.yml index 2c008864f3..b4de8c1859 100644 --- a/core/modules/block_content/config/optional/views.view.block_content.yml +++ b/core/modules/block_content/config/optional/views.view.block_content.yml @@ -431,15 +431,15 @@ display: entity_type: block_content entity_field: type plugin_id: bundle - reusable: - id: reusable + has_parent: + id: has_parent table: block_content_field_data - field: reusable + field: has_parent relationship: none group_type: group admin_label: '' operator: '=' - value: '1' + value: '0' group: 1 exposed: false expose: @@ -467,8 +467,8 @@ display: default_group_multiple: { } group_items: { } entity_type: block_content - entity_field: reusable - plugin_id: boolean + entity_field: parent_entity_type + plugin_id: boolean_string sorts: { } title: 'Custom block library' header: { } diff --git a/core/modules/block_content/src/BlockContentAccessControlHandler.php b/core/modules/block_content/src/BlockContentAccessControlHandler.php index 2ad950f4d0..34077b82af 100644 --- a/core/modules/block_content/src/BlockContentAccessControlHandler.php +++ b/core/modules/block_content/src/BlockContentAccessControlHandler.php @@ -2,7 +2,6 @@ namespace Drupal\block_content; -use Drupal\Core\Access\AccessDependentInterface; use Drupal\Core\Access\AccessResult; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityAccessControlHandler; @@ -27,15 +26,14 @@ protected function checkAccess(EntityInterface $entity, $operation, AccountInter $access = parent::checkAccess($entity, $operation, $account); } /** @var \Drupal\block_content\BlockContentInterface $entity */ - if ($entity->isReusable() === FALSE) { - if (!$entity instanceof AccessDependentInterface) { - throw new \LogicException("Non-reusable block entities must implement \Drupal\Core\Access\AccessDependentInterface for access control."); + if ($entity->hasParentEntity()) { + if ($parent_entity = $entity->getParentEntity()) { + $access = $access->andIf($parent_entity->access($operation, $account, TRUE))->addCacheableDependency($entity); } - $dependency = $entity->getAccessDependency(); - if (empty($dependency)) { - return AccessResult::forbidden("Non-reusable blocks must set an access dependency for access control."); + else { + // The entity has a parent but it was not able to be loaded. + return AccessResult::forbidden('Parent entity not available.')->addCacheableDependency($entity); } - $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 50fabdfb1a..16d427561e 100644 --- a/core/modules/block_content/src/BlockContentInterface.php +++ b/core/modules/block_content/src/BlockContentInterface.php @@ -2,16 +2,16 @@ namespace Drupal\block_content; -use Drupal\Core\Access\AccessDependentInterface; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityChangedInterface; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityPublishedInterface; use Drupal\Core\Entity\RevisionLogInterface; /** * Provides an interface defining a custom block entity. */ -interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface, AccessDependentInterface { +interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface { /** * Returns the block revision log message. @@ -49,24 +49,6 @@ public function setInfo($info); */ public function setRevisionLog($revision_log); - /** - * Determines if the block is reusable or not. - * - * @return bool - * Returns TRUE if reusable and FALSE otherwise. - */ - public function isReusable(); - - /** - * Sets the block to be reusable. - * - * @param bool $reusable - * Whether the block should be reusable, defaults to TRUE. - * - * @return $this - */ - public function setReusable($reusable = TRUE); - /** * Sets the theme value. * @@ -102,4 +84,41 @@ public function getTheme(); */ public function getInstances(); + /** + * Sets the parent entity. + * + * @param \Drupal\Core\Entity\EntityInterface $parent_entity + * The parent entity. + * + * @return \Drupal\block_content\BlockContentInterface + * The class instance that this method is called on. + */ + public function setParentEntity(EntityInterface $parent_entity); + + /** + * Gets the parent entity. + * + * @return \Drupal\Core\Entity\EntityInterface|null + * The parent entity or null if none exists. + * + * @todo How to deterine parent is set but no longer exists. + */ + public function getParentEntity(); + + /** + * Removes the parent entity. + * + * @return \Drupal\block_content\BlockContentInterface + * The class instance that this method is called on. + */ + public function removeParentEntity(); + + /** + * Whether the block has a parent entity set. + * + * @return bool + * TRUE if a parent entity is set, otherwise FALSE. + */ + public function hasParentEntity(); + } diff --git a/core/modules/block_content/src/BlockContentListBuilder.php b/core/modules/block_content/src/BlockContentListBuilder.php index 88545e09b4..8467c3c3e0 100644 --- a/core/modules/block_content/src/BlockContentListBuilder.php +++ b/core/modules/block_content/src/BlockContentListBuilder.php @@ -33,9 +33,8 @@ public function buildRow(EntityInterface $entity) { */ protected function getEntityIds() { $query = $this->getStorage()->getQuery() - ->sort($this->entityType->getKey('id')); - - $query->condition('reusable', TRUE); + ->sort($this->entityType->getKey('id')) + ->notExists('parent_entity_type'); // Only add the pager if a limit is specified. if ($this->limit) { diff --git a/core/modules/block_content/src/BlockContentViewsData.php b/core/modules/block_content/src/BlockContentViewsData.php index e9ff0eb4cd..de3df7af6a 100644 --- a/core/modules/block_content/src/BlockContentViewsData.php +++ b/core/modules/block_content/src/BlockContentViewsData.php @@ -21,6 +21,18 @@ public function getViewsData() { $data['block_content_field_data']['info']['field']['id'] = 'field'; $data['block_content_field_data']['info']['field']['link_to_entity default'] = TRUE; + $data['block_content_field_data']['has_parent'] = [ + 'title' => $this->t('Has Parent'), + 'help' => $this->t('Whether the block has a parent'), + 'field' => ['id' => 'field'], + 'filter' => [ + 'id' => 'boolean_string', + 'accept_null' => TRUE, + ], + 'entity field' => 'parent_entity_type', + 'real field' => 'parent_entity_type', + ]; + $data['block_content_field_data']['type']['field']['id'] = 'field'; $data['block_content_field_data']['table']['wizard_id'] = 'block_content'; diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php index 2995e99cec..ba5002f634 100644 --- a/core/modules/block_content/src/Entity/BlockContent.php +++ b/core/modules/block_content/src/Entity/BlockContent.php @@ -2,8 +2,8 @@ namespace Drupal\block_content\Entity; -use Drupal\Core\Access\AccessDependentTrait; use Drupal\Core\Entity\EditorialContentEntityBase; +use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Field\BaseFieldDefinition; @@ -78,8 +78,6 @@ */ class BlockContent extends EditorialContentEntityBase implements BlockContentInterface { - use AccessDependentTrait; - /** * The theme the block is being created in. * @@ -121,7 +119,7 @@ public function getTheme() { */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - if ($this->isReusable() || (isset($this->original) && $this->original->isReusable())) { + if (empty($this->get('parent_entity_type')->value) || (isset($this->original) && empty($this->original->get('parent_entity_type')->value))) { static::invalidateBlockPluginCache(); } } @@ -133,8 +131,8 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti parent::postDelete($storage, $entities); /** @var \Drupal\block_content\BlockContentInterface $block */ foreach ($entities as $block) { - if ($block->isReusable()) { - // If any deleted blocks are reusable clear the block cache. + if (!$block->hasParentEntity()) { + // If any deleted blocks do not have a parent ID clear the block cache. static::invalidateBlockPluginCache(); return; } @@ -212,13 +210,23 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setTranslatable(TRUE) ->setRevisionable(TRUE); - $fields['reusable'] = BaseFieldDefinition::create('boolean') - ->setLabel(t('Reusable')) - ->setDescription(t('A boolean indicating whether this block is reusable.')) + // @todo Is there a core issue to add + // https://www.drupal.org/project/dynamic_entity_reference + $fields['parent_entity_type'] = BaseFieldDefinition::create('string') + ->setLabel(t('Parent entity type')) + ->setDescription(t('The parent entity type.')) + ->setTranslatable(FALSE) + ->setRevisionable(FALSE) + ->setDefaultValue(NULL) + ->setInitialValue(NULL); + + $fields['parent_entity_id'] = BaseFieldDefinition::create('string') + ->setLabel(t('Parent ID')) + ->setDescription(t('The parent entity ID.')) ->setTranslatable(FALSE) ->setRevisionable(FALSE) - ->setDefaultValue(TRUE) - ->setInitialValue(TRUE); + ->setDefaultValue(NULL) + ->setInitialValue(NULL); return $fields; } @@ -302,26 +310,48 @@ public function setRevisionLogMessage($revision_log_message) { return $this; } + /** + * Invalidates the block plugin cache after changes and deletions. + */ + protected static function invalidateBlockPluginCache() { + // Invalidate the block cache to update custom block-based derivatives. + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + } + /** * {@inheritdoc} */ - public function isReusable() { - return (bool) $this->get('reusable')->value; + public function setParentEntity(EntityInterface $parent_entity) { + $this->set('parent_entity_type', $parent_entity->getEntityTypeId()); + $this->set('parent_entity_id', $parent_entity->id()); + return $this; } /** * {@inheritdoc} */ - public function setReusable($reusable = TRUE) { - return $this->set('reusable', $reusable); + public function getParentEntity() { + if ($this->hasParentEntity()) { + return \Drupal::entityTypeManager()->getStorage($this->get('parent_entity_type')->value)->load($this->get('parent_entity_id')->value); + } + return NULL; } /** - * Invalidates the block plugin cache after changes and deletions. + * {@inheritdoc} */ - protected static function invalidateBlockPluginCache() { - // Invalidate the block cache to update custom block-based derivatives. - \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); + public function removeParentEntity() { + $this->set('parent_entity_type', NULL); + $this->set('parent_entity_id', NULL); + return $this; } + /** + * {@inheritdoc} + */ + public function hasParentEntity() { + // If either parent field value is set then the block is considered to have + // a parent. + return !empty($this->get('parent_entity_type')->value) || !empty($this->get('parent_entity_id')->value); + } } diff --git a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php index ba1ab98968..edf82bc46a 100644 --- a/core/modules/block_content/src/Plugin/Derivative/BlockContent.php +++ b/core/modules/block_content/src/Plugin/Derivative/BlockContent.php @@ -43,10 +43,11 @@ public static function create(ContainerInterface $container, $base_plugin_id) { * {@inheritdoc} */ public function getDerivativeDefinitions($base_plugin_definition) { - $block_contents = $this->blockContentStorage->loadByProperties(['reusable' => TRUE]); + $block_ids = $this->blockContentStorage->getQuery()->notExists('parent_entity_type')->execute(); + $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/src/Plugin/views/wizard/BlockContent.php b/core/modules/block_content/src/Plugin/views/wizard/BlockContent.php index 6072501537..6c4bfcf336 100644 --- a/core/modules/block_content/src/Plugin/views/wizard/BlockContent.php +++ b/core/modules/block_content/src/Plugin/views/wizard/BlockContent.php @@ -20,14 +20,15 @@ class BlockContent extends WizardPluginBase { */ public function getFilters() { $filters = parent::getFilters(); - $filters['reusable'] = [ - 'id' => 'reusable', - 'plugin_id' => 'boolean', + $filters['has_parent'] = [ + 'id' => 'has_parent', + 'plugin_id' => 'boolean_string', 'table' => $this->base_table, - 'field' => 'reusable', - 'value' => '1', + 'field' => 'has_parent', + 'operator' => '=', + 'value' => '0', 'entity_type' => $this->entityTypeId, - 'entity_field' => 'reusable', + 'entity_field' => 'parent_entity_type', ]; return $filters; } diff --git a/core/modules/block_content/tests/modules/block_content_test/src/Plugin/EntityReferenceSelection/TestSelection.php b/core/modules/block_content/tests/modules/block_content_test/src/Plugin/EntityReferenceSelection/TestSelection.php index 82b8aa90dc..1c3f753478 100644 --- a/core/modules/block_content/tests/modules/block_content_test/src/Plugin/EntityReferenceSelection/TestSelection.php +++ b/core/modules/block_content/tests/modules/block_content_test/src/Plugin/EntityReferenceSelection/TestSelection.php @@ -5,7 +5,7 @@ use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection; /** - * Test EntityReferenceSelection class that adds various 'reusable' conditions. + * Test EntityReferenceSelection that adds various parent entity conditions. */ class TestSelection extends DefaultSelection { @@ -31,33 +31,35 @@ public function setTestMode($testMode) { */ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') { $query = parent::buildEntityQuery($match, $match_operator); + if (strpos($this->testMode, 'parent_entity_id') === 0) { + $field = 'parent_entity_id'; + } + else { + $field = 'parent_entity_type'; + } switch ($this->testMode) { - case 'reusable_condition_false': - $query->condition("reusable", 0); - break; - - case 'reusable_condition_exists': - $query->exists('reusable'); + case "{$field}_condition_false": + $query->notExists($field); break; - case 'reusable_condition_group_false': + case "{$field}_condition_group_false": $group = $query->andConditionGroup() - ->condition("reusable", 0) + ->notExists($field) ->exists('type'); $query->condition($group); break; - case 'reusable_condition_group_true': + case "{$field}_condition_group_true": $group = $query->andConditionGroup() - ->condition("reusable", 1) + ->exists($field) ->exists('type'); $query->condition($group); break; - case 'reusable_condition_nested_group_false': + case "{$field}_condition_nested_group_false": $query->exists('type'); $sub_group = $query->andConditionGroup() - ->condition("reusable", 0) + ->notExists($field) ->exists('type'); $group = $query->andConditionGroup() ->exists('type') @@ -65,10 +67,10 @@ protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') $query->condition($group); break; - case 'reusable_condition_nested_group_true': + case "{$field}_condition_nested_group_true": $query->exists('type'); $sub_group = $query->andConditionGroup() - ->condition("reusable", 1) + ->exists($field) ->exists('type'); $group = $query->andConditionGroup() ->exists('type') diff --git a/core/modules/block_content/tests/modules/block_content_view_override/config/install/views.view.block_content.yml b/core/modules/block_content/tests/modules/block_content_view_override/config/install/views.view.block_content.yml index a3cc406077..1c95f9f854 100644 --- a/core/modules/block_content/tests/modules/block_content_view_override/config/install/views.view.block_content.yml +++ b/core/modules/block_content/tests/modules/block_content_view_override/config/install/views.view.block_content.yml @@ -4,8 +4,6 @@ dependencies: module: - block_content - user -_core: - default_config_hash: gkRJCqHr3uSO8ALHLatX-7YKfX0lWEgkC5qMBtCf_Sg id: block_content label: 'Custom block library' module: views diff --git a/core/modules/block_content/tests/src/Functional/BlockContentListTest.php b/core/modules/block_content/tests/src/Functional/BlockContentListTest.php index 8919c05d00..12a8dcc129 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentListTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentListTest.php @@ -108,17 +108,17 @@ public function testListing() { $this->assertText(t('There are no custom blocks yet.')); $block_content = BlockContent::create([ - 'info' => 'Non-reusable block', + 'info' => 'Block with parent', 'type' => 'basic', - 'reusable' => FALSE, ]); + $block_content->setParentEntity($this->loggedInUser); $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'); + // Confirm the block with a parent is not on the page. + $this->assertSession()->pageTextNotContains('Block with parent'); } } diff --git a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php index f9cf29eb77..8a229e742c 100644 --- a/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php +++ b/core/modules/block_content/tests/src/Functional/BlockContentListViewsTest.php @@ -116,17 +116,17 @@ public function testListing() { $this->assertLink('custom block'); $block_content = BlockContent::create([ - 'info' => 'Non-reusable block', + 'info' => 'Block with parent', 'type' => 'basic', - 'reusable' => FALSE, ]); + $block_content->setParentEntity($this->loggedInUser); $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'); + // Confirm the Block with parent is not on the page. + $this->assertSession()->pageTextNotContains('Block with parent'); } } 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 4a3ac11f4c..13b2df05b1 100644 --- a/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php +++ b/core/modules/block_content/tests/src/Functional/Rest/BlockContentResourceTestBase.php @@ -92,11 +92,8 @@ protected function getExpectedNormalizedEntity() { 'value' => 'en', ], ], - 'reusable' => [ - [ - 'value' => TRUE, - ], - ], + 'parent_entity_type' => [], + 'parent_entity_id' => [], '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 new file mode 100644 index 0000000000..bd7cf0d771 --- /dev/null +++ b/core/modules/block_content/tests/src/Functional/Update/BlockContentParentEntityUpdateTest.php @@ -0,0 +1,168 @@ +databaseDumpFiles = [ + __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', + ]; + } + + /** + * Tests adding parent entity fields to the block content entity type. + * + * @see block_content_update_8600 + * @see block_content_post_update_add_views_parent_filter + */ + public function testParentFieldsAddition() { + $assert_session = $this->assertSession(); + $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); + + // Delete custom block library view. + View::load('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 + // This display also a filter only show blocks that contain 'block2' in the + // 'info' field. + $this->container->get('module_installer')->install(['block_content_view_override']); + + // Run updates. + $this->runUpdates(); + + // Check that the 'parent_entity_type' field exists and is configured + // correctly. + $parent_type_field = $entity_definition_update_manager->getFieldStorageDefinition('parent_entity_type', 'block_content'); + $this->assertEquals('Parent entity type', $parent_type_field->getLabel()); + $this->assertEquals('The parent entity type.', $parent_type_field->getDescription()); + $this->assertEquals(FALSE, $parent_type_field->isRevisionable()); + $this->assertEquals(FALSE, $parent_type_field->isTranslatable()); + + // Check that the 'parent_entity_id' field exists and is configured + // correctly. + $parent_id_field = $entity_definition_update_manager->getFieldStorageDefinition('parent_entity_id', 'block_content'); + $this->assertEquals('Parent ID', $parent_id_field->getLabel()); + $this->assertEquals('The parent entity ID.', $parent_id_field->getDescription()); + $this->assertEquals(FALSE, $parent_id_field->isRevisionable()); + $this->assertEquals(FALSE, $parent_id_field->isTranslatable()); + + $after_block1 = BlockContent::create([ + 'info' => 'After update block1', + 'type' => 'basic_block', + ]); + $after_block1->save(); + // Add second block that will be shown with the 'info' filter on the + // additional view display. + $after_block2 = BlockContent::create([ + 'info' => 'After update block2', + 'type' => 'basic_block', + ]); + $after_block2->save(); + + $this->assertEquals(FALSE, $after_block1->hasParentEntity()); + $this->assertEquals(FALSE, $after_block2->hasParentEntity()); + + $admin_user = $this->drupalCreateUser(['administer blocks']); + $this->drupalLogin($admin_user); + + $block_with_parent = BlockContent::create([ + 'info' => 'block1 with parent', + 'type' => 'basic_block', + ]); + $block_with_parent->setParentEntity($admin_user); + $block_with_parent->save(); + // Add second block that would be shown with the 'info' filter on the + // additional view display if the 'has_parent' filter was not added. + $block2_with_parent = BlockContent::create([ + 'info' => 'block2 with parent', + 'type' => 'basic_block', + ]); + $block2_with_parent->setParentEntity($admin_user); + $block2_with_parent->save(); + $this->assertEquals(TRUE, $block_with_parent->hasParentEntity()); + $this->assertEquals(TRUE, $block2_with_parent->hasParentEntity()); + + + + // Ensure the Custom Block view shows the blocks without parents only. + $this->drupalGet('admin/structure/block/block-content'); + file_put_contents('/Users/ted.bowman/Sites/www/test.html', $this->getSession()->getPage()->getOuterHtml()); + $assert_session->statusCodeEquals('200'); + $assert_session->responseContains('view-id-block_content'); + $assert_session->pageTextContains($after_block1->label()); + $assert_session->pageTextContains($after_block2->label()); + $assert_session->pageTextNotContains($block_with_parent->label()); + $assert_session->pageTextNotContains($block2_with_parent->label()); + + // Ensure the view's other display also only shows blocks without parent and + // still filters on the 'info' field. + $this->drupalGet('extra-view-display'); + $assert_session->statusCodeEquals('200'); + $assert_session->responseContains('view-id-block_content'); + $assert_session->pageTextNotContains($after_block1->label()); + $assert_session->pageTextContains($after_block2->label()); + $assert_session->pageTextNotContains($block_with_parent->label()); + $assert_session->pageTextNotContains($block2_with_parent->label()); + + // Ensure the Custom Block listing without Views installed shows the only + // blocks without parents. + $this->drupalGet('admin/structure/block/block-content'); + $this->container->get('module_installer')->uninstall(['views_ui', 'views']); + $this->drupalGet('admin/structure/block/block-content'); + $assert_session->statusCodeEquals('200'); + $assert_session->responseNotContains('view-id-block_content'); + $assert_session->pageTextContains($after_block1->label()); + $assert_session->pageTextContains($after_block2->label()); + $assert_session->pageTextNotContains($block_with_parent->label()); + $assert_session->pageTextNotContains($block2_with_parent->label()); + + $this->drupalGet('block/' . $after_block1->id()); + $assert_session->statusCodeEquals('200'); + + // Ensure the user who can access a block parent they can access edit form + // edit route is not accessible. + $this->drupalGet('block/' . $block_with_parent->id()); + $assert_session->statusCodeEquals('200'); + + $this->drupalLogout(); + + $this->drupalLogin($this->createUser([ + 'access user profiles', + 'administer blocks', + ])); + $this->drupalGet('block/' . $after_block1->id()); + $assert_session->statusCodeEquals('200'); + + $this->drupalGet('block/' . $block_with_parent->id()); + $assert_session->statusCodeEquals('403'); + + $this->drupalLogin($this->createUser([ + 'administer blocks', + ])); + + $this->drupalGet('block/' . $after_block1->id()); + $assert_session->statusCodeEquals('200'); + + $this->drupalGet('user/' . $admin_user->id()); + $assert_session->statusCodeEquals('403'); + + $this->drupalGet('block/' . $block_with_parent->id()); + $assert_session->statusCodeEquals('403'); + + } + +} diff --git a/core/modules/block_content/tests/src/Functional/Update/BlockContentReusableUpdateTest.php b/core/modules/block_content/tests/src/Functional/Update/BlockContentReusableUpdateTest.php deleted file mode 100644 index 7803f40813..0000000000 --- a/core/modules/block_content/tests/src/Functional/Update/BlockContentReusableUpdateTest.php +++ /dev/null @@ -1,131 +0,0 @@ -databaseDumpFiles = [ - __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.4.0.bare.standard.php.gz', - ]; - } - - /** - * Tests adding a reusable field to the block content entity type. - * - * @see block_content_update_8600 - * @see block_content_post_update_add_views_reusable_filter - */ - public function testReusableFieldAddition() { - $assert_session = $this->assertSession(); - $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager(); - - // Delete custom block library view. - View::load('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 a - // filter added for 'reusable' field so that it does not expose non-reusable - // fields. This display also a filter only show blocks that contain - // 'block2' in the 'info' field. - $this->container->get('module_installer')->install(['block_content_view_override']); - - // Run updates. - $this->runUpdates(); - - // Check that the field exists and is configured correctly. - $reusable_field = $entity_definition_update_manager->getFieldStorageDefinition('reusable', 'block_content'); - $this->assertEquals('Reusable', $reusable_field->getLabel()); - $this->assertEquals('A boolean indicating whether this block is reusable.', $reusable_field->getDescription()); - $this->assertEquals(FALSE, $reusable_field->isRevisionable()); - $this->assertEquals(FALSE, $reusable_field->isTranslatable()); - - $after_block1 = BlockContent::create([ - 'info' => 'After update block1', - 'type' => 'basic_block', - ]); - $after_block1->save(); - // Add second block that will be shown with the 'info' filter on the - // additional view display. - $after_block2 = BlockContent::create([ - 'info' => 'After update block2', - 'type' => 'basic_block', - ]); - $after_block2->save(); - - $this->assertEquals(TRUE, $after_block1->isReusable()); - $this->assertEquals(TRUE, $after_block2->isReusable()); - - $non_reusable_block = BlockContent::create([ - 'info' => 'non-reusable block1', - 'type' => 'basic_block', - 'reusable' => FALSE, - ]); - $non_reusable_block->save(); - // Add second block that will be would shown with the 'info' filter on the - // additional view display if the 'reusable filter was not added. - $non_reusable_block2 = BlockContent::create([ - 'info' => 'non-reusable block2', - 'type' => 'basic_block', - 'reusable' => FALSE, - ]); - $non_reusable_block2->save(); - $this->assertEquals(FALSE, $non_reusable_block->isReusable()); - $this->assertEquals(FALSE, $non_reusable_block2->isReusable()); - - $admin_user = $this->drupalCreateUser(['administer blocks']); - $this->drupalLogin($admin_user); - - // Ensure the Custom Block view shows the reusable blocks but not - // the non-reusable block. - $this->drupalGet('admin/structure/block/block-content'); - $assert_session->statusCodeEquals('200'); - $assert_session->responseContains('view-id-block_content'); - $assert_session->pageTextContains($after_block1->label()); - $assert_session->pageTextContains($after_block2->label()); - $assert_session->pageTextNotContains($non_reusable_block->label()); - $assert_session->pageTextNotContains($non_reusable_block2->label()); - - // Ensure the views other display also filters out non-reusable blocks and - // still filters on the 'info' field. - $this->drupalGet('extra-view-display'); - $assert_session->statusCodeEquals('200'); - $assert_session->responseContains('view-id-block_content'); - $assert_session->pageTextNotContains($after_block1->label()); - $assert_session->pageTextContains($after_block2->label()); - $assert_session->pageTextNotContains($non_reusable_block->label()); - $assert_session->pageTextNotContains($non_reusable_block2->label()); - - $this->drupalGet('block/' . $after_block1->id()); - $assert_session->statusCodeEquals('200'); - - // Ensure that non-reusable blocks edit form edit route is not accessible. - $this->drupalGet('block/' . $non_reusable_block->id()); - $assert_session->statusCodeEquals('403'); - - // Ensure the Custom Block listing without Views installed shows the - // reusable blocks but not the non-reusable blocks. - // the non-reusable block. - $this->drupalGet('admin/structure/block/block-content'); - $this->container->get('module_installer')->uninstall(['views_ui', 'views']); - $this->drupalGet('admin/structure/block/block-content'); - $assert_session->statusCodeEquals('200'); - $assert_session->responseNotContains('view-id-block_content'); - $assert_session->pageTextContains($after_block1->label()); - $assert_session->pageTextContains($after_block2->label()); - $assert_session->pageTextNotContains($non_reusable_block->label()); - $assert_session->pageTextNotContains($non_reusable_block2->label()); - } - -} diff --git a/core/modules/block_content/tests/src/Functional/Views/BlockContentWizardTest.php b/core/modules/block_content/tests/src/Functional/Views/BlockContentWizardTest.php index 2c59e5c50f..e765dd6684 100644 --- a/core/modules/block_content/tests/src/Functional/Views/BlockContentWizardTest.php +++ b/core/modules/block_content/tests/src/Functional/Views/BlockContentWizardTest.php @@ -43,10 +43,10 @@ public function testViewAddBlockContent() { $display_options = $view->getDisplay('default')['display_options']; - $this->assertEquals('block_content', $display_options['filters']['reusable']['entity_type']); - $this->assertEquals('reusable', $display_options['filters']['reusable']['entity_field']); - $this->assertEquals('boolean', $display_options['filters']['reusable']['plugin_id']); - $this->assertEquals('1', $display_options['filters']['reusable']['value']); + $this->assertEquals('block_content', $display_options['filters']['has_parent']['entity_type']); + $this->assertEquals('parent_entity_type', $display_options['filters']['has_parent']['entity_field']); + $this->assertEquals('boolean_string', $display_options['filters']['has_parent']['plugin_id']); + $this->assertEquals('0', $display_options['filters']['has_parent']['value']); } } diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php index 600fc75fab..dededb611a 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentDeriverTest.php @@ -6,6 +6,7 @@ use Drupal\block_content\Entity\BlockContentType; use Drupal\Component\Plugin\PluginBase; use Drupal\KernelTests\KernelTestBase; +use Drupal\user\Entity\User; /** * Tests block content plugin deriver. @@ -25,14 +26,15 @@ class BlockContentDeriverTest extends KernelTestBase { public function setUp() { parent::setUp(); $this->installSchema('system', ['sequence']); + $this->installSchema('system', ['sequences']); $this->installEntitySchema('user'); $this->installEntitySchema('block_content'); } /** - * Tests that only reusable blocks are derived. + * Tests that block with parents are not derived. */ - public function testReusableBlocksOnlyAreDerived() { + public function testBlocksWithParentsNotDerived() { // Create a block content type. $block_content_type = BlockContentType::create([ 'id' => 'spiffy', @@ -47,18 +49,23 @@ public function testReusableBlocksOnlyAreDerived() { ]); $block_content->save(); - // Ensure the reusable block content is provided as a derivative block + // Ensure block entity with no parent is provided as a derivative block // plugin. /** @var \Drupal\Core\Block\BlockManagerInterface $block_manager */ $block_manager = $this->container->get('plugin.manager.block'); $plugin_id = 'block_content' . PluginBase::DERIVATIVE_SEPARATOR . $block_content->uuid(); $this->assertTrue($block_manager->hasDefinition($plugin_id)); - // Set the block not to be reusable. - $block_content->setReusable(FALSE); + // Set the block not to have a parent. + $user = User::create([ + 'name' => 'username', + 'status' => 1, + ]); + $user->save(); + $block_content->setParentEntity($user); $block_content->save(); - // Ensure the non-reusable block content is not provided a derivative block + // Ensure the block content with a parent is not provided a derivative block // plugin. $this->assertFalse($block_manager->hasDefinition($plugin_id)); } diff --git a/core/modules/block_content/tests/src/Kernel/BlockContentEntityReferenceSelectionTest.php b/core/modules/block_content/tests/src/Kernel/BlockContentEntityReferenceSelectionTest.php index 882e653eaf..d401c56ebc 100644 --- a/core/modules/block_content/tests/src/Kernel/BlockContentEntityReferenceSelectionTest.php +++ b/core/modules/block_content/tests/src/Kernel/BlockContentEntityReferenceSelectionTest.php @@ -6,9 +6,10 @@ use Drupal\block_content\Entity\BlockContentType; use Drupal\block_content_test\Plugin\EntityReferenceSelection\TestSelection; use Drupal\KernelTests\KernelTestBase; +use Drupal\user\Entity\User; /** - * Tests EntityReference selection handlers don't return non-reusable blocks. + * Tests EntityReference selection handlers don't return blocks with parents. * * @see block_content_query_block_content_access_alter() * @@ -40,6 +41,7 @@ class BlockContentEntityReferenceSelectionTest extends KernelTestBase { public function setUp() { parent::setUp(); $this->installSchema('system', ['sequence']); + $this->installSchema('system', ['sequences']); $this->installEntitySchema('user'); $this->installEntitySchema('block_content'); @@ -54,26 +56,31 @@ public function setUp() { } /** - * Tests that non-reusable blocks are not referenceable entities. + * Tests that blocks with parent are not referenceable entities. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException * @throws \Exception */ public function testReferenceableEntities() { - // And reusable and non-reusable block content entities. - $block_content_reusable = BlockContent::create([ - 'info' => 'Reusable Block', + $user = User::create([ + 'name' => 'username', + 'status' => 1, + ]); + $user->save(); + + // And block content entities with and without parents. + $block_content = BlockContent::create([ + 'info' => 'Block no parent', 'type' => 'spiffy', - 'reusable' => TRUE, ]); - $block_content_reusable->save(); - $block_content_nonreusable = BlockContent::create([ - 'info' => 'Non-reusable Block', + $block_content->save(); + $block_content_with_parent = BlockContent::create([ + 'info' => 'Block with parent', 'type' => 'spiffy', - 'reusable' => FALSE, ]); - $block_content_nonreusable->save(); + $block_content_with_parent->setParentEntity($user); + $block_content_with_parent->save(); // Ensure that queries without all the tags are not altered. $query = $this->entityTypeManager->getStorage('block_content')->getQuery(); @@ -89,7 +96,7 @@ public function testReferenceableEntities() { // Use \Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection // class to test that getReferenceableEntities() does not get the - // non-reusable entity. + // entity wth a parent. $configuration = [ 'target_type' => 'block_content', 'target_bundles' => ['spiffy' => 'spiffy'], @@ -99,61 +106,57 @@ public function testReferenceableEntities() { // Setup the 3 expectation cases. $both_blocks = [ 'spiffy' => [ - $block_content_reusable->id() => $block_content_reusable->label(), - $block_content_nonreusable->id() => $block_content_nonreusable->label(), + $block_content->id() => $block_content->label(), + $block_content_with_parent->id() => $block_content_with_parent->label(), ], ]; - $reusable_block = ['spiffy' => [$block_content_reusable->id() => $block_content_reusable->label()]]; - $non_reusable_block = ['spiffy' => [$block_content_nonreusable->id() => $block_content_nonreusable->label()]]; + $block_no_parent = ['spiffy' => [$block_content->id() => $block_content->label()]]; + $block_with_parent = ['spiffy' => [$block_content_with_parent->id() => $block_content_with_parent->label()]]; $this->assertEquals( - $reusable_block, + $block_no_parent, $selection_handler->getReferenceableEntities() ); // Test various ways in which an EntityReferenceSelection plugin could set - // the 'reusable' condition. If the plugin has set a condition on 'reusable' - // at all then 'block_content_query_entity_reference_alter()' will not set - // a reusable condition. - $selection_handler->setTestMode('reusable_condition_false'); - $this->assertEquals( - $non_reusable_block, - $selection_handler->getReferenceableEntities() - ); - - $selection_handler->setTestMode('reusable_condition_exists'); - $this->assertEquals( - $both_blocks, - $selection_handler->getReferenceableEntities() - ); - - $selection_handler->setTestMode('reusable_condition_group_false'); - $this->assertEquals( - $non_reusable_block, - $selection_handler->getReferenceableEntities() - ); - - $selection_handler->setTestMode('reusable_condition_group_true'); - $this->assertEquals( - $reusable_block, - $selection_handler->getReferenceableEntities() - ); - - $selection_handler->setTestMode('reusable_condition_nested_group_false'); - $this->assertEquals( - $non_reusable_block, - $selection_handler->getReferenceableEntities() - ); - - $selection_handler->setTestMode('reusable_condition_nested_group_true'); - $this->assertEquals( - $reusable_block, - $selection_handler->getReferenceableEntities() - ); - - // Change the block to reusable. - $block_content_nonreusable->setReusable(TRUE); - $block_content_nonreusable->save(); + // a condition on either the 'parent_entity_id' or 'parent_entity_type' + // fields. If the plugin has set a condition on either of these fields + // then 'block_content_query_entity_reference_alter()' will not set + // a parent condition. + foreach (['parent_entity_id', 'parent_entity_type'] as $field) { + $selection_handler->setTestMode("{$field}_condition_false"); + $this->assertEquals( + $block_no_parent, + $selection_handler->getReferenceableEntities() + ); + + $selection_handler->setTestMode("{$field}_condition_group_false"); + $this->assertEquals( + $block_no_parent, + $selection_handler->getReferenceableEntities() + ); + + $selection_handler->setTestMode("{$field}_condition_group_true"); + $this->assertEquals( + $block_with_parent, + $selection_handler->getReferenceableEntities() + ); + + $selection_handler->setTestMode("{$field}_condition_nested_group_false"); + $this->assertEquals( + $block_no_parent, + $selection_handler->getReferenceableEntities() + ); + + $selection_handler->setTestMode("{$field}_condition_nested_group_true"); + $this->assertEquals( + $block_with_parent, + $selection_handler->getReferenceableEntities() + ); + } + + $block_content_with_parent->removeParentEntity(); + $block_content_with_parent->save(); // Don't use any conditions. $selection_handler->setTestMode(NULL); // Ensure that the block is now returned as a referenceable entity.