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..1541114 --- /dev/null +++ b/modules/ctools_inline_block/config/schema/ctools_inline_block.schema.yml @@ -0,0 +1,10 @@ +block.settings.inline_content:*: + 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..5765535 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.info.yml @@ -0,0 +1,9 @@ +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: + - inline_entity_form + - block_content 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..81c9110 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.install @@ -0,0 +1,30 @@ + [ + 'fields' => [ + 'uuid' => [ + 'type' => 'varchar', + 'length' => 64, + 'not null' => TRUE, + 'description' => 'UUID of the inline block.', + ], + 'loader' => [ + 'type' => 'varchar', + 'length' => 128, + 'not null' => TRUE, + 'description' => 'ID of the service which can load the block.', + ], + 'data' => [ + 'type' => 'text', + 'description' => 'Additional data needed to load the block.', + ], + ], + '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..4fc00e2 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.module @@ -0,0 +1,33 @@ +getPlugin(); + + if ($plugin instanceof InlineBlock) { + \Drupal::database() + ->insert('inline_block') + ->fields([ + 'uuid' => $plugin->getEntity()->uuid(), + 'loader' => 'ctools.block_loader', + 'data' => $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(); + } +} 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..3eed444 --- /dev/null +++ b/modules/ctools_inline_block/ctools_inline_block.services.yml @@ -0,0 +1,5 @@ +services: + ctools.block_loader: + class: '\Drupal\ctools_inline_block\BlockLoader' + arguments: + - '@entity_type.manager' diff --git a/modules/ctools_inline_block/src/BlockLoader.php b/modules/ctools_inline_block/src/BlockLoader.php new file mode 100644 index 0000000..40d413b --- /dev/null +++ b/modules/ctools_inline_block/src/BlockLoader.php @@ -0,0 +1,39 @@ +entityTypeManager = $entity_type_manager; + } + + /** + * {@inheritdoc} + */ + public function load($uuid, $data = NULL) { + // $data is expected to be the ID of the block config entity. + if ($data) { + return $this->entityTypeManager->getStorage('block')->load($data)->getPlugin()->getEntity(); + } + else { + throw new \InvalidArgumentException('No block entity ID was specified'); + } + } + +} diff --git a/modules/ctools_inline_block/src/BlockLoaderInterface.php b/modules/ctools_inline_block/src/BlockLoaderInterface.php new file mode 100644 index 0000000..526c064 --- /dev/null +++ b/modules/ctools_inline_block/src/BlockLoaderInterface.php @@ -0,0 +1,23 @@ +uuid(); + } + + /** + * {@inheritdoc} + */ + public function save() { + return $this->entityTypeManager() + ->getStorage('inline_block_content') + ->save($this); + } + + /** + * {@inheritdoc} + */ + public function delete() { + $this->entityTypeManager() + ->getStorage('inline_block_content') + ->delete([ + $this->id() => $this, + ]); + } + +} diff --git a/modules/ctools_inline_block/src/Entity/InlineBlockStorage.php b/modules/ctools_inline_block/src/Entity/InlineBlockStorage.php new file mode 100644 index 0000000..57efe8e --- /dev/null +++ b/modules/ctools_inline_block/src/Entity/InlineBlockStorage.php @@ -0,0 +1,159 @@ +getDefinition('block_content'), + $database, + $entity_manager, + $cache, + $language_manager + ); + + // ...but when creating or loading our entities, use the inline block + // content entity class. + $this->entityClass = $entity_type->getClass(); + } + + /** + * {@inheritdoc} + */ + public function delete(array $entities) { + parent::delete($entities); + + $ids = array_keys($entities); + + $this->database + ->delete('inline_block') + ->condition('uuid', $ids, 'IN') + ->execute(); + + $this->resetCache($ids); + } + + /** + * {@inheritdoc} + */ + protected function cleanIds(array $ids) { + return array_map('strval', $ids); + } + + /** + * {@inheritdoc} + */ + protected function getFromStorage(array $ids = NULL) { + $id_key = $this->idKey; + $this->idKey = 'uuid'; + $entities = parent::getFromStorage($ids); + $this->idKey = $id_key; + return $entities; + } + + /** + * {@inheritdoc} + */ + protected function buildQuery($ids, $revision_id = FALSE) { + $query = $this->database->select('inline_block', 'ib')->fields('ib'); + + if ($ids) { + $query->condition('uuid', $ids, 'IN'); + } + return $query; + } + + /** + * {@inheritdoc} + */ + protected function mapFromStorageRecords(array $records, $load_from_revision = FALSE) { + $mapped = []; + foreach ($records as $uuid => $record) { + $mapped[$uuid] = \Drupal::service($record->loader)->load($uuid, $record->data); + } + return $mapped; + } + + /** + * {@inheritdoc} + */ + public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) { + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeCreate(EntityTypeInterface $entity_type) { + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) { + $this->entityType = $entity_type; + } + + /** + * {@inheritdoc} + */ + public function onEntityTypeDelete(EntityTypeInterface $entity_type) { + } + + /** + * {@inheritdoc} + */ + protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) { + } + + /** + * {@inheritdoc} + */ + protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) { + } + + /** + * {@inheritdoc} + */ + protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = array()) { + } + +} 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..3729eef --- /dev/null +++ b/modules/ctools_inline_block/src/Plugin/Block/InlineBlock.php @@ -0,0 +1,75 @@ +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['view_mode'] = $form_state->getValue('view_mode'); + /** @var \Drupal\block_content\BlockContentInterface $entity */ + $entity = $form['settings']['entity']['#entity'] ?: $form['entity']['#entity']; + $this->configuration['label'] = $entity->label(); + $this->configuration['langcode'] = $form_state->getValue('langcode'); + + $this->entity = $entity; + $this->configuration['entity'] = serialize($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..7f91cd6 --- /dev/null +++ b/modules/ctools_inline_block/src/Plugin/Derivative/InlineBlock.php @@ -0,0 +1,67 @@ +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) { + $block_types = $this->blockContentTypeStorage->loadMultiple(); + // Reset the discovered definitions. + $this->derivatives = []; + /** @var $type \Drupal\block_content\Entity\BlockContent */ + foreach ($block_types as $id => $type) { + $this->derivatives[$id] = $base_plugin_definition; + $this->derivatives[$id]['admin_label'] = $this->t('Inline @type', ['@type' => $type->label()]); + $this->derivatives[$id]['config_dependencies']['content'] = array( + $type->getConfigDependencyName() + ); + } + return $this->derivatives; + } + +} 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..6f1a26a --- /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..020d381 --- /dev/null +++ b/modules/ctools_inline_block/tests/src/Functional/RenderTest.php @@ -0,0 +1,68 @@ +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_content 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_content: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..ac2334b --- /dev/null +++ b/modules/ctools_inline_block/tests/src/FunctionalJavascript/InlineBlockFieldTest.php @@ -0,0 +1,76 @@ +createUser(['administer blocks']); + $this->drupalLogin($account); + $this->drupalGet('/admin/structure/block/add/inline_content: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->pressButton('Save block'); + + $block = Block::load($block_id); + $block_content = $block->getPlugin()->getEntity(); + $this->assertInstanceOf(InlineBlockContent::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..3dd0e1d --- /dev/null +++ b/modules/ctools_inline_block/tests/src/Kernel/StorageTest.php @@ -0,0 +1,277 @@ +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 go to /dev/null if saved on their own. + */ + public function testLoadWithoutBlock() { + $block_content = InlineBlockContent::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(InlineBlockContent::load($id)); + $this->assertEmpty(InlineBlockContent::loadMultiple([$id])); + $this->assertEmpty(InlineBlockContent::loadMultiple()); + } + + /** + * Tests loading inline blocks by UUID. + * + * @depends testLoadWithoutBlock + */ + public function testLoadWithBlock() { + $block_content = InlineBlockContent::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_content:basic', + 'settings' => [ + 'entity' => serialize($block_content), + ], + ]); + $block->save(); + + $this->assertInstanceOf(InlineBlockContent::class, InlineBlockContent::load($id)); + + $loaded = InlineBlockContent::loadMultiple([$id]); + $this->assertInstanceOf(InlineBlockContent::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 = InlineBlockContent::create([ + 'type' => 'basic', + 'body' => $body, + ]); + + // Assert that it's an inline block masquerading as a normal block_content. + $this->assertInstanceOf(InlineBlockContent::class, $block_content); + $this->assertSame('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 = InlineBlockContent::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 = InlineBlockContent::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_content: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(InlineBlockContent::load($id)); + } + + /** + * Tests deleting a block that contains an inline block. + * + * @depends testDirectDelete + */ + public function testDeleteBlock() { + $block_content = InlineBlockContent::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_content: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(InlineBlockContent::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)); + } + +}