diff --git a/modules/ctools_inline_block/config/schema/ctools_inline_block.schema.yml b/modules/ctools_inline_block/config/schema/ctools_inline_block.schema.yml new file mode 100644 index 0000000..c06d215 --- /dev/null +++ b/modules/ctools_inline_block/config/schema/ctools_inline_block.schema.yml @@ -0,0 +1,10 @@ +block.settings.inline_block:*: + type: block_settings + label: 'Inline block content' + mapping: + langcode: + type: string + label: 'Language code' + entity: + type: string + label: 'Serialized block content entity' diff --git a/modules/ctools_inline_block/ctools_inline_block.info.yml b/modules/ctools_inline_block/ctools_inline_block.info.yml new file mode 100644 index 0000000..34fb158 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.info.yml @@ -0,0 +1,10 @@ +name: 'Inline Blocks' +type: module +description: 'Exposes the custom block functionality of Drupal core as inline blocks what do not save to the database.' +package: 'Chaos tool suite' +version: 3.x +core: 8.x +dependencies: + - block_content + - ctools_entity_mask + - inline_entity_form diff --git a/modules/ctools_inline_block/ctools_inline_block.install b/modules/ctools_inline_block/ctools_inline_block.install new file mode 100644 index 0000000..41d9b69 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.install @@ -0,0 +1,32 @@ + [ + 'fields' => [ + 'uuid' => [ + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'UUID of the inline block.', + ], + 'service_id' => [ + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'ID of the service which can load the block.', + ], + 'arguments' => [ + 'type' => 'text', + 'description' => 'Serialized array of additional data needed to load the block.', + 'serialize' => TRUE, + 'not null' => TRUE, + ], + ], + 'primary key' => ['uuid'], + ], + ]; +} diff --git a/modules/ctools_inline_block/ctools_inline_block.module b/modules/ctools_inline_block/ctools_inline_block.module new file mode 100644 index 0000000..5cee38d --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.module @@ -0,0 +1,49 @@ +getPlugin(); + + if ($plugin instanceof InlineBlock) { + \Drupal::database() + ->insert('inline_block') + ->fields([ + 'uuid' => $plugin->getEntity()->uuid(), + 'service_id' => 'inline_block.loader.default', + 'arguments' => serialize([ + $block->id(), + ]), + ]) + ->execute(); + } +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function ctools_inline_block_block_delete(BlockInterface $block) { + $plugin = $block->getPlugin(); + + if ($plugin instanceof InlineBlock) { + $plugin->getEntity()->delete(); + } +} + +/** + * Implements hook_ENTITY_TYPE_insert(). + */ +function ctools_inline_block_block_content_type_insert() { + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); +} + +/** + * Implements hook_ENTITY_TYPE_delete(). + */ +function ctools_inline_block_block_content_type_delete() { + \Drupal::service('plugin.manager.block')->clearCachedDefinitions(); +} diff --git a/modules/ctools_inline_block/ctools_inline_block.services.yml b/modules/ctools_inline_block/ctools_inline_block.services.yml new file mode 100644 index 0000000..02caae3 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.services.yml @@ -0,0 +1,5 @@ +services: + inline_block.loader.default: + class: '\Drupal\ctools_inline_block\DefaultBlockLoader' + arguments: + - '@entity_type.manager' diff --git a/modules/ctools_inline_block/src/BlockLoaderInterface.php b/modules/ctools_inline_block/src/BlockLoaderInterface.php new file mode 100644 index 0000000..5732594 --- /dev/null +++ b/modules/ctools_inline_block/src/BlockLoaderInterface.php @@ -0,0 +1,23 @@ +blockStorage = $entity_type_manager->getStorage('block'); + } + + /** + * {@inheritdoc} + */ + public function load($uuid, array $arguments = []) { + if (empty($arguments)) { + throw new \InvalidArgumentException('Cannot load inline block without a block entity ID.'); + } + + /** @var \Drupal\block\BlockInterface $block */ + $block = $this->blockStorage->load($arguments[0]); + + $plugin = $block->getPlugin(); + if ($plugin instanceof InlineBlock) { + return $plugin->getEntity(); + } + else { + throw new \UnexpectedValueException('Cannot load inline block from ' . get_class($plugin)); + } + } + +} diff --git a/modules/ctools_inline_block/src/Entity/BlockContent.php b/modules/ctools_inline_block/src/Entity/BlockContent.php new file mode 100644 index 0000000..5d7bbdf --- /dev/null +++ b/modules/ctools_inline_block/src/Entity/BlockContent.php @@ -0,0 +1,45 @@ +database = $database; + } + + /** + * {@inheritdoc} + */ + public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) { + $handler = new static( + $entity_type, + $container->get('entity.manager'), + $container->get('cache.entity'), + $container->get('database') + ); + + // The handler needs the container in order to access the various services + // that can load inline blocks. + $handler->setContainer($container); + + return $handler; + } + + /** + * {@inheritdoc} + */ + protected function doDeleteFieldItems($entities) { + parent::doDeleteFieldItems($entities); + + $ids = array_map( + function (EntityInterface $entity) { + return $entity->id(); + }, + $entities + ); + $this->database + ->delete('inline_block') + ->condition('uuid', $ids, 'IN') + ->execute(); + } + + /** + * {@inheritdoc} + */ + protected function doLoadMultiple(array $ids = NULL) { + // Attempt to load entities from the persistent cache. This will remove IDs + // that were loaded from $ids. + $entities_from_cache = $this->getFromPersistentCache($ids); + + // Load any remaining entities from the database. + if ($entities_from_storage = $this->getFromStorage($ids)) { + $this->invokeStorageLoadHook($entities_from_storage); + $this->setPersistentCache($entities_from_storage); + } + + return $entities_from_cache + $entities_from_storage; + } + + /** + * Gets entities from the storage. + * + * @param array|null $ids + * If not empty, return entities that match these IDs. Return all entities + * when NULL. + * + * @return \Drupal\Core\Entity\ContentEntityInterface[] + * Array of entities from the storage. + */ + protected function getFromStorage(array $ids = NULL) { + if (!empty($ids)) { + // Sanitize IDs. Before feeding ID array into the query, check whether + // it is empty as this would load all entities. + $ids = $this->cleanIds($ids); + } + + $query = $this->database->select('inline_block')->fields('inline_block'); + if ($ids) { + $query->condition('uuid', $ids, 'IN'); + } + $records = $query->execute()->fetchAllAssoc('uuid'); + + return $this->mapFromStorageRecords($records); + } + + /** + * {@inheritdoc} + */ + protected function cleanIds(array $ids) { + return array_map('strval', $ids); + } + + /** + * {@inheritdoc} + */ + protected function mapFromStorageRecords(array $records, $load_from_revision = FALSE) { + $map = function (\stdClass $record) { + return $this->container + ->get($record->service_id) + ->load($record->uuid, unserialize($record->arguments)); + }; + return array_map($map, $records); + } + +} diff --git a/modules/ctools_inline_block/src/Plugin/Block/InlineBlock.php b/modules/ctools_inline_block/src/Plugin/Block/InlineBlock.php new file mode 100644 index 0000000..e764069 --- /dev/null +++ b/modules/ctools_inline_block/src/Plugin/Block/InlineBlock.php @@ -0,0 +1,78 @@ +entity) && isset($this->configuration['entity'])) { + $this->entity = unserialize($this->configuration['entity']); + } + return $this->entity; + } + + /** + * {@inheritdoc} + */ + public function blockForm($form, FormStateInterface $form_state) { + $options = $this->entityManager->getViewModeOptionsByBundle('block_content', $this->getDerivativeId()); + $form['view_mode'] = array( + '#type' => 'select', + '#options' => $options, + '#title' => $this->t('View mode'), + '#description' => $this->t('Output the block in this view mode.'), + '#default_value' => $this->configuration['view_mode'], + '#access' => (count($options) > 1), + ); + $form['title']['#description'] = $this->t('The title of the block as shown to the user.'); + $form['entity'] = [ + '#type' => 'inline_entity_form', + '#entity_type' => 'inline_block_content', + '#bundle' => $this->getDerivativeId(), + // If the #default_value is NULL, a new entity will be created. + '#default_value' => $this->getEntity(), + ]; + return $form; + } + + /** + * {@inheritdoc} + */ + public function blockSubmit($form, FormStateInterface $form_state) { + $this->configuration['langcode'] = $form_state->getValue('langcode'); + $this->configuration['view_mode'] = $form_state->getValue('view_mode'); + + if (!empty($form['settings']['entity']['#entity'])) { + $this->entity = $form['settings']['entity']['#entity']; + } + else { + $this->entity = $form['entity']['#entity']; + } + $this->configuration['label'] = $this->entity->label(); + $this->configuration['entity'] = serialize($this->entity); + } + +} diff --git a/modules/ctools_inline_block/src/Plugin/Derivative/InlineBlock.php b/modules/ctools_inline_block/src/Plugin/Derivative/InlineBlock.php new file mode 100644 index 0000000..45178c3 --- /dev/null +++ b/modules/ctools_inline_block/src/Plugin/Derivative/InlineBlock.php @@ -0,0 +1,68 @@ +blockContentTypeStorage = $block_content_type_storage; + $this->stringTranslation = $translation; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, $base_plugin_id) { + return new static( + $container->get('entity.manager')->getStorage('block_content_type'), + $container->get('string_translation') + ); + } + + /** + * {@inheritdoc} + */ + public function getDerivativeDefinitions($base_plugin_definition) { + // Reset the discovered definitions. + $this->derivatives = []; + + $block_types = $this->blockContentTypeStorage->loadMultiple(); + /** @var \Drupal\block_content\BlockContentTypeInterface $block_type */ + foreach ($block_types as $id => $block_type) { + $this->derivatives[$id] = $base_plugin_definition; + $this->derivatives[$id]['admin_label'] = $block_type->label(); + $this->derivatives[$id]['config_dependencies']['content'] = [ + $block_type->getConfigDependencyName() + ]; + } + return parent::getDerivativeDefinitions($base_plugin_definition); + } + +} diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/block_content.type.basic.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/block_content.type.basic.yml new file mode 100644 index 0000000..02982e4 --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/block_content.type.basic.yml @@ -0,0 +1,5 @@ +id: basic +label: 'Basic block' +revision: 0 +description: 'A basic block contains a title and a body.' +langcode: en diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_form_display.block_content.basic.default.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_form_display.block_content.basic.default.yml new file mode 100644 index 0000000..5923ff8 --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_form_display.block_content.basic.default.yml @@ -0,0 +1,39 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + - field.field.block_content.basic.field_puppies + - image.style.thumbnail + module: + - image + - text +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + type: text_textarea_with_summary + weight: 1 + settings: + rows: 9 + summary_rows: 3 + placeholder: '' + third_party_settings: { } + field_puppies: + type: image_image + weight: 2 + settings: + progress_indicator: throbber + preview_image_style: thumbnail + third_party_settings: { } + info: + type: string_textfield + weight: 0 + settings: + size: 60 + placeholder: '' + third_party_settings: { } +hidden: { } diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_view_display.block_content.basic.default.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_view_display.block_content.basic.default.yml new file mode 100644 index 0000000..ad99a7e --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/core.entity_view_display.block_content.basic.default.yml @@ -0,0 +1,28 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.field.block_content.basic.body + module: + - text +id: block_content.basic.default +targetEntityType: block_content +bundle: basic +mode: default +content: + body: + type: text_default + weight: 0 + label: hidden + settings: { } + third_party_settings: { } + field_puppies: + type: image + weight: 1 + label: above + settings: + image_style: '' + image_link: '' + third_party_settings: { } +hidden: { } diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.body.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.body.yml new file mode 100644 index 0000000..89118ef --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.body.yml @@ -0,0 +1,21 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.body + module: + - text +id: block_content.basic.body +field_name: body +entity_type: block_content +bundle: basic +label: Body +description: '' +required: false +translatable: true +default_value: { } +default_value_callback: '' +settings: + display_summary: false +field_type: text_with_summary diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.field_puppies.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.field_puppies.yml new file mode 100644 index 0000000..17168ee --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.field.block_content.basic.field_puppies.yml @@ -0,0 +1,37 @@ +langcode: en +status: true +dependencies: + config: + - block_content.type.basic + - field.storage.block_content.field_puppies + module: + - image +id: block_content.basic.field_puppies +field_name: field_puppies +entity_type: block_content +bundle: basic +label: Puppies +description: 'This is where you should upload pictures of puppies.' +required: false +translatable: false +default_value: { } +default_value_callback: '' +settings: + file_directory: '[date:custom:Y]-[date:custom:m]' + file_extensions: 'png gif jpg jpeg' + max_filesize: '' + max_resolution: '' + min_resolution: '' + alt_field: false + alt_field_required: false + title_field: false + title_field_required: false + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + handler: 'default:file' + handler_settings: { } +field_type: image diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.storage.block_content.field_puppies.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.storage.block_content.field_puppies.yml new file mode 100644 index 0000000..b5bee7e --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/config/install/field.storage.block_content.field_puppies.yml @@ -0,0 +1,29 @@ +langcode: en +status: true +dependencies: + module: + - block_content + - file + - image +id: block_content.field_puppies +field_name: field_puppies +entity_type: block_content +type: image +settings: + uri_scheme: public + default_image: + uuid: '' + alt: '' + title: '' + width: null + height: null + target_type: file + display_field: false + display_default: false +module: image +locked: false +cardinality: 1 +translatable: true +indexes: { } +persist_with_no_fields: false +custom_storage: false diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.info.yml b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.info.yml new file mode 100644 index 0000000..5c5b50b --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.info.yml @@ -0,0 +1,8 @@ +type: module +name: CTools Inline Block Test +description: 'Required for CTools Inline Block simpletests only.' +core: 8.x +dependencies: + - ctools_inline_block + - image + - text diff --git a/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.module b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.module new file mode 100644 index 0000000..385a0e3 --- /dev/null +++ b/modules/ctools_inline_block/tests/modules/ctools_inline_block_test/ctools_inline_block_test.module @@ -0,0 +1,11 @@ +uuid(); + } +} diff --git a/modules/ctools_inline_block/tests/src/Functional/RenderTest.php b/modules/ctools_inline_block/tests/src/Functional/RenderTest.php new file mode 100644 index 0000000..b7fe0ae --- /dev/null +++ b/modules/ctools_inline_block/tests/src/Functional/RenderTest.php @@ -0,0 +1,69 @@ +assertSession(); + + $block_content = BlockContent::create([ + 'type' => 'basic', + 'info' => $this->randomMachineName(16), + 'body' => 'Yeah, I like animals better than people sometimes... Especially dogs.', + ]); + $block_content->save(); + + // Place the block using the normal block_content plugin and render + // it out. Nothing special here. + $block = $this->placeBlock('block_content:' . $block_content->uuid(), [ + 'region' => 'content', + ]); + $selector = '#block-' . $block->id(); + + $this->drupalGet('/user/login'); + + $concrete_output = $assert->elementExists('css', $selector)->getOuterHtml(); + + // Use the inline_block plugin to display the same block_content entity, + // serialized and stored in the plugin configuration. + $settings = $block->get('settings'); + $settings['entity'] = serialize($block_content); + $block + ->set('plugin', 'inline_block:basic') + ->set('settings', $settings) + ->save(); + + $this->getSession()->reload(); + $inline_output = $assert->elementExists('css', $selector)->getOuterHtml(); + + $this->assertSame($concrete_output, $inline_output); + } + +} diff --git a/modules/ctools_inline_block/tests/src/FunctionalJavascript/InlineBlockFieldTest.php b/modules/ctools_inline_block/tests/src/FunctionalJavascript/InlineBlockFieldTest.php new file mode 100644 index 0000000..9db803e --- /dev/null +++ b/modules/ctools_inline_block/tests/src/FunctionalJavascript/InlineBlockFieldTest.php @@ -0,0 +1,77 @@ +createUser(['administer blocks']); + $this->drupalLogin($account); + $this->drupalGet('/admin/structure/block/add/inline_block:basic/classy'); + + $page = $this->getSession()->getPage(); + + $image = \Drupal::moduleHandler()->getModule('image')->getPath() . '/sample.png'; + $page->attachFileToField('Puppies', \Drupal::root() . '/' . $image); + + $block_id = strtolower($this->randomMachineName(16)); + + // Wait for the file element to do its AJAX business. + $result = $this->getSession()->wait(10000, '(typeof(jQuery)=="undefined" || (0 === jQuery.active && 0 === jQuery(\':animated\').length))'); + if (!$result) { + $this->fail('Timed out waiting for AJAX upload to complete.'); + } + $page->fillField('Block description', $this->randomMachineName()); + $page->fillField('id', $block_id); + $page->fillField('region', 'content'); + $page->pressButton('Save block'); + + $block = Block::load($block_id); + $block_content = $block->getPlugin()->getEntity(); + $this->assertInstanceOf(BlockContent::class, $block_content); + $this->assertFalse($block_content->field_puppies->isEmpty()); + + /** @var \Drupal\file\FileInterface $image */ + $image = $block_content->field_puppies->entity; + /** @var \Drupal\file\FileUsage\FileUsageInterface $file_usage */ + $file_usage = \Drupal::service('file.usage'); + $this->assertTrue($image->isPermanent()); + $this->assertNotEmpty($file_usage->listUsage($image)); + + // Deleting the block should delete the inline block, which in turn should + // cause the image to be marked as temporary since nothing will be using it. + $block->delete(); + $this->assertEmpty($file_usage->listUsage($image)); + $image = \Drupal::entityTypeManager()->getStorage('file')->loadUnchanged($image->id()); + $this->assertTrue($image->isTemporary()); + } + +} diff --git a/modules/ctools_inline_block/tests/src/Kernel/StorageTest.php b/modules/ctools_inline_block/tests/src/Kernel/StorageTest.php new file mode 100644 index 0000000..e3fa3e1 --- /dev/null +++ b/modules/ctools_inline_block/tests/src/Kernel/StorageTest.php @@ -0,0 +1,279 @@ +installConfig('block_content'); + $this->installConfig('ctools_inline_block_test'); + + $this->installEntitySchema('block_content'); + $this->installEntitySchema('file'); + + $this->installSchema('ctools_inline_block', ['inline_block']); + } + + /** + * Tests that inline blocks are not loadable if saved directly. + */ + public function testLoadWithoutBlock() { + $block_content = BlockContent::create([ + 'type' => 'basic', + 'info' => $this->randomMachineName(), + 'body' => 'Pasta ipsum dolor sit amet tripoline farfalle croxetti mezzelune spirali manicotti ditalini spaghetti calamarata ricciolini ciriole spirali cencioni.', + ]); + $block_content->save(); + + $id = $block_content->id(); + $this->assertNotEmpty($id); + $this->assertEquals($id, $block_content->uuid()); + + $this->assertNull(BlockContent::load($id)); + $this->assertEmpty(BlockContent::loadMultiple([$id])); + $this->assertEmpty(BlockContent::loadMultiple()); + } + + /** + * Tests loading inline blocks by UUID. + * + * @depends testLoadWithoutBlock + */ + public function testLoadWithBlock() { + $block_content = BlockContent::create([ + 'type' => 'basic', + 'info' => $this->randomMachineName(), + 'body' => 'Pasta ipsum dolor sit amet tripoline farfalle croxetti mezzelune spirali manicotti ditalini spaghetti calamarata ricciolini ciriole spirali cencioni.', + ]); + $block_content->save(); + $id = $block_content->id(); + + $block = Block::create([ + 'id' => $this->randomMachineName(), + 'plugin' => 'inline_block:basic', + 'settings' => [ + 'entity' => serialize($block_content), + ], + ]); + $block->save(); + + $this->assertInstanceOf(BlockContent::class, BlockContent::load($id)); + + $loaded = BlockContent::loadMultiple([$id]); + $this->assertInstanceOf(BlockContent::class, $loaded[$id]); + } + + /** + * Tests that inline blocks can be created like normal custom blocks. + */ + public function testCreate() { + $body = 'Pasta ipsum dolor sit amet quadrefiore vermicelli rigatoncini fettuccine timpano. Mezzelune penne trofie penne lisce trenne trennette timpano corzetti tagliatelle calamaretti capunti fedelini corzetti farfalline.'; + + $block_content = BlockContent::create([ + 'type' => 'basic', + 'body' => $body, + ]); + + // Assert that it's an inline block. + $this->assertInstanceOf(BlockContent::class, $block_content); + $this->assertSame('inline_block_content', $block_content->getEntityTypeId()); + // Like any other entity, inline blocks should be new if unsaved. + $this->assertTrue($block_content->isNew()); + + // Assert that it has the same fields. + $this->assertTrue($block_content->hasField('body')); + $this->assertEquals($body, $block_content->body->value); + $this->assertTrue($block_content->hasField('field_puppies')); + $this->assertTrue($block_content->get('field_puppies')->isEmpty()); + } + + /** + * Tests that inline blocks don't put anything in the database when saved. + * + * @depends testCreate + */ + public function testSave() { + $block_content = BlockContent::create([ + 'type' => 'test_type', + 'info' => $this->randomMachineName(), + 'body' => 'Creste di galli spaghettoni trennette pennette trennette penne zita linguettine orecchiette pappardelle gramigna casarecce.', + ]); + $block_content->save(); + + // Inline blocks use their UUID as their ID. + $this->assertEquals($block_content->id(), $block_content->uuid()); + // Like any other entity, inline blocks with an ID should not be new. + $this->assertFalse($block_content->isNew()); + + // Ensure that no concrete block_content entities were created. + $count = \Drupal::entityQuery('block_content')->count()->execute(); + $this->assertNumeric($count); + $this->assertEmpty($count); + + // Nothing should be written to the field tables. + $this->assertTableIsEmpty('block_content__body'); + $this->assertTableIsEmpty('block_content_revision__body'); + + // Inline blocks are invisible to entity queries. + $count = \Drupal::entityQuery('inline_block_content')->count()->execute(); + $this->assertNumeric($count); + $this->assertEmpty($count); + + // Nothing should be in the tracking table if saved directly. + $this->assertTableIsEmpty('inline_block'); + } + + /** + * Tests deleting an inline block directly. + * + * @depends testLoadWithBlock + */ + public function testDirectDelete() { + $block_content = BlockContent::create([ + 'type' => 'basic', + 'info' => $this->randomMachineName(), + 'body' => 'Pasta ipsum dolor sit amet tripoline farfalle croxetti mezzelune spirali manicotti ditalini spaghetti calamarata ricciolini ciriole spirali cencioni.', + ]); + $block_content->save(); + + $block = Block::create([ + 'id' => $this->randomMachineName(), + 'plugin' => 'inline_block:basic', + 'settings' => [ + 'entity' => serialize($block_content), + ], + ]); + $block->save(); + + // Because the inline block is associated with a real block, there should + // be an entry in the tracking table. + $this->assertTableIsNotEmpty('inline_block'); + + // Deleting the inline block should clear it from the tracking table... + $block_content->delete(); + $this->assertTableIsEmpty('inline_block'); + + // ...which means it should no longer be loadable. + $id = $block_content->id(); + $this->assertNull(BlockContent::load($id)); + } + + /** + * Tests deleting a block that contains an inline block. + * + * @depends testDirectDelete + */ + public function testDeleteBlock() { + $block_content = BlockContent::create([ + 'type' => 'basic', + 'info' => $this->randomMachineName(), + 'body' => 'Pasta ipsum dolor sit amet tripoline farfalle croxetti mezzelune spirali manicotti ditalini spaghetti calamarata ricciolini ciriole spirali cencioni.', + ]); + $block_content->save(); + + $block = Block::create([ + 'id' => $this->randomMachineName(), + 'plugin' => 'inline_block:basic', + 'settings' => [ + 'entity' => serialize($block_content), + ], + ]); + $block->save(); + + // Because the inline block is associated with a real block, there should + // be an entry in the tracking table. + $this->assertTableIsNotEmpty('inline_block'); + + // Deleting the block should clear the inline block from the tracking table. + $block->delete(); + $this->assertTableIsEmpty('inline_block'); + + // ...which means it should no longer be loadable. + $id = $block_content->id(); + $this->assertNull(BlockContent::load($id)); + } + + /** + * Asserts that a value is numeric. + * + * @param mixed $value + * The value to check. + */ + protected function assertNumeric($value) { + $this->assertTrue(is_numeric($value)); + } + + /** + * Counts the rows in a database table. + * + * @param string $table + * The table to count. + * + * @return int + * The number of rows in the table. + */ + protected function countTable($table) { + $count = \Drupal::database() + ->select($table) + ->countQuery() + ->execute() + ->fetchField(); + + $this->assertNumeric($count); + return (int) $count; + } + + /** + * Asserts that a database table is empty. + * + * @param string $table + * The table to check. + */ + protected function assertTableIsEmpty($table) { + $this->assertEmpty($this->countTable($table)); + } + + /** + * Asserts that a database table is not empty. + * + * @param string $table + * The table to check. + */ + protected function assertTableIsNotEmpty($table) { + $this->assertNotEmpty($this->countTable($table)); + } + +}