adding everything
This commit is contained in:
commit
0ef5628d44
10
block_content.info.yml
Normal file
10
block_content.info.yml
Normal file
@ -0,0 +1,10 @@
|
||||
name: 'Block Content'
|
||||
type: module
|
||||
description: 'Allows the creation of content blocks and block types.'
|
||||
package: Core
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:block
|
||||
- drupal:text
|
||||
- drupal:user
|
||||
configure: entity.block_content.collection
|
||||
65
block_content.install
Normal file
65
block_content.install
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the block_content module.
|
||||
*/
|
||||
|
||||
use Drupal\block_content\BlockContentStorageSchema;
|
||||
use Drupal\Core\Entity\Form\RevisionDeleteForm;
|
||||
use Drupal\Core\Entity\Form\RevisionRevertForm;
|
||||
use Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
|
||||
/**
|
||||
* Implements hook_update_last_removed().
|
||||
*/
|
||||
function block_content_update_last_removed() {
|
||||
return 8600;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update entity definition to handle revision routes.
|
||||
*/
|
||||
function block_content_update_10100(&$sandbox = NULL): TranslatableMarkup {
|
||||
$entityDefinitionUpdateManager = \Drupal::entityDefinitionUpdateManager();
|
||||
$definition = $entityDefinitionUpdateManager->getEntityType('block_content');
|
||||
$routeProviders = $definition->get('route_provider');
|
||||
$routeProviders['revision'] = RevisionHtmlRouteProvider::class;
|
||||
$definition
|
||||
->setFormClass('revision-delete', RevisionDeleteForm::class)
|
||||
->setFormClass('revision-revert', RevisionRevertForm::class)
|
||||
->set('route_provider', $routeProviders)
|
||||
->setLinkTemplate('revision-delete-form', '/admin/content/block/{block_content}/revision/{block_content_revision}/delete')
|
||||
->setLinkTemplate('revision-revert-form', '/admin/content/block/{block_content}/revision/{block_content_revision}/revert')
|
||||
->setLinkTemplate('version-history', '/admin/content/block/{block_content}/revisions');
|
||||
$entityDefinitionUpdateManager->updateEntityType($definition);
|
||||
return \t('Added revision routes to Content block entity type.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the unique values constraint from block content info fields.
|
||||
*/
|
||||
function block_content_update_10200() {
|
||||
$constraint = 'UniqueField';
|
||||
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$field_storage_definition = $definition_update_manager->getFieldStorageDefinition('info', 'block_content');
|
||||
$constraints = $field_storage_definition->getConstraints();
|
||||
if (isset($constraints[$constraint])) {
|
||||
unset($constraints[$constraint]);
|
||||
$field_storage_definition->setConstraints($constraints);
|
||||
$definition_update_manager->updateFieldStorageDefinition($field_storage_definition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply index to reusable column.
|
||||
*/
|
||||
function block_content_update_10300(): void {
|
||||
$manager = \Drupal::entityDefinitionUpdateManager();
|
||||
$entity_type = $manager->getEntityType('block_content')
|
||||
->setHandlerClass('storage_schema', BlockContentStorageSchema::class);
|
||||
$manager->updateEntityType($entity_type);
|
||||
$manager->updateFieldStorageDefinition(\Drupal::service('entity_field.manager')
|
||||
->getBaseFieldDefinitions('block_content')['reusable']);
|
||||
}
|
||||
13
block_content.links.action.yml
Normal file
13
block_content.links.action.yml
Normal file
@ -0,0 +1,13 @@
|
||||
block_content_type_add:
|
||||
route_name: block_content.type_add
|
||||
title: 'Add block type'
|
||||
appears_on:
|
||||
- entity.block_content_type.collection
|
||||
|
||||
block_content_add_action:
|
||||
route_name: block_content.add_page
|
||||
title: 'Add content block'
|
||||
appears_on:
|
||||
- block.admin_library
|
||||
- entity.block_content.collection
|
||||
class: \Drupal\block_content\Plugin\Menu\LocalAction\BlockContentAddLocalAction
|
||||
4
block_content.links.contextual.yml
Normal file
4
block_content.links.contextual.yml
Normal file
@ -0,0 +1,4 @@
|
||||
block_content.block_edit:
|
||||
title: 'Edit'
|
||||
group: block_content
|
||||
route_name: 'entity.block_content.edit_form'
|
||||
5
block_content.links.menu.yml
Normal file
5
block_content.links.menu.yml
Normal file
@ -0,0 +1,5 @@
|
||||
entity.block_content_type.collection:
|
||||
title: 'Block types'
|
||||
parent: system.admin_structure
|
||||
description: 'Create and manage fields, forms, and display settings for your content blocks.'
|
||||
route_name: entity.block_content_type.collection
|
||||
19
block_content.links.task.yml
Normal file
19
block_content.links.task.yml
Normal file
@ -0,0 +1,19 @@
|
||||
entity.block_content.collection:
|
||||
title: Blocks
|
||||
route_name: entity.block_content.collection
|
||||
base_route: system.admin_content
|
||||
|
||||
entity.block_content.canonical:
|
||||
title: Edit
|
||||
route_name: entity.block_content.canonical
|
||||
base_route: entity.block_content.canonical
|
||||
entity.block_content.delete_form:
|
||||
title: Delete
|
||||
route_name: entity.block_content.delete_form
|
||||
base_route: entity.block_content.canonical
|
||||
|
||||
# Default tab for block type editing.
|
||||
entity.block_content_type.edit_form:
|
||||
title: 'Edit'
|
||||
route_name: entity.block_content_type.edit_form
|
||||
base_route: entity.block_content_type.edit_form
|
||||
243
block_content.module
Normal file
243
block_content.module
Normal file
@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Allows the creation of content blocks through the user interface.
|
||||
*/
|
||||
|
||||
use Drupal\block\BlockInterface;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Database\Query\AlterableInterface;
|
||||
use Drupal\Core\Database\Query\ConditionInterface;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
function block_content_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.block_content':
|
||||
$field_ui = \Drupal::moduleHandler()->moduleExists('field_ui') ? Url::fromRoute('help.page', ['name' => 'field_ui'])->toString() : '#';
|
||||
$output = '';
|
||||
$output .= '<h2>' . t('About') . '</h2>';
|
||||
$output .= '<p>' . t('The Block Content module allows you to create and manage custom <em>block types</em> and <em>content-containing blocks</em>. For more information, see the <a href=":online-help">online documentation for the Block Content module</a>.', [':online-help' => 'https://www.drupal.org/documentation/modules/block_content']) . '</p>';
|
||||
$output .= '<h2>' . t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . t('Creating and managing block types') . '</dt>';
|
||||
$output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can create and edit block types with fields and display settings, from the <a href=":types">Block types</a> page under the Structure menu. For more information about managing fields and display settings, see the <a href=":field-ui">Field UI module help</a> and <a href=":field">Field module help</a>.', [':types' => Url::fromRoute('entity.block_content_type.collection')->toString(), ':field-ui' => $field_ui, ':field' => Url::fromRoute('help.page', ['name' => 'field'])->toString()]) . '</dd>';
|
||||
$output .= '<dt>' . t('Creating content blocks') . '</dt>';
|
||||
$output .= '<dd>' . t('Users with the <em>Administer blocks</em> permission can create, edit, and delete content blocks of each defined block type, from the <a href=":block-library">Content blocks page</a>. After creating a block, place it in a region from the <a href=":blocks">Block layout page</a>, just like blocks provided by other modules.', [':blocks' => Url::fromRoute('block.admin_display')->toString(), ':block-library' => Url::fromRoute('entity.block_content.collection')->toString()]) . '</dd>';
|
||||
$output .= '</dl>';
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
function block_content_theme($existing, $type, $theme, $path) {
|
||||
return [
|
||||
'block_content_add_list' => [
|
||||
'variables' => ['content' => NULL],
|
||||
'file' => 'block_content.pages.inc',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
function block_content_entity_type_alter(array &$entity_types) {
|
||||
/** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
|
||||
// Add a translation handler for fields if the language module is enabled.
|
||||
if (\Drupal::moduleHandler()->moduleExists('language')) {
|
||||
$translation = $entity_types['block_content']->get('translation');
|
||||
$translation['block_content'] = TRUE;
|
||||
$entity_types['block_content']->set('translation', $translation);
|
||||
}
|
||||
|
||||
// Swap out the default EntityChanged constraint with a custom one with
|
||||
// different logic for inline blocks.
|
||||
$constraints = $entity_types['block_content']->getConstraints();
|
||||
unset($constraints['EntityChanged']);
|
||||
$constraints['BlockContentEntityChanged'] = NULL;
|
||||
$entity_types['block_content']->setConstraints($constraints);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the default body field to a block type.
|
||||
*
|
||||
* @param string $block_type_id
|
||||
* Id of the block type.
|
||||
* @param string $label
|
||||
* (optional) The label for the body instance. Defaults to 'Body'
|
||||
*
|
||||
* @return \Drupal\field\Entity\FieldConfig
|
||||
* A Body field object.
|
||||
*/
|
||||
function block_content_add_body_field($block_type_id, $label = 'Body') {
|
||||
// Add or remove the body field, as needed.
|
||||
$field = FieldConfig::loadByName('block_content', $block_type_id, 'body');
|
||||
if (empty($field)) {
|
||||
$field = FieldConfig::create([
|
||||
'field_storage' => FieldStorageConfig::loadByName('block_content', 'body'),
|
||||
'bundle' => $block_type_id,
|
||||
'label' => $label,
|
||||
'settings' => [
|
||||
'display_summary' => FALSE,
|
||||
'allowed_formats' => [],
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
/** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
|
||||
$display_repository = \Drupal::service('entity_display.repository');
|
||||
|
||||
// Assign widget settings for the default form mode.
|
||||
$display_repository->getFormDisplay('block_content', $block_type_id)
|
||||
->setComponent('body', [
|
||||
'type' => 'text_textarea_with_summary',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Assign display settings for default view mode.
|
||||
$display_repository->getViewDisplay('block_content', $block_type_id)
|
||||
->setComponent('body', [
|
||||
'label' => 'hidden',
|
||||
'type' => 'text_default',
|
||||
])
|
||||
->save();
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_query_TAG_alter().
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Block_content entities that are not reusable should by default not be
|
||||
* selectable as entity reference values. A module can still create an instance
|
||||
* of \Drupal\Core\Entity\EntityReferenceSelection\SelectionInterface
|
||||
* that will allow selection of non-reusable blocks by explicitly setting
|
||||
* a condition on the 'reusable' field.
|
||||
*
|
||||
* @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(), $query->getTables())) {
|
||||
$query->condition("$data_table.reusable", TRUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to find nested conditions using the reusable field.
|
||||
*
|
||||
* @todo Replace this function with a call to the API in
|
||||
* https://www.drupal.org/project/drupal/issues/2984930
|
||||
*
|
||||
* @param array $condition
|
||||
* The condition or condition group to check.
|
||||
* @param array $tables
|
||||
* The tables from the related select query.
|
||||
*
|
||||
* @see \Drupal\Core\Database\Query\SelectInterface::getTables
|
||||
*
|
||||
* @return bool
|
||||
* Whether the conditions contain any condition using the reusable field.
|
||||
*/
|
||||
function _block_content_has_reusable_condition(array $condition, array $tables) {
|
||||
// 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, $tables)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
if (isset($condition['field'])) {
|
||||
$field = $condition['field'];
|
||||
if (is_object($field) && $field instanceof ConditionInterface) {
|
||||
return _block_content_has_reusable_condition($field->conditions(), $tables);
|
||||
}
|
||||
$field_parts = explode('.', $field);
|
||||
$data_table = \Drupal::entityTypeManager()->getDefinition('block_content')->getDataTable();
|
||||
foreach ($tables as $table) {
|
||||
if ($table['table'] === $data_table && $field_parts[0] === $table['alias'] && $field_parts[1] === 'reusable') {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme_suggestions_HOOK_alter() for block templates.
|
||||
*/
|
||||
function block_content_theme_suggestions_block_alter(array &$suggestions, array $variables) {
|
||||
$suggestions_new = [];
|
||||
$content = $variables['elements']['content'];
|
||||
|
||||
$block_content = $variables['elements']['content']['#block_content'] ?? NULL;
|
||||
|
||||
if ($block_content instanceof BlockContentInterface) {
|
||||
$bundle = $content['#block_content']->bundle();
|
||||
$view_mode = strtr($variables['elements']['#configuration']['view_mode'], '.', '_');
|
||||
|
||||
$suggestions_new[] = 'block__block_content__view__' . $view_mode;
|
||||
$suggestions_new[] = 'block__block_content__type__' . $bundle;
|
||||
$suggestions_new[] = 'block__block_content__view_type__' . $bundle . '__' . $view_mode;
|
||||
|
||||
if (!empty($variables['elements']['#id'])) {
|
||||
$suggestions_new[] = 'block__block_content__id__' . $variables['elements']['#id'];
|
||||
$suggestions_new[] = 'block__block_content__id_view__' . $variables['elements']['#id'] . '__' . $view_mode;
|
||||
}
|
||||
|
||||
// Remove duplicate block__block_content.
|
||||
$suggestions = array_unique($suggestions);
|
||||
array_splice($suggestions, 1, 0, $suggestions_new);
|
||||
}
|
||||
|
||||
return $suggestions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_operation().
|
||||
*/
|
||||
function block_content_entity_operation(EntityInterface $entity): array {
|
||||
$operations = [];
|
||||
if ($entity instanceof BlockInterface) {
|
||||
$plugin = $entity->getPlugin();
|
||||
if ($plugin->getBaseId() === 'block_content') {
|
||||
$custom_block = \Drupal::entityTypeManager()->getStorage('block_content')->loadByProperties([
|
||||
'uuid' => $plugin->getDerivativeId(),
|
||||
]);
|
||||
$custom_block = reset($custom_block);
|
||||
if ($custom_block && $custom_block->access('update')) {
|
||||
$operations['block-edit'] = [
|
||||
'title' => t('Edit block'),
|
||||
'url' => $custom_block->toUrl('edit-form')->setOptions([]),
|
||||
'weight' => 50,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $operations;
|
||||
}
|
||||
37
block_content.pages.inc
Normal file
37
block_content.pages.inc
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Provides page callbacks for content blocks.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Link;
|
||||
use Drupal\Core\Url;
|
||||
|
||||
/**
|
||||
* Prepares variables for a block type creation list templates.
|
||||
*
|
||||
* Default template: block-content-add-list.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - content: An array of block types.
|
||||
*
|
||||
* @see block_content_add_page()
|
||||
*/
|
||||
function template_preprocess_block_content_add_list(&$variables) {
|
||||
$variables['types'] = [];
|
||||
$query = \Drupal::request()->query->all();
|
||||
foreach ($variables['content'] as $type) {
|
||||
$variables['types'][$type->id()] = [
|
||||
'link' => Link::fromTextAndUrl($type->label(), Url::fromRoute('block_content.add_form', ['block_content_type' => $type->id()], ['query' => $query]))->toString(),
|
||||
'description' => [
|
||||
'#markup' => $type->getDescription(),
|
||||
],
|
||||
'title' => $type->label(),
|
||||
'localized_options' => [
|
||||
'query' => $query,
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
16
block_content.permissions.yml
Normal file
16
block_content.permissions.yml
Normal file
@ -0,0 +1,16 @@
|
||||
permission_callbacks:
|
||||
- \Drupal\block_content\BlockContentPermissions::blockTypePermissions
|
||||
|
||||
access block library:
|
||||
title: 'Access the Content blocks overview page'
|
||||
description: 'Get an overview of all content blocks.'
|
||||
|
||||
administer block types:
|
||||
title: 'Administer block types'
|
||||
description: 'Maintain the block types of block content available and the fields that are associated with those types.'
|
||||
restrict access: TRUE
|
||||
|
||||
administer block content:
|
||||
title: 'Administer block content'
|
||||
description: 'View, edit and delete all block content regardless of permission restrictions.'
|
||||
restrict access: TRUE
|
||||
94
block_content.post_update.php
Normal file
94
block_content.post_update.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Post update functions for Content Block.
|
||||
*/
|
||||
|
||||
use Drupal\block_content\BlockContentTypeInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityUpdater;
|
||||
use Drupal\user\Entity\Role;
|
||||
use Drupal\views\Entity\View;
|
||||
|
||||
/**
|
||||
* Implements hook_removed_post_updates().
|
||||
*/
|
||||
function block_content_removed_post_updates() {
|
||||
return [
|
||||
'block_content_post_update_add_views_reusable_filter' => '9.0.0',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the entity type cache.
|
||||
*/
|
||||
function block_content_post_update_entity_changed_constraint() {
|
||||
// Empty post_update hook.
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the custom block library to Content.
|
||||
*/
|
||||
function block_content_post_update_move_custom_block_library() {
|
||||
|
||||
if (!\Drupal::service('module_handler')->moduleExists('views')) {
|
||||
return;
|
||||
}
|
||||
if (!$view = View::load('block_content')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$display =& $view->getDisplay('page_1');
|
||||
if (empty($display) || $display['display_options']['path'] !== 'admin/structure/block/block-content') {
|
||||
return;
|
||||
}
|
||||
|
||||
$display['display_options']['path'] = 'admin/content/block';
|
||||
$menu =& $display['display_options']['menu'];
|
||||
$menu['title'] = 'Blocks';
|
||||
$menu['description'] = 'Create and edit block content.';
|
||||
$menu['expanded'] = FALSE;
|
||||
$menu['parent'] = 'system.admin_content';
|
||||
$view->set('label', 'Content blocks');
|
||||
|
||||
$view->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update block_content 'block library' view permission.
|
||||
*/
|
||||
function block_content_post_update_block_library_view_permission() {
|
||||
$config_factory = \Drupal::configFactory();
|
||||
$config = $config_factory->getEditable('views.view.block_content');
|
||||
$current_perm = $config->get('display.default.display_options.access.options.perm');
|
||||
if ($current_perm === 'administer blocks') {
|
||||
$config->set('display.default.display_options.access.options.perm', 'access block library')
|
||||
->save(TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update permissions for users with "administer blocks" permission.
|
||||
*/
|
||||
function block_content_post_update_sort_permissions(&$sandbox = NULL) {
|
||||
\Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'user_role', function (Role $role) {
|
||||
if ($role->hasPermission('administer blocks')) {
|
||||
$role->grantPermission('administer block content');
|
||||
$role->grantPermission('access block library');
|
||||
$role->grantPermission('administer block types');
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update configuration for revision type.
|
||||
*/
|
||||
function block_content_post_update_revision_type(&$sandbox = NULL) {
|
||||
\Drupal::classResolver(ConfigEntityUpdater::class)
|
||||
->update($sandbox, 'block_content_type', function (BlockContentTypeInterface $block_content_type) {
|
||||
$block_content_type->set('revision', (bool) $block_content_type->get('revision'));
|
||||
return TRUE;
|
||||
});
|
||||
}
|
||||
132
block_content.routing.yml
Normal file
132
block_content.routing.yml
Normal file
@ -0,0 +1,132 @@
|
||||
block_content.add_page:
|
||||
path: '/block/add'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::add'
|
||||
_title: 'Add content block'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_create_any_access: 'block_content'
|
||||
|
||||
# @todo Deprecate this route once
|
||||
# https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
|
||||
# it in Drupal 11.
|
||||
# @see https://www.drupal.org/node/3320855
|
||||
entity.block_content_type.collection.bc:
|
||||
path: '/admin/structure/block/block-content/types'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::blockContentTypeRedirect'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_permission: 'administer block types'
|
||||
|
||||
# @todo Deprecate this route once
|
||||
# https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
|
||||
# it in Drupal 11.
|
||||
# @see https://www.drupal.org/node/3320855
|
||||
entity.block_content_type.edit_form.bc:
|
||||
path: '/admin/structure/block/block-content/manage/{block_content_type}'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::blockContentTypeRedirect'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_permission: 'administer block types'
|
||||
|
||||
block_content.add_form:
|
||||
path: '/block/add/{block_content_type}'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::addForm'
|
||||
_title_callback: '\Drupal\block_content\Controller\BlockContentController::getAddFormTitle'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_create_access: 'block_content:{block_content_type}'
|
||||
|
||||
entity.block_content.canonical:
|
||||
path: '/admin/content/block/{block_content}'
|
||||
defaults:
|
||||
_entity_form: 'block_content.edit'
|
||||
_title_callback: '\Drupal\Core\Entity\Controller\EntityController::title'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.update'
|
||||
block_content: \d+
|
||||
|
||||
entity.block_content.edit_form:
|
||||
path: '/admin/content/block/{block_content}'
|
||||
defaults:
|
||||
_entity_form: 'block_content.edit'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.update'
|
||||
block_content: \d+
|
||||
|
||||
# @todo Deprecate this route once
|
||||
# https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
|
||||
# it in Drupal 11.
|
||||
# @see https://www.drupal.org/node/2317981
|
||||
entity.block_content.edit_form.bc:
|
||||
path: '/block/{block_content}'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::editRedirect'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.update'
|
||||
block_content: \d+
|
||||
|
||||
entity.block_content.delete_form:
|
||||
path: '/admin/content/block/{block_content}/delete'
|
||||
defaults:
|
||||
_entity_form: 'block_content.delete'
|
||||
_title: 'Delete'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.delete'
|
||||
block_content: \d+
|
||||
|
||||
# @todo Deprecate this route once
|
||||
# https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
|
||||
# it in Drupal 11.
|
||||
# @see https://www.drupal.org/node/2317981
|
||||
entity.block_content.delete_form.bc:
|
||||
path: '/block/{block_content}/delete'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::editRedirect'
|
||||
options:
|
||||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.delete'
|
||||
block_content: \d+
|
||||
|
||||
block_content.type_add:
|
||||
path: '/admin/structure/block-content/add'
|
||||
defaults:
|
||||
_entity_form: 'block_content_type.add'
|
||||
_title: 'Add'
|
||||
requirements:
|
||||
_entity_create_access: 'block_content_type'
|
||||
|
||||
entity.block_content.collection:
|
||||
path: '/admin/content/block'
|
||||
defaults:
|
||||
_title: 'Content blocks'
|
||||
_entity_list: 'block_content'
|
||||
requirements:
|
||||
_permission: 'access block library+administer block content'
|
||||
|
||||
# @todo Deprecate this route once
|
||||
# https://www.drupal.org/project/drupal/issues/3159210 is fixed, or remove
|
||||
# it in Drupal 11.
|
||||
# @see https://www.drupal.org/node/3320855
|
||||
entity.block_content.collection.bc:
|
||||
path: '/admin/structure/block/block-content'
|
||||
defaults:
|
||||
_controller: '\Drupal\block_content\Controller\BlockContentController::blockLibraryRedirect'
|
||||
requirements:
|
||||
_permission: 'access block library+administer block content'
|
||||
11
block_content.services.yml
Normal file
11
block_content.services.yml
Normal file
@ -0,0 +1,11 @@
|
||||
services:
|
||||
_defaults:
|
||||
autoconfigure: true
|
||||
block_content.uuid_lookup:
|
||||
class: \Drupal\block_content\BlockContentUuidLookup
|
||||
arguments: ['@cache.bootstrap', '@lock', '@entity_type.manager']
|
||||
tags:
|
||||
- { name: needs_destruction }
|
||||
block_content.bc_subscriber:
|
||||
class: Drupal\block_content\Routing\RouteSubscriber
|
||||
arguments: ['@entity_type.manager', '@module_handler']
|
||||
10
config/install/core.entity_view_mode.block_content.full.yml
Normal file
10
config/install/core.entity_view_mode.block_content.full.yml
Normal file
@ -0,0 +1,10 @@
|
||||
langcode: en
|
||||
status: false
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: block_content.full
|
||||
label: Full
|
||||
description: ''
|
||||
targetEntityType: block_content
|
||||
cache: true
|
||||
18
config/install/field.storage.block_content.body.yml
Normal file
18
config/install/field.storage.block_content.body.yml
Normal file
@ -0,0 +1,18 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
- text
|
||||
id: block_content.body
|
||||
field_name: body
|
||||
entity_type: block_content
|
||||
type: text_with_summary
|
||||
settings: { }
|
||||
module: text
|
||||
locked: false
|
||||
cardinality: 1
|
||||
translatable: true
|
||||
indexes: { }
|
||||
persist_with_no_fields: true
|
||||
custom_storage: false
|
||||
550
config/optional/views.view.block_content.yml
Normal file
550
config/optional/views.view.block_content.yml
Normal file
@ -0,0 +1,550 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
- user
|
||||
id: block_content
|
||||
label: 'Content blocks'
|
||||
module: views
|
||||
description: 'Find and manage content blocks.'
|
||||
tag: default
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: 'Content blocks'
|
||||
fields:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: null
|
||||
entity_field: info
|
||||
plugin_id: field
|
||||
label: 'Block description'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
type:
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
plugin_id: field
|
||||
label: 'Block type'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
changed:
|
||||
id: changed
|
||||
table: block_content_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
label: Updated
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: short
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
tooltip:
|
||||
date_format: long
|
||||
custom_date_format: ''
|
||||
time_diff:
|
||||
enabled: false
|
||||
future_format: '@interval hence'
|
||||
past_format: '@interval ago'
|
||||
granularity: 2
|
||||
refresh: 60
|
||||
operations:
|
||||
id: operations
|
||||
table: block_content
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
plugin_id: entity_operations
|
||||
label: Operations
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
destination: true
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
offset: 0
|
||||
pagination_heading_level: h4
|
||||
items_per_page: 50
|
||||
total_pages: null
|
||||
id: 0
|
||||
tags:
|
||||
next: 'Next ›'
|
||||
previous: '‹ Previous'
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: true
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access block library'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: text_custom
|
||||
empty: true
|
||||
content: 'There are no content blocks available.'
|
||||
tokenize: false
|
||||
block_content_listing_empty:
|
||||
id: block_content_listing_empty
|
||||
table: block_content
|
||||
field: block_content_listing_empty
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
plugin_id: block_content_listing_empty
|
||||
label: ''
|
||||
empty: true
|
||||
sorts: { }
|
||||
arguments: { }
|
||||
filters:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: info
|
||||
plugin_id: string
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: info_op
|
||||
label: 'Block description'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: info_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: info
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
type:
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: type_op
|
||||
label: 'Block type'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: type_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: type
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reusable:
|
||||
id: reusable
|
||||
table: block_content_field_data
|
||||
field: reusable
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: reusable
|
||||
plugin_id: boolean
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
columns:
|
||||
info: info
|
||||
type: type
|
||||
changed: changed
|
||||
operations: operations
|
||||
default: changed
|
||||
info:
|
||||
info:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
type:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
changed:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
operations:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
override: true
|
||||
sticky: false
|
||||
summary: ''
|
||||
empty_table: true
|
||||
caption: ''
|
||||
description: ''
|
||||
row:
|
||||
type: fields
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/content/block
|
||||
menu:
|
||||
type: tab
|
||||
title: 'Blocks'
|
||||
description: 'Create and edit content blocks.'
|
||||
weight: 0
|
||||
menu_name: admin
|
||||
parent: system.admin_content
|
||||
context: '0'
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
24
config/schema/block_content.schema.yml
Normal file
24
config/schema/block_content.schema.yml
Normal file
@ -0,0 +1,24 @@
|
||||
# Schema for the configuration files of the Content Block module.
|
||||
|
||||
block_content.type.*:
|
||||
type: config_entity
|
||||
label: 'Block type settings'
|
||||
mapping:
|
||||
id:
|
||||
type: machine_name
|
||||
label: 'ID'
|
||||
label:
|
||||
type: required_label
|
||||
label: 'Label'
|
||||
revision:
|
||||
type: boolean
|
||||
label: 'Whether a new revision should be created by default'
|
||||
description:
|
||||
type: text
|
||||
label: 'Description'
|
||||
nullable: true
|
||||
constraints:
|
||||
NotBlank:
|
||||
allowNull: true
|
||||
constraints:
|
||||
FullyValidatable: ~
|
||||
20
help_topics/block_content.add.html.twig
Normal file
20
help_topics/block_content.add.html.twig
Normal file
@ -0,0 +1,20 @@
|
||||
---
|
||||
label: 'Creating a content block'
|
||||
related:
|
||||
- block.overview
|
||||
- block.configure
|
||||
- block.place
|
||||
- block_content.type
|
||||
---
|
||||
{% set library_link_text %}{% trans %}Content blocks{% endtrans %}{% endset %}
|
||||
{% set library_link = render_var(help_route_link(library_link_text, 'entity.block_content.collection')) %}
|
||||
<h2>{% trans %}Goal{% endtrans %}</h2>
|
||||
<p>{% trans %}Create a content block, which can later be placed on the site.{% endtrans %}</p>
|
||||
<h2>{% trans %}Steps{% endtrans %}</h2>
|
||||
<ol>
|
||||
<li>{% trans %}In the <em>Manage</em> administrative menu, navigate to <em>Content</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Open the {{ library_link }} tab.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Add content block</em>. If you have more than one block type defined on your site, click the name of the type you want to create.{% endtrans %}</li>
|
||||
<li>{% trans %}Enter a description of your block (to be shown to administrators) and the body text for your block.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Save</em>.{% endtrans %}</li>
|
||||
</ol>
|
||||
24
help_topics/block_content.type.html.twig
Normal file
24
help_topics/block_content.type.html.twig
Normal file
@ -0,0 +1,24 @@
|
||||
---
|
||||
label: 'Defining a block type'
|
||||
related:
|
||||
- block.overview
|
||||
- block.configure
|
||||
- block.place
|
||||
- block_content.add
|
||||
- field_ui.add_field
|
||||
- field_ui.manage_form
|
||||
- field_ui.manage_display
|
||||
---
|
||||
{% set types_link_text %}{% trans %}Block types{% endtrans %}{% endset %}
|
||||
{% set types_link = render_var(help_route_link(types_link_text, 'entity.block_content_type.collection')) %}
|
||||
<h2>{% trans %}Goal{% endtrans %}</h2>
|
||||
<p>{% trans %}Define a block type and its fields.{% endtrans %}</p>
|
||||
<h2>{% trans %}Steps{% endtrans %}</h2>
|
||||
<ol>
|
||||
<li>{% trans %}In the <em>Manage</em> administrative menu, navigate to <em>Structure</em> > <em>{{ types_link }}</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Add block type</em>.{% endtrans %}</li>
|
||||
<li>{% trans %}Enter a label for this block type (shown in the administrative interface). Optionally, edit the automatically-generated machine name or the description.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Save</em>. You will be returned to the <em>Block types</em> page.{% endtrans %}</li>
|
||||
<li>{% trans %}Click <em>Manage fields</em> in the row of your new block type, and add the desired fields to your block type.{% endtrans %}</li>
|
||||
<li>{% trans %}Optionally, click <em>Manage form display</em> or <em>Manage display</em> to change the editing form or field display for your block type.{% endtrans %}</li>
|
||||
</ol>
|
||||
38
migrations/block_content_body_field.yml
Normal file
38
migrations/block_content_body_field.yml
Normal file
@ -0,0 +1,38 @@
|
||||
id: block_content_body_field
|
||||
label: Block content body field configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: embedded_data
|
||||
data_rows:
|
||||
-
|
||||
entity_type: block_content
|
||||
bundle: basic
|
||||
field_name: body
|
||||
label: Body
|
||||
display_summary: false
|
||||
allowed_formats: { }
|
||||
ids:
|
||||
entity_type:
|
||||
type: string
|
||||
bundle:
|
||||
type: string
|
||||
field_name:
|
||||
type: string
|
||||
source_module: block
|
||||
process:
|
||||
entity_type: entity_type
|
||||
bundle: bundle
|
||||
field_name: field_name
|
||||
label: label
|
||||
'settings/display_summary': display_summary
|
||||
destination:
|
||||
plugin: entity:field_config
|
||||
migration_dependencies:
|
||||
required:
|
||||
- block_content_type
|
||||
provider:
|
||||
- block_content
|
||||
- migrate_drupal
|
||||
40
migrations/block_content_entity_display.yml
Normal file
40
migrations/block_content_entity_display.yml
Normal file
@ -0,0 +1,40 @@
|
||||
id: block_content_entity_display
|
||||
label: Body field display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: embedded_data
|
||||
data_rows:
|
||||
-
|
||||
entity_type: block_content
|
||||
bundle: basic
|
||||
view_mode: default
|
||||
field_name: body
|
||||
options:
|
||||
label: hidden
|
||||
ids:
|
||||
entity_type:
|
||||
type: string
|
||||
bundle:
|
||||
type: string
|
||||
view_mode:
|
||||
type: string
|
||||
field_name:
|
||||
type: string
|
||||
source_module: block
|
||||
process:
|
||||
entity_type: entity_type
|
||||
bundle: bundle
|
||||
view_mode: view_mode
|
||||
field_name: field_name
|
||||
options: options
|
||||
destination:
|
||||
plugin: component_entity_display
|
||||
migration_dependencies:
|
||||
required:
|
||||
- block_content_body_field
|
||||
provider:
|
||||
- block_content
|
||||
- migrate_drupal
|
||||
37
migrations/block_content_entity_form_display.yml
Normal file
37
migrations/block_content_entity_form_display.yml
Normal file
@ -0,0 +1,37 @@
|
||||
id: block_content_entity_form_display
|
||||
label: Body field form display configuration
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: embedded_data
|
||||
data_rows:
|
||||
-
|
||||
entity_type: block_content
|
||||
bundle: basic
|
||||
form_mode: default
|
||||
field_name: body
|
||||
ids:
|
||||
entity_type:
|
||||
type: string
|
||||
bundle:
|
||||
type: string
|
||||
form_mode:
|
||||
type: string
|
||||
field_name:
|
||||
type: string
|
||||
source_module: block
|
||||
process:
|
||||
entity_type: entity_type
|
||||
bundle: bundle
|
||||
form_mode: form_mode
|
||||
field_name: field_name
|
||||
destination:
|
||||
plugin: component_entity_form_display
|
||||
migration_dependencies:
|
||||
required:
|
||||
- block_content_body_field
|
||||
provider:
|
||||
- block_content
|
||||
- migrate_drupal
|
||||
24
migrations/block_content_type.yml
Normal file
24
migrations/block_content_type.yml
Normal file
@ -0,0 +1,24 @@
|
||||
id: block_content_type
|
||||
label: Block content type
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Drupal 7
|
||||
- Configuration
|
||||
source:
|
||||
plugin: embedded_data
|
||||
data_rows:
|
||||
-
|
||||
id: basic
|
||||
label: Basic
|
||||
ids:
|
||||
id:
|
||||
type: string
|
||||
source_module: block
|
||||
process:
|
||||
id: id
|
||||
label: label
|
||||
destination:
|
||||
plugin: entity:block_content_type
|
||||
provider:
|
||||
- block_content
|
||||
- migrate_drupal
|
||||
24
migrations/d6_custom_block.yml
Normal file
24
migrations/d6_custom_block.yml
Normal file
@ -0,0 +1,24 @@
|
||||
id: d6_custom_block
|
||||
label: Content blocks
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 6
|
||||
- Content
|
||||
source:
|
||||
plugin: d6_box
|
||||
process:
|
||||
id: bid
|
||||
info: info
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d6_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
destination:
|
||||
plugin: entity:block_content
|
||||
default_bundle: basic
|
||||
no_stub: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d6_filter_format
|
||||
- block_content_body_field
|
||||
24
migrations/d7_custom_block.yml
Normal file
24
migrations/d7_custom_block.yml
Normal file
@ -0,0 +1,24 @@
|
||||
id: d7_custom_block
|
||||
label: Content blocks
|
||||
audit: true
|
||||
migration_tags:
|
||||
- Drupal 7
|
||||
- Content
|
||||
source:
|
||||
plugin: d7_block_custom
|
||||
process:
|
||||
id: bid
|
||||
info: info
|
||||
'body/format':
|
||||
plugin: migration_lookup
|
||||
migration: d7_filter_format
|
||||
source: format
|
||||
'body/value': body
|
||||
destination:
|
||||
plugin: entity:block_content
|
||||
default_bundle: basic
|
||||
no_stub: true
|
||||
migration_dependencies:
|
||||
required:
|
||||
- d7_filter_format
|
||||
- block_content_body_field
|
||||
5
migrations/state/block_content.migrate_drupal.yml
Normal file
5
migrations/state/block_content.migrate_drupal.yml
Normal file
@ -0,0 +1,5 @@
|
||||
finished:
|
||||
6:
|
||||
block: block_content
|
||||
7:
|
||||
block: block_content
|
||||
49
src/Access/AccessGroupAnd.php
Normal file
49
src/Access/AccessGroupAnd.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* An access group where all the dependencies must be allowed.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class AccessGroupAnd implements AccessibleInterface {
|
||||
|
||||
/**
|
||||
* The access dependencies.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessibleInterface[]
|
||||
*/
|
||||
protected $dependencies = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addDependency(AccessibleInterface $dependency) {
|
||||
$this->dependencies[] = $dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function access($operation, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
|
||||
$access_result = AccessResult::neutral();
|
||||
foreach (array_slice($this->dependencies, 1) as $dependency) {
|
||||
$access_result = $access_result->andIf($dependency->access($operation, $account, TRUE));
|
||||
}
|
||||
return $return_as_object ? $access_result : $access_result->isAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDependencies() {
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
}
|
||||
35
src/Access/DependentAccessInterface.php
Normal file
35
src/Access/DependentAccessInterface.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Access;
|
||||
|
||||
/**
|
||||
* Interface for AccessibleInterface objects that have an access dependency.
|
||||
*
|
||||
* Objects should implement this interface when their access depends on access
|
||||
* to another object that implements \Drupal\Core\Access\AccessibleInterface.
|
||||
* This interface simply provides the getter method for the access
|
||||
* dependency object. Objects that implement this interface are responsible for
|
||||
* checking access of the access dependency because the dependency may not take
|
||||
* effect in all cases. For instance an entity may only need the access
|
||||
* dependency set when it is embedded within another entity and its access
|
||||
* should be dependent on access to the entity in which it is embedded.
|
||||
*
|
||||
* To check the access to the dependency the object implementing this interface
|
||||
* can use code like this:
|
||||
* @code
|
||||
* $accessible->getAccessDependency()->access($op, $account, TRUE);
|
||||
* @endcode
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface DependentAccessInterface {
|
||||
|
||||
/**
|
||||
* Gets the access dependency.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessibleInterface|null
|
||||
* The access dependency or NULL if none has been set.
|
||||
*/
|
||||
public function getAccessDependency();
|
||||
|
||||
}
|
||||
48
src/Access/RefinableDependentAccessInterface.php
Normal file
48
src/Access/RefinableDependentAccessInterface.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
|
||||
/**
|
||||
* An interface to allow adding an access dependency.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface RefinableDependentAccessInterface extends DependentAccessInterface {
|
||||
|
||||
/**
|
||||
* Sets the access dependency.
|
||||
*
|
||||
* If an access dependency is already set this will replace the existing
|
||||
* dependency.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $access_dependency
|
||||
* The object upon which access depends.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setAccessDependency(AccessibleInterface $access_dependency);
|
||||
|
||||
/**
|
||||
* Adds an access dependency into the existing access dependency.
|
||||
*
|
||||
* If no existing dependency is currently set this will set the dependency
|
||||
* will be set to the new value.
|
||||
*
|
||||
* If there is an existing dependency and it is not an instance of
|
||||
* AccessGroupAnd the dependency will be set as a new AccessGroupAnd
|
||||
* instance with the existing and new dependencies as the members of the
|
||||
* group.
|
||||
*
|
||||
* If there is an existing dependency and it is an instance of AccessGroupAnd
|
||||
* the dependency will be added to the existing access group.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $access_dependency
|
||||
* The access dependency to merge.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function addAccessDependency(AccessibleInterface $access_dependency);
|
||||
|
||||
}
|
||||
52
src/Access/RefinableDependentAccessTrait.php
Normal file
52
src/Access/RefinableDependentAccessTrait.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Access;
|
||||
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
|
||||
/**
|
||||
* Trait for \Drupal\block_content\Access\RefinableDependentAccessInterface.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
trait RefinableDependentAccessTrait {
|
||||
|
||||
/**
|
||||
* The access dependency.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessibleInterface
|
||||
*/
|
||||
protected $accessDependency;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setAccessDependency(AccessibleInterface $access_dependency) {
|
||||
$this->accessDependency = $access_dependency;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getAccessDependency() {
|
||||
return $this->accessDependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addAccessDependency(AccessibleInterface $access_dependency) {
|
||||
if (empty($this->accessDependency)) {
|
||||
$this->accessDependency = $access_dependency;
|
||||
return $this;
|
||||
}
|
||||
if (!$this->accessDependency instanceof AccessGroupAnd) {
|
||||
$accessGroup = new AccessGroupAnd();
|
||||
$this->accessDependency = $accessGroup->addDependency($this->accessDependency);
|
||||
}
|
||||
$this->accessDependency->addDependency($access_dependency);
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
116
src/BlockContentAccessControlHandler.php
Normal file
116
src/BlockContentAccessControlHandler.php
Normal file
@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\block_content\Access\DependentAccessInterface;
|
||||
use Drupal\block_content\Event\BlockContentGetDependencyEvent;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityHandlerInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the content block entity type.
|
||||
*
|
||||
* @see \Drupal\block_content\Entity\BlockContent
|
||||
*/
|
||||
class BlockContentAccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
* @var \Symfony\Contracts\EventDispatcher\EventDispatcherInterface
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* BlockContentAccessControlHandler constructor.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
* @param \Symfony\Contracts\EventDispatcher\EventDispatcherInterface $dispatcher
|
||||
* The event dispatcher.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, EventDispatcherInterface $dispatcher) {
|
||||
parent::__construct($entity_type);
|
||||
$this->eventDispatcher = $dispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('event_dispatcher')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
assert($entity instanceof BlockContentInterface);
|
||||
$bundle = $entity->bundle();
|
||||
$forbidIfNotReusable = fn (): AccessResultInterface => AccessResult::forbiddenIf($entity->isReusable() === FALSE, sprintf('Block content must be reusable to use `%s` operation', $operation));
|
||||
$access = AccessResult::allowedIfHasPermissions($account, ['administer block content']);
|
||||
if (!$access->isAllowed()) {
|
||||
$access = match ($operation) {
|
||||
// Allow view and update access to user with the 'edit any (type) block
|
||||
// content' permission or the 'administer block content' permission.
|
||||
'view' => AccessResult::allowedIf($entity->isPublished())
|
||||
->orIf(AccessResult::allowedIfHasPermission($account, 'access block library')),
|
||||
'update' => AccessResult::allowedIfHasPermission($account, 'edit any ' . $bundle . ' block content'),
|
||||
'delete' => AccessResult::allowedIfHasPermission($account, 'delete any ' . $bundle . ' block content'),
|
||||
// Revisions.
|
||||
'view revision', 'view all revisions' => AccessResult::allowedIfHasPermission($account, 'view any ' . $bundle . ' block content history'),
|
||||
'revert' => AccessResult::allowedIfHasPermission($account, 'revert any ' . $bundle . ' block content revisions')
|
||||
->orIf($forbidIfNotReusable()),
|
||||
'delete revision' => AccessResult::allowedIfHasPermission($account, 'delete any ' . $bundle . ' block content revisions')
|
||||
->orIf($forbidIfNotReusable()),
|
||||
|
||||
default => parent::checkAccess($entity, $operation, $account),
|
||||
};
|
||||
}
|
||||
|
||||
// Add the entity as a cacheable dependency because access will at least be
|
||||
// determined by whether the block is reusable.
|
||||
$access->addCacheableDependency($entity);
|
||||
if ($entity->isReusable() === FALSE && $access->isForbidden() !== TRUE) {
|
||||
if (!$entity instanceof DependentAccessInterface) {
|
||||
throw new \LogicException("Non-reusable block entities must implement \Drupal\block_content\Access\DependentAccessInterface for access control.");
|
||||
}
|
||||
$dependency = $entity->getAccessDependency();
|
||||
if (empty($dependency)) {
|
||||
// If an access dependency has not been set let modules set one.
|
||||
$event = new BlockContentGetDependencyEvent($entity);
|
||||
$this->eventDispatcher->dispatch($event, BlockContentEvents::BLOCK_CONTENT_GET_DEPENDENCY);
|
||||
$dependency = $event->getAccessDependency();
|
||||
if (empty($dependency)) {
|
||||
return AccessResult::forbidden("Non-reusable blocks must set an access dependency for access control.");
|
||||
}
|
||||
}
|
||||
/** @var \Drupal\Core\Entity\EntityInterface $dependency */
|
||||
$access = $access->andIf($dependency->access($operation, $account, TRUE));
|
||||
}
|
||||
return $access;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) {
|
||||
return AccessResult::allowedIfHasPermissions($account, [
|
||||
'create ' . $entity_bundle . ' block content',
|
||||
'access block library',
|
||||
])->orIf(AccessResult::allowedIfHasPermissions($account, [
|
||||
'administer block content',
|
||||
]));
|
||||
}
|
||||
|
||||
}
|
||||
31
src/BlockContentEvents.php
Normal file
31
src/BlockContentEvents.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
/**
|
||||
* Defines events for the block_content module.
|
||||
*
|
||||
* @see \Drupal\block_content\Event\BlockContentGetDependencyEvent
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class BlockContentEvents {
|
||||
|
||||
/**
|
||||
* Name of the event when getting the dependency of a non-reusable block.
|
||||
*
|
||||
* This event allows modules to provide a dependency for non-reusable block
|
||||
* access if
|
||||
* \Drupal\block_content\Access\DependentAccessInterface::getAccessDependency()
|
||||
* did not return a dependency during access checking.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
* @see \Drupal\block_content\Event\BlockContentGetDependencyEvent
|
||||
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const BLOCK_CONTENT_GET_DEPENDENCY = 'block_content.get_dependency';
|
||||
|
||||
}
|
||||
134
src/BlockContentForm.php
Normal file
134
src/BlockContentForm.php
Normal file
@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Entity\ContentEntityForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form handler for the content block edit forms.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentForm extends ContentEntityForm {
|
||||
|
||||
/**
|
||||
* The block content entity.
|
||||
*
|
||||
* @var \Drupal\block_content\BlockContentInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$block = $this->entity;
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
if ($this->operation == 'edit') {
|
||||
$form['#title'] = $this->t('Edit content block %label', ['%label' => $block->label()]);
|
||||
}
|
||||
// Override the default CSS class name, since the user-defined content block
|
||||
// type name in 'TYPE-block-form' potentially clashes with third-party class
|
||||
// names.
|
||||
$form['#attributes']['class'][0] = 'block-' . Html::getClass($block->bundle()) . '-form';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function actions(array $form, FormStateInterface $form_state): array {
|
||||
$element = parent::actions($form, $form_state);
|
||||
|
||||
if ($this->getRequest()->query->has('theme')) {
|
||||
$element['submit']['#value'] = $this->t('Save and configure');
|
||||
}
|
||||
|
||||
if ($this->currentUser()->hasPermission('administer blocks') && !$this->getRequest()->query->has('theme') && $this->entity->isNew()) {
|
||||
$element['configure_block'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save and configure'),
|
||||
'#weight' => 20,
|
||||
'#submit' => array_merge($element['submit']['#submit'], ['::configureBlock']),
|
||||
];
|
||||
}
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form submission handler for the 'configureBlock' action.
|
||||
*
|
||||
* @param array $form
|
||||
* An associative array containing the structure of the form.
|
||||
* @param \Drupal\Core\Form\FormStateInterface $form_state
|
||||
* The current state of the form.
|
||||
*/
|
||||
public function configureBlock(array $form, FormStateInterface $form_state): void {
|
||||
$block = $this->entity;
|
||||
if (!$theme = $block->getTheme()) {
|
||||
$theme = $this->config('system.theme')->get('default');
|
||||
}
|
||||
$form_state->setRedirect(
|
||||
'block.admin_add',
|
||||
[
|
||||
'plugin_id' => 'block_content:' . $block->uuid(),
|
||||
'theme' => $theme,
|
||||
]
|
||||
);
|
||||
$form_state->setIgnoreDestination();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$block = $this->entity;
|
||||
|
||||
$insert = $block->isNew();
|
||||
$block->save();
|
||||
$context = ['@type' => $block->bundle(), '%info' => $block->label()];
|
||||
$logger = $this->logger('block_content');
|
||||
$block_type = $this->getBundleEntity();
|
||||
$t_args = ['@type' => $block_type->label(), '%info' => $block->label()];
|
||||
|
||||
if ($insert) {
|
||||
$logger->info('@type: added %info.', $context);
|
||||
$this->messenger()->addStatus($this->t('@type %info has been created.', $t_args));
|
||||
}
|
||||
else {
|
||||
$logger->info('@type: updated %info.', $context);
|
||||
$this->messenger()->addStatus($this->t('@type %info has been updated.', $t_args));
|
||||
}
|
||||
|
||||
if ($block->id()) {
|
||||
$form_state->setValue('id', $block->id());
|
||||
$form_state->set('id', $block->id());
|
||||
$theme = $block->getTheme();
|
||||
if ($insert && $theme) {
|
||||
$form_state->setRedirect(
|
||||
'block.admin_add',
|
||||
[
|
||||
'plugin_id' => 'block_content:' . $block->uuid(),
|
||||
'theme' => $theme,
|
||||
'region' => $this->getRequest()->query->getString('region'),
|
||||
]
|
||||
);
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirectUrl($block->toUrl('collection'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// In the unlikely case something went wrong on save, the block will be
|
||||
// rebuilt and block form redisplayed.
|
||||
$this->messenger()->addError($this->t('The block could not be saved.'));
|
||||
$form_state->setRebuild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
84
src/BlockContentInterface.php
Normal file
84
src/BlockContentInterface.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\block_content\Access\RefinableDependentAccessInterface;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityChangedInterface;
|
||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||
use Drupal\Core\Entity\RevisionLogInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a content block entity.
|
||||
*/
|
||||
interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface, EntityPublishedInterface, RefinableDependentAccessInterface {
|
||||
|
||||
/**
|
||||
* Sets the block description.
|
||||
*
|
||||
* @param string $info
|
||||
* The block description.
|
||||
*
|
||||
* @return $this
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setInfo($info);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setReusable();
|
||||
|
||||
/**
|
||||
* Sets the block to be non-reusable.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setNonReusable();
|
||||
|
||||
/**
|
||||
* Sets the theme value.
|
||||
*
|
||||
* When creating a new block content block from the block library, the user is
|
||||
* redirected to the configure form for that block in the given theme. The
|
||||
* theme is stored against the block when the block content add form is shown.
|
||||
*
|
||||
* @param string $theme
|
||||
* The theme name.
|
||||
*
|
||||
* @return $this
|
||||
* The class instance that this method is called on.
|
||||
*/
|
||||
public function setTheme($theme);
|
||||
|
||||
/**
|
||||
* Gets the theme value.
|
||||
*
|
||||
* When creating a new block content block from the block library, the user is
|
||||
* redirected to the configure form for that block in the given theme. The
|
||||
* theme is stored against the block when the block content add form is shown.
|
||||
*
|
||||
* @return string
|
||||
* The theme name.
|
||||
*/
|
||||
public function getTheme();
|
||||
|
||||
/**
|
||||
* Gets the configured instances of this content block.
|
||||
*
|
||||
* @return array
|
||||
* Array of Drupal\block\Core\Plugin\Entity\Block entities.
|
||||
*/
|
||||
public function getInstances();
|
||||
|
||||
}
|
||||
60
src/BlockContentListBuilder.php
Normal file
60
src/BlockContentListBuilder.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityListBuilder;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of content block entities.
|
||||
*
|
||||
* @see \Drupal\block_content\Entity\BlockContent
|
||||
*/
|
||||
class BlockContentListBuilder extends EntityListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['label'] = $this->t('Block description');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['label'] = $entity->label();
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntityIds() {
|
||||
$request = \Drupal::request();
|
||||
$query = $this->getStorage()->getQuery()->accessCheck(TRUE);
|
||||
$foo = $request->get('foo') ?? 0;
|
||||
if ($foo) {
|
||||
$query->condition('label', "%" . $foo . "%", 'LIKE');
|
||||
}
|
||||
$query->sort($this->entityType->getKey('id'));
|
||||
$query->condition('reusable', TRUE);
|
||||
// Only add the pager if a limit is specified.
|
||||
if ($this->limit) {
|
||||
$query->pager($this->limit);
|
||||
}
|
||||
return $query->execute();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render() {
|
||||
$build['form'] = \Drupal::formBuilder()->getForm('Drupal\block_content\Form\BlockContentListFiltersForm');
|
||||
$build += parent::render();
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
84
src/BlockContentPermissions.php
Normal file
84
src/BlockContentPermissions.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||
use Drupal\Core\Entity\BundlePermissionHandlerTrait;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Provide dynamic permissions for blocks of different types.
|
||||
*/
|
||||
class BlockContentPermissions implements ContainerInjectionInterface {
|
||||
|
||||
use StringTranslationTrait;
|
||||
use BundlePermissionHandlerTrait;
|
||||
|
||||
/**
|
||||
* Constructs a BlockContentPermissions instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
|
||||
* Entity type manager.
|
||||
*/
|
||||
public function __construct(
|
||||
protected EntityTypeManagerInterface $entityTypeManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('entity_type.manager'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build permissions for each block type.
|
||||
*
|
||||
* @return array
|
||||
* The block type permissions.
|
||||
*/
|
||||
public function blockTypePermissions() {
|
||||
return $this->generatePermissions($this->entityTypeManager->getStorage('block_content_type')->loadMultiple(), [$this, 'buildPermissions']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the permissions available for a block type.
|
||||
*
|
||||
* @param \Drupal\block_content\Entity\BlockContentType $type
|
||||
* The block type.
|
||||
*
|
||||
* @return array
|
||||
* Permissions available for the given block type.
|
||||
*/
|
||||
protected function buildPermissions(BlockContentType $type) {
|
||||
$type_id = $type->id();
|
||||
$type_params = ['%type_name' => $type->label()];
|
||||
return [
|
||||
"create $type_id block content" => [
|
||||
'title' => $this->t('%type_name: Create new content block', $type_params),
|
||||
],
|
||||
"edit any $type_id block content" => [
|
||||
'title' => $this->t('%type_name: Edit content block', $type_params),
|
||||
],
|
||||
"delete any $type_id block content" => [
|
||||
'title' => $this->t('%type_name: Delete content block', $type_params),
|
||||
],
|
||||
"view any $type_id block content history" => [
|
||||
'title' => $this->t('%type_name: View content block history pages', $type_params),
|
||||
],
|
||||
"revert any $type_id block content revisions" => [
|
||||
'title' => $this->t('%type_name: Revert content block revisions', $type_params),
|
||||
],
|
||||
"delete any $type_id block content revisions" => [
|
||||
'title' => $this->t('%type_name: Delete content block revisions', $type_params),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
27
src/BlockContentStorageSchema.php
Normal file
27
src/BlockContentStorageSchema.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Defines the block content schema handler.
|
||||
*/
|
||||
class BlockContentStorageSchema extends SqlContentEntityStorageSchema {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getSharedTableFieldSchema(FieldStorageDefinitionInterface $storage_definition, $table_name, array $column_mapping): array {
|
||||
$schema = parent::getSharedTableFieldSchema($storage_definition, $table_name, $column_mapping);
|
||||
$field_name = $storage_definition->getName();
|
||||
|
||||
if ($table_name === $this->storage->getDataTable() && $field_name === 'reusable') {
|
||||
$this->addSharedTableFieldIndex($storage_definition, $schema);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
||||
22
src/BlockContentTranslationHandler.php
Normal file
22
src/BlockContentTranslationHandler.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\content_translation\ContentTranslationHandler;
|
||||
|
||||
/**
|
||||
* Defines the translation handler for content blocks.
|
||||
*/
|
||||
class BlockContentTranslationHandler extends ContentTranslationHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function entityFormTitle(EntityInterface $entity) {
|
||||
$block_type = BlockContentType::load($entity->bundle());
|
||||
return $this->t('<em>Edit @type</em> @title', ['@type' => $block_type->label(), '@title' => $entity->label()]);
|
||||
}
|
||||
|
||||
}
|
||||
128
src/BlockContentTypeForm.php
Normal file
128
src/BlockContentTypeForm.php
Normal file
@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Entity\BundleEntityFormBase;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\language\Entity\ContentLanguageSettings;
|
||||
|
||||
/**
|
||||
* The block content type entity form.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentTypeForm extends BundleEntityFormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function form(array $form, FormStateInterface $form_state) {
|
||||
$form = parent::form($form, $form_state);
|
||||
|
||||
/** @var \Drupal\block_content\BlockContentTypeInterface $block_type */
|
||||
$block_type = $this->entity;
|
||||
|
||||
if ($this->operation == 'add') {
|
||||
$form['#title'] = $this->t('Add block type');
|
||||
}
|
||||
else {
|
||||
$form['#title'] = $this->t('Edit %label block type', ['%label' => $block_type->label()]);
|
||||
}
|
||||
|
||||
$form['label'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => $this->t('Label'),
|
||||
'#maxlength' => 255,
|
||||
'#default_value' => $block_type->label(),
|
||||
'#description' => $this->t("The human-readable name for this block type, displayed on the <em>Block types</em> page."),
|
||||
'#required' => TRUE,
|
||||
];
|
||||
$form['id'] = [
|
||||
'#type' => 'machine_name',
|
||||
'#default_value' => $block_type->id(),
|
||||
'#machine_name' => [
|
||||
'exists' => '\Drupal\block_content\Entity\BlockContentType::load',
|
||||
],
|
||||
'#description' => $this->t("Unique machine-readable name: lowercase letters, numbers, and underscores only."),
|
||||
'#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
|
||||
];
|
||||
|
||||
$form['description'] = [
|
||||
'#type' => 'textarea',
|
||||
'#default_value' => $block_type->getDescription(),
|
||||
'#description' => $this->t('Displays on the <em>Block types</em> page.'),
|
||||
'#title' => $this->t('Description'),
|
||||
];
|
||||
|
||||
$form['revision'] = [
|
||||
'#type' => 'checkbox',
|
||||
'#title' => $this->t('Create new revision'),
|
||||
'#default_value' => $block_type->shouldCreateNewRevision(),
|
||||
'#description' => $this->t('Create a new revision by default for this block type.'),
|
||||
];
|
||||
|
||||
if ($this->moduleHandler->moduleExists('language')) {
|
||||
$form['language'] = [
|
||||
'#type' => 'details',
|
||||
'#title' => $this->t('Language settings'),
|
||||
'#group' => 'additional_settings',
|
||||
];
|
||||
|
||||
$language_configuration = ContentLanguageSettings::loadByEntityTypeBundle('block_content', $block_type->id());
|
||||
$form['language']['language_configuration'] = [
|
||||
'#type' => 'language_configuration',
|
||||
'#entity_information' => [
|
||||
'entity_type' => 'block_content',
|
||||
'bundle' => $block_type->id(),
|
||||
],
|
||||
'#default_value' => $language_configuration,
|
||||
];
|
||||
|
||||
$form['#submit'][] = 'language_configuration_element_submit';
|
||||
}
|
||||
|
||||
$form['actions'] = ['#type' => 'actions'];
|
||||
$form['actions']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => $this->t('Save'),
|
||||
];
|
||||
|
||||
return $this->protectBundleIdElement($form);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state) {
|
||||
// An empty description violates config schema.
|
||||
if (trim($form_state->getValue('description', '')) === '') {
|
||||
$form_state->unsetValue('description');
|
||||
}
|
||||
parent::copyFormValuesToEntity($entity, $form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(array $form, FormStateInterface $form_state) {
|
||||
$block_type = $this->entity;
|
||||
$status = $block_type->save();
|
||||
|
||||
$edit_link = $this->entity->toLink($this->t('Edit'), 'edit-form')->toString();
|
||||
$logger = $this->logger('block_content');
|
||||
if ($status == SAVED_UPDATED) {
|
||||
$this->messenger()->addStatus($this->t('Block type %label has been updated.', ['%label' => $block_type->label()]));
|
||||
$logger->notice('Block type %label has been updated.', ['%label' => $block_type->label(), 'link' => $edit_link]);
|
||||
}
|
||||
else {
|
||||
block_content_add_body_field($block_type->id());
|
||||
$this->messenger()->addStatus($this->t('Block type %label has been added.', ['%label' => $block_type->label()]));
|
||||
$logger->notice('Block type %label has been added.', ['%label' => $block_type->label(), 'link' => $edit_link]);
|
||||
}
|
||||
|
||||
$form_state->setRedirectUrl($this->entity->toUrl('collection'));
|
||||
}
|
||||
|
||||
}
|
||||
21
src/BlockContentTypeInterface.php
Normal file
21
src/BlockContentTypeInterface.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityInterface;
|
||||
use Drupal\Core\Entity\RevisionableEntityBundleInterface;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a block type entity.
|
||||
*/
|
||||
interface BlockContentTypeInterface extends ConfigEntityInterface, RevisionableEntityBundleInterface {
|
||||
|
||||
/**
|
||||
* Returns the description of the block type.
|
||||
*
|
||||
* @return string
|
||||
* The description of the type of this block.
|
||||
*/
|
||||
public function getDescription();
|
||||
|
||||
}
|
||||
53
src/BlockContentTypeListBuilder.php
Normal file
53
src/BlockContentTypeListBuilder.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
|
||||
/**
|
||||
* Defines a class to build a listing of block type entities.
|
||||
*
|
||||
* @see \Drupal\block_content\Entity\BlockContentType
|
||||
*/
|
||||
class BlockContentTypeListBuilder extends ConfigEntityListBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefaultOperations(EntityInterface $entity) {
|
||||
$operations = parent::getDefaultOperations($entity);
|
||||
// Place the edit operation after the operations added by field_ui.module
|
||||
// which have the weights 15, 20, 25.
|
||||
if (isset($operations['edit'])) {
|
||||
$operations['edit']['weight'] = 30;
|
||||
}
|
||||
return $operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildHeader() {
|
||||
$header['type'] = $this->t('Block type');
|
||||
$header['description'] = $this->t('Description');
|
||||
return $header + parent::buildHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildRow(EntityInterface $entity) {
|
||||
$row['type'] = $entity->toLink(NULL, 'edit-form')->toString();
|
||||
$row['description']['data']['#markup'] = $entity->getDescription();
|
||||
return $row + parent::buildRow($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getTitle() {
|
||||
return $this->t('Block types');
|
||||
}
|
||||
|
||||
}
|
||||
64
src/BlockContentUuidLookup.php
Normal file
64
src/BlockContentUuidLookup.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Cache\CacheCollector;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Lock\LockBackendInterface;
|
||||
|
||||
/**
|
||||
* A cache collector that caches IDs for block_content UUIDs.
|
||||
*
|
||||
* As block_content entities are used as block plugin derivatives, it is a
|
||||
* fairly safe limitation that there are not hundreds of them, a site will
|
||||
* likely run into problems with too many block content entities in other places
|
||||
* than a cache that only stores UUID's and IDs. The same assumption is not true
|
||||
* for other content entities.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentUuidLookup extends CacheCollector {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a BlockContentUuidLookup instance.
|
||||
*
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Lock\LockBackendInterface $lock
|
||||
* The lock backend.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager.
|
||||
*/
|
||||
public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct('block_content_uuid', $cache, $lock);
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function resolveCacheMiss($key) {
|
||||
$ids = $this->entityTypeManager->getStorage('block_content')->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('uuid', $key)
|
||||
->execute();
|
||||
|
||||
// Only cache if there is a match, otherwise creating new entities would
|
||||
// require to invalidate the cache.
|
||||
$id = reset($ids);
|
||||
if ($id) {
|
||||
$this->storage[$key] = $id;
|
||||
$this->persist($key);
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
|
||||
}
|
||||
50
src/BlockContentViewBuilder.php
Normal file
50
src/BlockContentViewBuilder.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityViewBuilder;
|
||||
|
||||
/**
|
||||
* View builder handler for content blocks.
|
||||
*
|
||||
* Note: Content blocks (block_content entities) are not designed to be displayed
|
||||
* outside of blocks! This BlockContentViewBuilder class is designed to be used
|
||||
* by \Drupal\block_content\Plugin\Block\BlockContentBlock::build() and by
|
||||
* nothing else.
|
||||
*
|
||||
* @see \Drupal\block_content\Plugin\Block\BlockContentBlock
|
||||
*/
|
||||
class BlockContentViewBuilder extends EntityViewBuilder {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
|
||||
return $this->viewMultiple([$entity], $view_mode, $langcode)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function viewMultiple(array $entities = [], $view_mode = 'full', $langcode = NULL) {
|
||||
$build_list = parent::viewMultiple($entities, $view_mode, $langcode);
|
||||
// Apply the buildMultiple() #pre_render callback immediately, to make
|
||||
// bubbling of attributes and contextual links to the actual block work.
|
||||
// @see \Drupal\block\BlockViewBuilder::buildBlock()
|
||||
unset($build_list['#pre_render'][0]);
|
||||
return $this->buildMultiple($build_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getBuildDefaults(EntityInterface $entity, $view_mode) {
|
||||
$build = parent::getBuildDefaults($entity, $view_mode);
|
||||
// The content block will be rendered in the wrapped block template already
|
||||
// and thus has no entity template itself.
|
||||
unset($build['#theme']);
|
||||
return $build;
|
||||
}
|
||||
|
||||
}
|
||||
42
src/BlockContentViewsData.php
Normal file
42
src/BlockContentViewsData.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\views\EntityViewsData;
|
||||
|
||||
/**
|
||||
* Provides the views data for the block_content entity type.
|
||||
*/
|
||||
class BlockContentViewsData extends EntityViewsData {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getViewsData() {
|
||||
|
||||
$data = parent::getViewsData();
|
||||
|
||||
$data['block_content_field_data']['id']['field']['id'] = 'field';
|
||||
|
||||
$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']['type']['field']['id'] = 'field';
|
||||
|
||||
$data['block_content_field_data']['table']['wizard_id'] = 'block_content';
|
||||
|
||||
$data['block_content']['block_content_listing_empty'] = [
|
||||
'title' => $this->t('Empty block library behavior'),
|
||||
'help' => $this->t('Provides a link to add a new block.'),
|
||||
'area' => [
|
||||
'id' => 'block_content_listing_empty',
|
||||
],
|
||||
];
|
||||
// Advertise this table as a possible base table.
|
||||
$data['block_content_field_revision']['table']['base']['help'] = $this->t('Block Content revision is a history of changes to block content.');
|
||||
$data['block_content_field_revision']['table']['base']['defaults']['title'] = 'info';
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
}
|
||||
33
src/BlockTypeAccessControlHandler.php
Normal file
33
src/BlockTypeAccessControlHandler.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Entity\EntityAccessControlHandler;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines the access control handler for the "Block Type" entity type.
|
||||
*
|
||||
* @see \Drupal\block_content\Entity\BlockContentType
|
||||
*/
|
||||
class BlockTypeAccessControlHandler extends EntityAccessControlHandler {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $viewLabelOperation = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account) {
|
||||
if ($operation === 'view label') {
|
||||
return AccessResult::allowedIfHasPermission($account, 'access block library')
|
||||
->orIf(parent::checkAccess($entity, $operation, $account));
|
||||
}
|
||||
return parent::checkAccess($entity, $operation, $account);
|
||||
}
|
||||
|
||||
}
|
||||
239
src/Controller/BlockContentController.php
Normal file
239
src/Controller/BlockContentController.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Controller;
|
||||
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Routing\PathChangedHelper;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\block_content\BlockContentTypeInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
|
||||
class BlockContentController extends ControllerBase {
|
||||
|
||||
/**
|
||||
* The content block storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $blockContentStorage;
|
||||
|
||||
/**
|
||||
* The content block type storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $blockContentTypeStorage;
|
||||
|
||||
/**
|
||||
* The theme handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||
*/
|
||||
protected $themeHandler;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
return new static(
|
||||
$entity_type_manager->getStorage('block_content'),
|
||||
$entity_type_manager->getStorage('block_content_type'),
|
||||
$container->get('theme_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a BlockContent object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $block_content_storage
|
||||
* The content block storage.
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $block_content_type_storage
|
||||
* The block type storage.
|
||||
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||
* The theme handler.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $block_content_storage, EntityStorageInterface $block_content_type_storage, ThemeHandlerInterface $theme_handler) {
|
||||
$this->blockContentStorage = $block_content_storage;
|
||||
$this->blockContentTypeStorage = $block_content_type_storage;
|
||||
$this->themeHandler = $theme_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays add content block links for available types.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @return array
|
||||
* A render array for a list of the block types that can be added or
|
||||
* if there is only one block type defined for the site, the function
|
||||
* returns the content block add page for that block type.
|
||||
*/
|
||||
public function add(Request $request) {
|
||||
// @todo deprecate see https://www.drupal.org/project/drupal/issues/3346394.
|
||||
$types = [];
|
||||
// Only use block types the user has access to.
|
||||
foreach ($this->blockContentTypeStorage->loadMultiple() as $type) {
|
||||
$access = $this->entityTypeManager()->getAccessControlHandler('block_content')->createAccess($type->id(), NULL, [], TRUE);
|
||||
if ($access->isAllowed()) {
|
||||
$types[$type->id()] = $type;
|
||||
}
|
||||
}
|
||||
uasort($types, [$this->blockContentTypeStorage->getEntityType()->getClass(), 'sort']);
|
||||
if ($types && count($types) == 1) {
|
||||
$type = reset($types);
|
||||
return $this->addForm($type, $request);
|
||||
}
|
||||
if (count($types) === 0) {
|
||||
return [
|
||||
'#markup' => $this->t('You have not created any block types yet. Go to the <a href=":url">block type creation page</a> to add a new block type.', [
|
||||
':url' => Url::fromRoute('block_content.type_add')->toString(),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
return ['#theme' => 'block_content_add_list', '#content' => $types];
|
||||
}
|
||||
|
||||
/**
|
||||
* Presents the content block creation form.
|
||||
*
|
||||
* @param \Drupal\block_content\BlockContentTypeInterface $block_content_type
|
||||
* The block type to add.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @return array
|
||||
* A form array as expected by
|
||||
* \Drupal\Core\Render\RendererInterface::render().
|
||||
*/
|
||||
public function addForm(BlockContentTypeInterface $block_content_type, Request $request) {
|
||||
$block = $this->blockContentStorage->create([
|
||||
'type' => $block_content_type->id(),
|
||||
]);
|
||||
if (($theme = $request->query->get('theme')) && in_array($theme, array_keys($this->themeHandler->listInfo()))) {
|
||||
// We have navigated to this page from the block library and will keep track
|
||||
// of the theme for redirecting the user to the configuration page for the
|
||||
// newly created block in the given theme.
|
||||
$block->setTheme($theme);
|
||||
}
|
||||
return $this->entityFormBuilder()->getForm($block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides the page title for this controller.
|
||||
*
|
||||
* @param \Drupal\block_content\BlockContentTypeInterface $block_content_type
|
||||
* The block type being added.
|
||||
*
|
||||
* @return string
|
||||
* The page title.
|
||||
*/
|
||||
public function getAddFormTitle(BlockContentTypeInterface $block_content_type) {
|
||||
return $this->t('Add %type content block', ['%type' => $block_content_type->label()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a redirect to the list of block types.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* A route match object, used for the route name and the parameters.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*
|
||||
* @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use
|
||||
* /admin/structure/block-content directly instead of
|
||||
* /admin/structure/block/block-content/types.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3320855
|
||||
*/
|
||||
public function blockContentTypeRedirect(RouteMatchInterface $route_match, Request $request): RedirectResponse {
|
||||
@trigger_error('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
|
||||
$helper = new PathChangedHelper($route_match, $request);
|
||||
$params = [
|
||||
'%old_path' => $helper->oldPath(),
|
||||
'%new_path' => $helper->newPath(),
|
||||
'%change_record' => 'https://www.drupal.org/node/3320855',
|
||||
];
|
||||
$warning_message = $this->t('You have been redirected from %old_path. Update links, shortcuts, and bookmarks to use %new_path.', $params);
|
||||
$this->messenger()->addWarning($warning_message);
|
||||
$this->getLogger('block_content')->warning('A user was redirected from %old_path. This redirect will be removed in a future version of Drupal. Update links, shortcuts, and bookmarks to use %new_path. See %change_record for more information.', $params);
|
||||
|
||||
return $helper->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a redirect to the content block library.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* A route match object, used for the route name and the parameters.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*
|
||||
* @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use
|
||||
* /admin/content/block directly instead of
|
||||
* /admin/structure/block/block-content.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3320855
|
||||
*/
|
||||
public function blockLibraryRedirect(RouteMatchInterface $route_match, Request $request) {
|
||||
@trigger_error('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
|
||||
$helper = new PathChangedHelper($route_match, $request);
|
||||
$params = [
|
||||
'%old_path' => $helper->oldPath(),
|
||||
'%new_path' => $helper->newPath(),
|
||||
'%change_record' => 'https://www.drupal.org/node/3320855',
|
||||
];
|
||||
$warning_message = $this->t('You have been redirected from %old_path. Update links, shortcuts, and bookmarks to use %new_path.', $params);
|
||||
$this->messenger()->addWarning($warning_message);
|
||||
$this->getLogger('block_content')
|
||||
->warning('A user was redirected from %old_path. This redirect will be removed in a future version of Drupal. Update links, shortcuts, and bookmarks to use %new_path. See %change_record for more information.', $params);
|
||||
|
||||
return $helper->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a redirect to block edit page.
|
||||
*
|
||||
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
|
||||
* A route match object, used for the route name and the parameters.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The current request object.
|
||||
* @param Drupal\block_content\BlockContentInterface $block_content
|
||||
* The block to be edited.
|
||||
*
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse
|
||||
*
|
||||
* @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use
|
||||
* /admin/content/block/{block_content} directly instead of
|
||||
* /block/{block_content}.
|
||||
*
|
||||
* @see https://www.drupal.org/node/3320855
|
||||
*/
|
||||
public function editRedirect(RouteMatchInterface $route_match, Request $request, BlockContentInterface $block_content): RedirectResponse {
|
||||
@trigger_error('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855', E_USER_DEPRECATED);
|
||||
$helper = new PathChangedHelper($route_match, $request);
|
||||
$params = [
|
||||
'%old_path' => $helper->oldPath(),
|
||||
'%new_path' => $helper->newPath(),
|
||||
'%change_record' => 'https://www.drupal.org/node/3320855',
|
||||
];
|
||||
$warning_message = $this->t('You have been redirected from %old_path. Update links, shortcuts, and bookmarks to use %new_path.', $params);
|
||||
$this->messenger()->addWarning($warning_message);
|
||||
$this->getLogger('block_content')->warning('A user was redirected from %old_path to %new_path. This redirect will be removed in a future version of Drupal. Update links, shortcuts, and bookmarks to use %new_path. See %change_record for more information.', $params);
|
||||
|
||||
return $helper->redirect();
|
||||
}
|
||||
|
||||
}
|
||||
274
src/Entity/BlockContent.php
Normal file
274
src/Entity/BlockContent.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Entity;
|
||||
|
||||
use Drupal\block_content\Access\RefinableDependentAccessTrait;
|
||||
use Drupal\Core\Entity\EditorialContentEntityBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Field\BaseFieldDefinition;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
|
||||
/**
|
||||
* Defines the content block entity class.
|
||||
*
|
||||
* @ContentEntityType(
|
||||
* id = "block_content",
|
||||
* label = @Translation("Content block"),
|
||||
* label_collection = @Translation("Content blocks"),
|
||||
* label_singular = @Translation("content block"),
|
||||
* label_plural = @Translation("content blocks"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count content block",
|
||||
* plural = "@count content blocks",
|
||||
* ),
|
||||
* bundle_label = @Translation("Block type"),
|
||||
* handlers = {
|
||||
* "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
|
||||
* "storage_schema" = "Drupal\block_content\BlockContentStorageSchema",
|
||||
* "access" = "Drupal\block_content\BlockContentAccessControlHandler",
|
||||
* "list_builder" = "Drupal\block_content\BlockContentListBuilder",
|
||||
* "view_builder" = "Drupal\block_content\BlockContentViewBuilder",
|
||||
* "views_data" = "Drupal\block_content\BlockContentViewsData",
|
||||
* "form" = {
|
||||
* "add" = "Drupal\block_content\BlockContentForm",
|
||||
* "edit" = "Drupal\block_content\BlockContentForm",
|
||||
* "delete" = "Drupal\block_content\Form\BlockContentDeleteForm",
|
||||
* "default" = "Drupal\block_content\BlockContentForm",
|
||||
* "revision-delete" = \Drupal\Core\Entity\Form\RevisionDeleteForm::class,
|
||||
* "revision-revert" = \Drupal\Core\Entity\Form\RevisionRevertForm::class,
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "revision" = \Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider::class,
|
||||
* },
|
||||
* "translation" = "Drupal\block_content\BlockContentTranslationHandler"
|
||||
* },
|
||||
* admin_permission = "administer block content",
|
||||
* collection_permission = "access block library",
|
||||
* base_table = "block_content",
|
||||
* revision_table = "block_content_revision",
|
||||
* data_table = "block_content_field_data",
|
||||
* revision_data_table = "block_content_field_revision",
|
||||
* show_revision_ui = TRUE,
|
||||
* links = {
|
||||
* "canonical" = "/admin/content/block/{block_content}",
|
||||
* "delete-form" = "/admin/content/block/{block_content}/delete",
|
||||
* "edit-form" = "/admin/content/block/{block_content}",
|
||||
* "collection" = "/admin/content/block",
|
||||
* "create" = "/block",
|
||||
* "revision-delete-form" = "/admin/content/block/{block_content}/revision/{block_content_revision}/delete",
|
||||
* "revision-revert-form" = "/admin/content/block/{block_content}/revision/{block_content_revision}/revert",
|
||||
* "version-history" = "/admin/content/block/{block_content}/revisions",
|
||||
* },
|
||||
* translatable = TRUE,
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "revision" = "revision_id",
|
||||
* "bundle" = "type",
|
||||
* "label" = "info",
|
||||
* "langcode" = "langcode",
|
||||
* "uuid" = "uuid",
|
||||
* "published" = "status",
|
||||
* },
|
||||
* revision_metadata_keys = {
|
||||
* "revision_user" = "revision_user",
|
||||
* "revision_created" = "revision_created",
|
||||
* "revision_log_message" = "revision_log"
|
||||
* },
|
||||
* bundle_entity_type = "block_content_type",
|
||||
* field_ui_base_route = "entity.block_content_type.edit_form",
|
||||
* render_cache = FALSE,
|
||||
* )
|
||||
*
|
||||
* Note that render caching of block_content entities is disabled because they
|
||||
* are always rendered as blocks, and blocks already have their own render
|
||||
* caching.
|
||||
* See https://www.drupal.org/node/2284917#comment-9132521 for more information.
|
||||
*/
|
||||
class BlockContent extends EditorialContentEntityBase implements BlockContentInterface {
|
||||
|
||||
use RefinableDependentAccessTrait;
|
||||
|
||||
/**
|
||||
* The theme the block is being created in.
|
||||
*
|
||||
* When creating a new content block from the block library, the user is
|
||||
* redirected to the configure form for that block in the given theme. The
|
||||
* theme is stored against the block when the content block add form is shown.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $theme;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createDuplicate() {
|
||||
$duplicate = parent::createDuplicate();
|
||||
$duplicate->revision_id->value = NULL;
|
||||
$duplicate->id->value = NULL;
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTheme($theme) {
|
||||
$this->theme = $theme;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTheme() {
|
||||
return $this->theme;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||
parent::postSave($storage, $update);
|
||||
if ($this->isReusable() || (isset($this->original) && $this->original->isReusable())) {
|
||||
static::invalidateBlockPluginCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function preDelete(EntityStorageInterface $storage, array $entities) {
|
||||
parent::preDelete($storage, $entities);
|
||||
|
||||
/** @var \Drupal\block_content\BlockContentInterface $block */
|
||||
foreach ($entities as $block) {
|
||||
foreach ($block->getInstances() as $instance) {
|
||||
$instance->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function postDelete(EntityStorageInterface $storage, array $entities) {
|
||||
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.
|
||||
static::invalidateBlockPluginCache();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getInstances() {
|
||||
return \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(['plugin' => 'block_content:' . $this->uuid()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) {
|
||||
parent::preSaveRevision($storage, $record);
|
||||
|
||||
if (!$this->isNewRevision() && isset($this->original) && empty($record->revision_log_message)) {
|
||||
// If we are updating an existing block_content without adding a new
|
||||
// revision and the user did not supply a revision log, keep the existing
|
||||
// one.
|
||||
$record->revision_log = $this->original->getRevisionLogMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
|
||||
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
|
||||
$fields = parent::baseFieldDefinitions($entity_type);
|
||||
|
||||
$fields['id']->setLabel(t('Content block ID'))
|
||||
->setDescription(t('The content block ID.'));
|
||||
|
||||
$fields['uuid']->setDescription(t('The content block UUID.'));
|
||||
|
||||
$fields['revision_id']->setDescription(t('The revision ID.'));
|
||||
|
||||
$fields['langcode']->setDescription(t('The content block language code.'));
|
||||
|
||||
$fields['type']->setLabel(t('Block type'))
|
||||
->setDescription(t('The block type.'));
|
||||
|
||||
$fields['revision_log']->setDescription(t('The log entry explaining the changes in this revision.'));
|
||||
|
||||
$fields['info'] = BaseFieldDefinition::create('string')
|
||||
->setLabel(t('Block description'))
|
||||
->setDescription(t('A brief description of your block.'))
|
||||
->setRevisionable(TRUE)
|
||||
->setTranslatable(TRUE)
|
||||
->setRequired(TRUE)
|
||||
->setDisplayOptions('form', [
|
||||
'type' => 'string_textfield',
|
||||
'weight' => -5,
|
||||
])
|
||||
->setDisplayConfigurable('form', TRUE);
|
||||
|
||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||
->setLabel(t('Changed'))
|
||||
->setDescription(t('The time that the content block was last edited.'))
|
||||
->setTranslatable(TRUE)
|
||||
->setRevisionable(TRUE);
|
||||
|
||||
$fields['reusable'] = BaseFieldDefinition::create('boolean')
|
||||
->setLabel(t('Reusable'))
|
||||
->setDescription(t('A boolean indicating whether this block is reusable.'))
|
||||
->setTranslatable(FALSE)
|
||||
->setRevisionable(FALSE)
|
||||
->setDefaultValue(TRUE);
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setInfo($info) {
|
||||
$this->set('info', $info);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isReusable() {
|
||||
return (bool) $this->get('reusable')->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setReusable() {
|
||||
return $this->set('reusable', TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setNonReusable() {
|
||||
return $this->set('reusable', FALSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates the block plugin cache after changes and deletions.
|
||||
*/
|
||||
protected static function invalidateBlockPluginCache() {
|
||||
// Invalidate the block cache to update content block-based derivatives.
|
||||
\Drupal::service('plugin.manager.block')->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
}
|
||||
100
src/Entity/BlockContentType.php
Normal file
100
src/Entity/BlockContentType.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Entity;
|
||||
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\block_content\BlockContentTypeInterface;
|
||||
|
||||
/**
|
||||
* Defines the block type entity.
|
||||
*
|
||||
* @ConfigEntityType(
|
||||
* id = "block_content_type",
|
||||
* label = @Translation("Block type"),
|
||||
* label_collection = @Translation("Block types"),
|
||||
* label_singular = @Translation("block type"),
|
||||
* label_plural = @Translation("block types"),
|
||||
* label_count = @PluralTranslation(
|
||||
* singular = "@count block type",
|
||||
* plural = "@count block types",
|
||||
* ),
|
||||
* handlers = {
|
||||
* "access" = "Drupal\block_content\BlockTypeAccessControlHandler",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\block_content\BlockContentTypeForm",
|
||||
* "add" = "Drupal\block_content\BlockContentTypeForm",
|
||||
* "edit" = "Drupal\block_content\BlockContentTypeForm",
|
||||
* "delete" = "Drupal\block_content\Form\BlockContentTypeDeleteForm"
|
||||
* },
|
||||
* "route_provider" = {
|
||||
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
|
||||
* "permissions" = "Drupal\user\Entity\EntityPermissionsRouteProvider",
|
||||
* },
|
||||
* "list_builder" = "Drupal\block_content\BlockContentTypeListBuilder"
|
||||
* },
|
||||
* admin_permission = "administer block types",
|
||||
* config_prefix = "type",
|
||||
* bundle_of = "block_content",
|
||||
* entity_keys = {
|
||||
* "id" = "id",
|
||||
* "label" = "label"
|
||||
* },
|
||||
* links = {
|
||||
* "delete-form" = "/admin/structure/block-content/manage/{block_content_type}/delete",
|
||||
* "edit-form" = "/admin/structure/block-content/manage/{block_content_type}",
|
||||
* "entity-permissions-form" = "/admin/structure/block-content/manage/{block_content_type}/permissions",
|
||||
* "collection" = "/admin/structure/block-content",
|
||||
* },
|
||||
* config_export = {
|
||||
* "id",
|
||||
* "label",
|
||||
* "revision",
|
||||
* "description",
|
||||
* }
|
||||
* )
|
||||
*/
|
||||
class BlockContentType extends ConfigEntityBundleBase implements BlockContentTypeInterface {
|
||||
|
||||
/**
|
||||
* The block type ID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
/**
|
||||
* The block type label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* The default revision setting for content blocks of this type.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $revision = FALSE;
|
||||
|
||||
/**
|
||||
* The description of the block type.
|
||||
*
|
||||
* @var string|null
|
||||
*/
|
||||
protected $description = NULL;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
return $this->description ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function shouldCreateNewRevision() {
|
||||
return $this->revision;
|
||||
}
|
||||
|
||||
}
|
||||
70
src/Event/BlockContentGetDependencyEvent.php
Normal file
70
src/Event/BlockContentGetDependencyEvent.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Event;
|
||||
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\Core\Access\AccessibleInterface;
|
||||
use Drupal\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Block content event to allow setting an access dependency.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentGetDependencyEvent extends Event {
|
||||
|
||||
/**
|
||||
* The block content entity.
|
||||
*
|
||||
* @var \Drupal\block_content\BlockContentInterface
|
||||
*/
|
||||
protected $blockContent;
|
||||
|
||||
/**
|
||||
* The dependency.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessibleInterface
|
||||
*/
|
||||
protected $accessDependency;
|
||||
|
||||
/**
|
||||
* BlockContentGetDependencyEvent constructor.
|
||||
*
|
||||
* @param \Drupal\block_content\BlockContentInterface $blockContent
|
||||
* The block content entity.
|
||||
*/
|
||||
public function __construct(BlockContentInterface $blockContent) {
|
||||
$this->blockContent = $blockContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the block content entity.
|
||||
*
|
||||
* @return \Drupal\block_content\BlockContentInterface
|
||||
* The block content entity.
|
||||
*/
|
||||
public function getBlockContentEntity() {
|
||||
return $this->blockContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the access dependency.
|
||||
*
|
||||
* @return \Drupal\Core\Access\AccessibleInterface
|
||||
* The access dependency.
|
||||
*/
|
||||
public function getAccessDependency() {
|
||||
return $this->accessDependency;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the access dependency.
|
||||
*
|
||||
* @param \Drupal\Core\Access\AccessibleInterface $access_dependency
|
||||
* The access dependency.
|
||||
*/
|
||||
public function setAccessDependency(AccessibleInterface $access_dependency) {
|
||||
$this->accessDependency = $access_dependency;
|
||||
}
|
||||
|
||||
}
|
||||
25
src/Form/BlockContentDeleteForm.php
Normal file
25
src/Form/BlockContentDeleteForm.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Form;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityDeleteForm;
|
||||
|
||||
/**
|
||||
* Provides a confirmation form for deleting a content block entity.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentDeleteForm extends ContentEntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDescription() {
|
||||
$instances = $this->entity->getInstances();
|
||||
if (!empty($instances)) {
|
||||
return $this->formatPlural(count($instances), 'This will also remove 1 placed block instance. This action cannot be undone.', 'This will also remove @count placed block instances. This action cannot be undone.');
|
||||
}
|
||||
return parent::getDescription();
|
||||
}
|
||||
|
||||
}
|
||||
80
src/Form/BlockContentListFiltersForm.php
Normal file
80
src/Form/BlockContentListFiltersForm.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Form;
|
||||
|
||||
use Drupal\Core\Form\FormBase;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Form for filters on the blocks entity list page.
|
||||
*/
|
||||
class BlockContentListFiltersForm extends FormBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
return 'block_content_list_filters_form';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$request = \Drupal::request();
|
||||
$form['filter'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => [
|
||||
'class' => ['form--inline', 'clearfix'],
|
||||
],
|
||||
];
|
||||
|
||||
$form['filter']['foo'] = [
|
||||
'#type' => 'textfield',
|
||||
'#title' => 'Search for block',
|
||||
'#default_value' => $request->get('foo') ?? "",
|
||||
];
|
||||
|
||||
$form['actions']['wrapper'] = [
|
||||
'#type' => 'container',
|
||||
'#attributes' => ['class' => ['form-item']],
|
||||
];
|
||||
|
||||
$form['actions']['wrapper']['submit'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => 'Filter',
|
||||
];
|
||||
|
||||
if ($request->getQueryString()) {
|
||||
$form['actions']['wrapper']['reset'] = [
|
||||
'#type' => 'submit',
|
||||
'#value' => 'Reset',
|
||||
'#submit' => ['::resetForm'],
|
||||
];
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, FormStateInterface $form_state) {
|
||||
$query = [];
|
||||
|
||||
$foo = $form_state->getValue('foo') ?? 0;
|
||||
if ($foo) {
|
||||
$query['foo'] = $foo;
|
||||
}
|
||||
|
||||
$form_state->setRedirect('entity.block_content.collection', $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function resetForm(array $form, FormStateInterface &$form_state) {
|
||||
$form_state->setRedirect('entity.block_content.collection');
|
||||
}
|
||||
|
||||
}
|
||||
34
src/Form/BlockContentTypeDeleteForm.php
Normal file
34
src/Form/BlockContentTypeDeleteForm.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Form;
|
||||
|
||||
use Drupal\Core\Entity\EntityDeleteForm;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
|
||||
/**
|
||||
* Provides a confirmation form for deleting a block type entity.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class BlockContentTypeDeleteForm extends EntityDeleteForm {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildForm(array $form, FormStateInterface $form_state) {
|
||||
$block_count = $this->entityTypeManager->getStorage('block_content')->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->condition('type', $this->entity->id())
|
||||
->count()
|
||||
->execute();
|
||||
if ($block_count) {
|
||||
$caption = '<p>' . $this->formatPlural($block_count, '%label is used by 1 content block on your site. You can not remove this block type until you have removed all of the %label blocks.', '%label is used by @count content blocks on your site. You may not remove %label until you have removed all of the %label content blocks.', ['%label' => $this->entity->label()]) . '</p>';
|
||||
$form['description'] = ['#markup' => $caption];
|
||||
return $form;
|
||||
}
|
||||
else {
|
||||
return parent::buildForm($form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
218
src/Plugin/Block/BlockContentBlock.php
Normal file
218
src/Plugin/Block/BlockContentBlock.php
Normal file
@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Block;
|
||||
|
||||
use Drupal\block_content\BlockContentUuidLookup;
|
||||
use Drupal\block_content\Plugin\Derivative\BlockContent;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Block\Attribute\Block;
|
||||
use Drupal\Core\Block\BlockBase;
|
||||
use Drupal\Core\Block\BlockManagerInterface;
|
||||
use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Form\FormStateInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Routing\UrlGeneratorInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines a generic block type.
|
||||
*/
|
||||
#[Block(
|
||||
id: "block_content",
|
||||
admin_label: new TranslatableMarkup("Content block"),
|
||||
category: new TranslatableMarkup("Content block"),
|
||||
deriver: BlockContent::class
|
||||
)]
|
||||
class BlockContentBlock extends BlockBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The Plugin Block Manager.
|
||||
*
|
||||
* @var \Drupal\Core\Block\BlockManagerInterface
|
||||
*/
|
||||
protected $blockManager;
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The Drupal account to use for checking for access to block.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $account;
|
||||
|
||||
/**
|
||||
* The block content entity.
|
||||
*
|
||||
* @var \Drupal\block_content\BlockContentInterface
|
||||
*/
|
||||
protected $blockContent;
|
||||
|
||||
/**
|
||||
* The URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGeneratorInterface
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* The block content UUID lookup service.
|
||||
*
|
||||
* @var \Drupal\block_content\BlockContentUuidLookup
|
||||
*/
|
||||
protected $uuidLookup;
|
||||
|
||||
/**
|
||||
* The entity display repository.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface
|
||||
*/
|
||||
protected $entityDisplayRepository;
|
||||
|
||||
/**
|
||||
* Constructs a new BlockContentBlock.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
|
||||
* The Plugin Block Manager.
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Session\AccountInterface $account
|
||||
* The account for which view access should be checked.
|
||||
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
|
||||
* The URL generator.
|
||||
* @param \Drupal\block_content\BlockContentUuidLookup $uuid_lookup
|
||||
* The block content UUID lookup service.
|
||||
* @param \Drupal\Core\Entity\EntityDisplayRepositoryInterface $entity_display_repository
|
||||
* The entity display repository.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockManagerInterface $block_manager, EntityTypeManagerInterface $entity_type_manager, AccountInterface $account, UrlGeneratorInterface $url_generator, BlockContentUuidLookup $uuid_lookup, EntityDisplayRepositoryInterface $entity_display_repository) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->blockManager = $block_manager;
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->account = $account;
|
||||
$this->urlGenerator = $url_generator;
|
||||
$this->uuidLookup = $uuid_lookup;
|
||||
$this->entityDisplayRepository = $entity_display_repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('plugin.manager.block'),
|
||||
$container->get('entity_type.manager'),
|
||||
$container->get('current_user'),
|
||||
$container->get('url_generator'),
|
||||
$container->get('block_content.uuid_lookup'),
|
||||
$container->get('entity_display.repository')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defaultConfiguration() {
|
||||
return [
|
||||
'status' => TRUE,
|
||||
'info' => '',
|
||||
'view_mode' => 'full',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides \Drupal\Core\Block\BlockBase::blockForm().
|
||||
*
|
||||
* Adds body and description fields to the block configuration form.
|
||||
*/
|
||||
public function blockForm($form, FormStateInterface $form_state) {
|
||||
$block = $this->getEntity();
|
||||
if (!$block) {
|
||||
return $form;
|
||||
}
|
||||
$options = $this->entityDisplayRepository->getViewModeOptionsByBundle('block_content', $block->bundle());
|
||||
|
||||
$form['view_mode'] = [
|
||||
'#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.');
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function blockSubmit($form, FormStateInterface $form_state) {
|
||||
// Invalidate the block cache to update content block-based derivatives.
|
||||
$this->configuration['view_mode'] = $form_state->getValue('view_mode');
|
||||
$this->blockManager->clearCachedDefinitions();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function blockAccess(AccountInterface $account) {
|
||||
if ($this->getEntity()) {
|
||||
return $this->getEntity()->access('view', $account, TRUE);
|
||||
}
|
||||
return AccessResult::forbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build() {
|
||||
if ($block = $this->getEntity()) {
|
||||
return $this->entityTypeManager->getViewBuilder($block->getEntityTypeId())->view($block, $this->configuration['view_mode']);
|
||||
}
|
||||
else {
|
||||
return [
|
||||
'#markup' => $this->t('Block with uuid %uuid does not exist. <a href=":url">Add content block</a>.', [
|
||||
'%uuid' => $this->getDerivativeId(),
|
||||
':url' => $this->urlGenerator->generate('block_content.add_page'),
|
||||
]),
|
||||
'#access' => $this->account->hasPermission('administer blocks'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the block content entity of the block.
|
||||
*
|
||||
* @return \Drupal\block_content\BlockContentInterface|null
|
||||
* The block content entity.
|
||||
*/
|
||||
protected function getEntity() {
|
||||
if (!isset($this->blockContent)) {
|
||||
$uuid = $this->getDerivativeId();
|
||||
if ($id = $this->uuidLookup->get($uuid)) {
|
||||
$this->blockContent = $this->entityTypeManager->getStorage('block_content')->load($id);
|
||||
}
|
||||
}
|
||||
return $this->blockContent;
|
||||
}
|
||||
|
||||
}
|
||||
60
src/Plugin/Derivative/BlockContent.php
Normal file
60
src/Plugin/Derivative/BlockContent.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Derivative;
|
||||
|
||||
use Drupal\Component\Plugin\Derivative\DeriverBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Retrieves block plugin definitions for all content blocks.
|
||||
*/
|
||||
class BlockContent extends DeriverBase implements ContainerDeriverInterface {
|
||||
|
||||
/**
|
||||
* The content block storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $blockContentStorage;
|
||||
|
||||
/**
|
||||
* Constructs a BlockContent object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityStorageInterface $block_content_storage
|
||||
* The content block storage.
|
||||
*/
|
||||
public function __construct(EntityStorageInterface $block_content_storage) {
|
||||
$this->blockContentStorage = $block_content_storage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, $base_plugin_id) {
|
||||
$entity_type_manager = $container->get('entity_type.manager');
|
||||
return new static(
|
||||
$entity_type_manager->getStorage('block_content')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
$block_contents = $this->blockContentStorage->loadByProperties(['reusable' => TRUE]);
|
||||
// Reset the discovered definitions.
|
||||
$this->derivatives = [];
|
||||
/** @var \Drupal\block_content\Entity\BlockContent $block_content */
|
||||
foreach ($block_contents as $block_content) {
|
||||
$this->derivatives[$block_content->uuid()] = $base_plugin_definition;
|
||||
$this->derivatives[$block_content->uuid()]['admin_label'] = $block_content->label() ?? ($block_content->type->entity->label() . ': ' . $block_content->id());
|
||||
$this->derivatives[$block_content->uuid()]['config_dependencies']['content'] = [
|
||||
$block_content->getConfigDependencyName(),
|
||||
];
|
||||
}
|
||||
return parent::getDerivativeDefinitions($base_plugin_definition);
|
||||
}
|
||||
|
||||
}
|
||||
65
src/Plugin/Menu/LocalAction/BlockContentAddLocalAction.php
Normal file
65
src/Plugin/Menu/LocalAction/BlockContentAddLocalAction.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Menu\LocalAction;
|
||||
|
||||
use Drupal\Core\Menu\LocalActionDefault;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Modifies the 'Add content block' local action.
|
||||
*/
|
||||
class BlockContentAddLocalAction extends LocalActionDefault {
|
||||
|
||||
/**
|
||||
* Constructs a BlockContentAddLocalAction object.
|
||||
*/
|
||||
public function __construct(
|
||||
array $configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
RouteProviderInterface $routeProvider,
|
||||
protected RequestStack $requestStack,
|
||||
) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $routeProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('router.route_provider'),
|
||||
$container->get('request_stack'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getOptions(RouteMatchInterface $route_match) {
|
||||
$options = parent::getOptions($route_match);
|
||||
// If the route specifies a theme, append it to the query string.
|
||||
if ($theme = $route_match->getParameter('theme')) {
|
||||
$options['query']['theme'] = $theme;
|
||||
}
|
||||
|
||||
// If the current request has a region, append it to the query string.
|
||||
if ($region = $this->requestStack->getCurrentRequest()->query->getString('region')) {
|
||||
$options['query']['region'] = $region;
|
||||
}
|
||||
|
||||
// Adds a destination on content block listing.
|
||||
if ($route_match->getRouteName() == 'entity.block_content.collection') {
|
||||
$options['query']['destination'] = Url::fromRoute('<current>')->toString();
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Validation\Attribute\Constraint;
|
||||
|
||||
/**
|
||||
* Validation constraint for the block content entity changed timestamp.
|
||||
*/
|
||||
#[Constraint(
|
||||
id: 'BlockContentEntityChanged',
|
||||
label: new TranslatableMarkup('Block content entity changed', [], ['context' => 'Validation']),
|
||||
type: ['entity']
|
||||
)]
|
||||
class BlockContentEntityChangedConstraint extends EntityChangedConstraint {
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Validation\Constraint;
|
||||
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraintValidator;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Validates the BlockContentEntityChanged constraint.
|
||||
*/
|
||||
class BlockContentEntityChangedConstraintValidator extends EntityChangedConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($entity, Constraint $constraint) {
|
||||
// This prevents saving an update to the block via a host entity's form if
|
||||
// the host entity has had other changes made via the API instead of the
|
||||
// entity form, such as a revision revert. This is safe, for example, in the
|
||||
// Layout Builder the inline blocks are not saved until the whole layout is
|
||||
// saved, in which case Layout Builder forces a new revision for the block.
|
||||
// @see \Drupal\layout_builder\InlineBlockEntityOperations::handlePreSave.
|
||||
if ($entity instanceof BlockContentInterface && !$entity->isReusable()) {
|
||||
return;
|
||||
}
|
||||
parent::validate($entity, $constraint);
|
||||
}
|
||||
|
||||
}
|
||||
53
src/Plugin/migrate/source/d6/Box.php
Normal file
53
src/Plugin/migrate/source/d6/Box.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 6 block source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_box",
|
||||
* source_module = "block"
|
||||
* )
|
||||
*/
|
||||
class Box extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this->select('boxes', 'b')
|
||||
->fields('b', ['bid', 'body', 'info', 'format']);
|
||||
$query->orderBy('b.bid');
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'bid' => $this->t('The numeric identifier of the block/box'),
|
||||
'body' => $this->t('The block/box content'),
|
||||
'info' => $this->t('Admin title of the block/box.'),
|
||||
'format' => $this->t('Input format of the content block/box content.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['bid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
28
src/Plugin/migrate/source/d6/BoxTranslation.php
Normal file
28
src/Plugin/migrate/source/d6/BoxTranslation.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\migrate\source\d6;
|
||||
|
||||
use Drupal\block_content\Plugin\migrate\source\d7\BlockCustomTranslation as D7BlockCustomTranslation;
|
||||
|
||||
/**
|
||||
* Drupal 6 i18n content block translations source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d6_box_translation",
|
||||
* source_module = "i18nblocks"
|
||||
* )
|
||||
*/
|
||||
class BoxTranslation extends D7BlockCustomTranslation {
|
||||
|
||||
/**
|
||||
* Drupal 6 table names.
|
||||
*/
|
||||
const CUSTOM_BLOCK_TABLE = 'boxes';
|
||||
const I18N_STRING_TABLE = 'i18n_strings';
|
||||
|
||||
}
|
||||
49
src/Plugin/migrate/source/d7/BlockCustom.php
Normal file
49
src/Plugin/migrate/source/d7/BlockCustom.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
|
||||
/**
|
||||
* Drupal 7 content block source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_block_custom",
|
||||
* source_module = "block"
|
||||
* )
|
||||
*/
|
||||
class BlockCustom extends DrupalSqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
return $this->select('block_custom', 'b')->fields('b');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'bid' => $this->t('The numeric identifier of the block/box'),
|
||||
'body' => $this->t('The block/box content'),
|
||||
'info' => $this->t('Admin title of the block/box.'),
|
||||
'format' => $this->t('Input format of the content block/box content.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['bid']['type'] = 'integer';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
105
src/Plugin/migrate/source/d7/BlockCustomTranslation.php
Normal file
105
src/Plugin/migrate/source/d7/BlockCustomTranslation.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\migrate\source\d7;
|
||||
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
|
||||
use Drupal\content_translation\Plugin\migrate\source\I18nQueryTrait;
|
||||
|
||||
/**
|
||||
* Drupal 7 i18n content block translations source from database.
|
||||
*
|
||||
* For available configuration keys, refer to the parent classes.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "d7_block_custom_translation",
|
||||
* source_module = "i18n_block"
|
||||
* )
|
||||
*/
|
||||
class BlockCustomTranslation extends DrupalSqlBase {
|
||||
|
||||
use I18nQueryTrait;
|
||||
|
||||
/**
|
||||
* Drupal 7 table names.
|
||||
*/
|
||||
const CUSTOM_BLOCK_TABLE = 'block_custom';
|
||||
const I18N_STRING_TABLE = 'i18n_string';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
// Build a query based on blockCustomTable table where each row has the
|
||||
// translation for only one property, either title or description. The
|
||||
// method prepareRow() is then used to obtain the translation for the
|
||||
// other property.
|
||||
$query = $this->select(static::CUSTOM_BLOCK_TABLE, 'b')
|
||||
->fields('b', ['bid', 'format', 'body'])
|
||||
->fields('i18n', ['property'])
|
||||
->fields('lt', ['lid', 'translation', 'language'])
|
||||
->orderBy('b.bid');
|
||||
|
||||
// Use 'title' for the info field to match the property name in
|
||||
// i18nStringTable.
|
||||
$query->addField('b', 'info', 'title');
|
||||
|
||||
// Add in the property, which is either title or body. Cast the bid to text
|
||||
// so PostgreSQL can make the join.
|
||||
$query->leftJoin(static::I18N_STRING_TABLE, 'i18n', '[i18n].[objectid] = CAST([b].[bid] AS CHAR(255))');
|
||||
$query->condition('i18n.type', 'block');
|
||||
|
||||
// Add in the translation for the property.
|
||||
$query->innerJoin('locales_target', 'lt', '[lt].[lid] = [i18n].[lid]');
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function prepareRow(Row $row) {
|
||||
if (!parent::prepareRow($row)) {
|
||||
return FALSE;
|
||||
}
|
||||
// Set the i18n string table for use in I18nQueryTrait.
|
||||
$this->i18nStringTable = static::I18N_STRING_TABLE;
|
||||
// Save the translation for this property.
|
||||
$property_in_row = $row->getSourceProperty('property');
|
||||
// Get the translation for the property not already in the row and save it
|
||||
// in the row.
|
||||
$property_not_in_row = ($property_in_row === 'title') ? 'body' : 'title';
|
||||
return $this->getPropertyNotInRowTranslation($row, $property_not_in_row, 'bid', $this->idMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [
|
||||
'bid' => $this->t('The block numeric identifier.'),
|
||||
'format' => $this->t('Input format of the content block/box content.'),
|
||||
'lid' => $this->t('i18n_string table id'),
|
||||
'language' => $this->t('Language for this field.'),
|
||||
'property' => $this->t('Block property'),
|
||||
'translation' => $this->t('The translation of the value of "property".'),
|
||||
'title' => $this->t('Block title.'),
|
||||
'title_translated' => $this->t('Block title translation.'),
|
||||
'body' => $this->t('Block body.'),
|
||||
'body_translated' => $this->t('Block body translation.'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['bid']['type'] = 'integer';
|
||||
$ids['bid']['alias'] = 'b';
|
||||
$ids['language']['type'] = 'string';
|
||||
return $ids;
|
||||
}
|
||||
|
||||
}
|
||||
89
src/Plugin/views/area/ListingEmpty.php
Normal file
89
src/Plugin/views/area/ListingEmpty.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\views\area;
|
||||
|
||||
use Drupal\Core\Access\AccessManagerInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\views\Attribute\ViewsArea;
|
||||
use Drupal\views\Plugin\views\area\AreaPluginBase;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Defines an area plugin to display a block add link.
|
||||
*
|
||||
* @ingroup views_area_handlers
|
||||
*/
|
||||
#[ViewsArea("block_content_listing_empty")]
|
||||
class ListingEmpty extends AreaPluginBase {
|
||||
|
||||
/**
|
||||
* The access manager.
|
||||
*
|
||||
* @var \Drupal\Core\Access\AccessManagerInterface
|
||||
*/
|
||||
protected $accessManager;
|
||||
|
||||
/**
|
||||
* The current user.
|
||||
*
|
||||
* @var \Drupal\Core\Session\AccountInterface
|
||||
*/
|
||||
protected $currentUser;
|
||||
|
||||
/**
|
||||
* Constructs a new ListingEmpty.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID for the plugin instance.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param \Drupal\Core\Access\AccessManagerInterface $access_manager
|
||||
* The access manager.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current user.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, AccessManagerInterface $access_manager, AccountInterface $current_user) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
|
||||
$this->accessManager = $access_manager;
|
||||
$this->currentUser = $current_user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('access_manager'),
|
||||
$container->get('current_user')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function render($empty = FALSE) {
|
||||
if (!$empty || !empty($this->options['empty'])) {
|
||||
/** @var \Drupal\Core\Access\AccessResultInterface|\Drupal\Core\Cache\CacheableDependencyInterface $access_result */
|
||||
$access_result = $this->accessManager->checkNamedRoute('block_content.add_page', [], $this->currentUser, TRUE);
|
||||
$element = [
|
||||
'#markup' => $this->t('Add a <a href=":url">content block</a>.', [':url' => Url::fromRoute('block_content.add_page')->toString()]),
|
||||
'#access' => $access_result->isAllowed(),
|
||||
'#cache' => [
|
||||
'contexts' => $access_result->getCacheContexts(),
|
||||
'tags' => $access_result->getCacheTags(),
|
||||
'max-age' => $access_result->getCacheMaxAge(),
|
||||
],
|
||||
];
|
||||
return $element;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
36
src/Plugin/views/wizard/BlockContent.php
Normal file
36
src/Plugin/views/wizard/BlockContent.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\views\wizard;
|
||||
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\views\Attribute\ViewsWizard;
|
||||
use Drupal\views\Plugin\views\wizard\WizardPluginBase;
|
||||
|
||||
/**
|
||||
* Used for creating 'block_content' views with the wizard.
|
||||
*/
|
||||
#[ViewsWizard(
|
||||
id: 'block_content',
|
||||
title: new TranslatableMarkup('Content Block'),
|
||||
base_table: 'block_content_field_data'
|
||||
)]
|
||||
class BlockContent extends WizardPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFilters() {
|
||||
$filters = parent::getFilters();
|
||||
$filters['reusable'] = [
|
||||
'id' => 'reusable',
|
||||
'plugin_id' => 'boolean',
|
||||
'table' => $this->base_table,
|
||||
'field' => 'reusable',
|
||||
'value' => '1',
|
||||
'entity_type' => $this->entityTypeId,
|
||||
'entity_field' => 'reusable',
|
||||
];
|
||||
return $filters;
|
||||
}
|
||||
|
||||
}
|
||||
237
src/Routing/RouteSubscriber.php
Normal file
237
src/Routing/RouteSubscriber.php
Normal file
@ -0,0 +1,237 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Routing;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Routing\RouteBuildEvent;
|
||||
use Drupal\Core\Routing\RouteSubscriberBase;
|
||||
use Drupal\Core\Routing\RoutingEvents;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
|
||||
/**
|
||||
* Subscriber for Block content BC routes.
|
||||
*/
|
||||
class RouteSubscriber extends RouteSubscriberBase {
|
||||
|
||||
/**
|
||||
* The entity type manager service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* The route collection for adding routes.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\RouteCollection
|
||||
*/
|
||||
protected $collection;
|
||||
|
||||
/**
|
||||
* The current base path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* The BC base path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $basePathBc;
|
||||
|
||||
/**
|
||||
* The redirect controller.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* Constructs a RouteSubscriber object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||
* The entity type manager service.
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(EntityTypeManagerInterface $entity_type_manager, ModuleHandlerInterface $module_handler) {
|
||||
$this->entityTypeManager = $entity_type_manager;
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function alterRoutes(RouteCollection $collection) {
|
||||
$this->collection = $collection;
|
||||
|
||||
// @see block_content.routing.yml
|
||||
if ($this->setUpBaseRoute('entity.block_content_type.collection')) {
|
||||
$this->addRedirectRoute('block_content.type_add');
|
||||
}
|
||||
|
||||
$entity_type = $this->entityTypeManager->getDefinition('block_content');
|
||||
if ($this->setUpBaseRoute($entity_type->get('field_ui_base_route'))) {
|
||||
foreach ($this->childRoutes($entity_type) as $route_name) {
|
||||
$this->addRedirectRoute($route_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters from a base route and saves them in class variables.
|
||||
*
|
||||
* @param string $base_route_name
|
||||
* The name of a base route that already has a BC variant.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if all parameters are set, FALSE if not.
|
||||
*/
|
||||
protected function setUpBaseRoute(string $base_route_name): bool {
|
||||
$base_route = $this->collection->get($base_route_name);
|
||||
$base_route_bc = $this->collection->get("$base_route_name.bc");
|
||||
if (empty($base_route) || empty($base_route_bc)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$this->basePath = $base_route->getPath();
|
||||
$this->basePathBc = $base_route_bc->getPath();
|
||||
$this->controller = $base_route_bc->getDefault('_controller');
|
||||
if (empty($this->basePath) || empty($this->basePathBc) || empty($this->controller) || $this->basePathBc === $this->basePath) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a redirect route.
|
||||
*
|
||||
* @param string $route_name
|
||||
* The name of a route whose path has changed.
|
||||
*/
|
||||
protected function addRedirectRoute(string $route_name): void {
|
||||
// Exit early if the BC route is already there.
|
||||
if (!empty($this->collection->get("$route_name.bc"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$route = $this->collection->get($route_name);
|
||||
if (empty($route)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$new_path = $route->getPath();
|
||||
if (!str_starts_with($new_path, $this->basePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bc_route = clone $route;
|
||||
// Set the path to what it was in earlier versions of Drupal.
|
||||
$bc_route->setPath($this->basePathBc . substr($new_path, strlen($this->basePath)));
|
||||
if ($bc_route->getPath() === $route->getPath()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace the handler with the stored redirect controller.
|
||||
$defaults = array_diff_key($route->getDefaults(), array_flip([
|
||||
'_entity_form',
|
||||
'_entity_list',
|
||||
'_entity_view',
|
||||
'_form',
|
||||
]));
|
||||
$defaults['_controller'] = $this->controller;
|
||||
$bc_route->setDefaults($defaults);
|
||||
|
||||
$this->collection->add("$route_name.bc", $bc_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a list of routes that need BC redirects.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* The entity type.
|
||||
*
|
||||
* @return string[]
|
||||
* A list of route names.
|
||||
*/
|
||||
protected function childRoutes(EntityTypeInterface $entity_type): array {
|
||||
$route_names = [];
|
||||
|
||||
if ($field_ui_base_route = $entity_type->get('field_ui_base_route')) {
|
||||
$updated_routes = new RouteCollection();
|
||||
$updated_routes->add($field_ui_base_route, $this->collection->get($field_ui_base_route));
|
||||
$event = new RouteBuildEvent($updated_routes);
|
||||
|
||||
// Apply route subscribers that add routes based on field_ui_base_route,
|
||||
// in the order of their weights.
|
||||
$subscribers = [
|
||||
'field_ui' => 'field_ui.subscriber',
|
||||
'content_translation' => 'content_translation.subscriber',
|
||||
];
|
||||
foreach ($subscribers as $module_name => $service_name) {
|
||||
if ($this->moduleHandler->moduleExists($module_name)) {
|
||||
\Drupal::service($service_name)->onAlterRoutes($event);
|
||||
}
|
||||
}
|
||||
|
||||
$updated_routes->remove($field_ui_base_route);
|
||||
$route_names = array_merge($route_names, array_keys($updated_routes->all()));
|
||||
$route_names = array_merge($route_names, [
|
||||
// @see \Drupal\config_translation\Routing\RouteSubscriber::alterRoutes()
|
||||
"config_translation.item.add.{$field_ui_base_route}",
|
||||
"config_translation.item.edit.{$field_ui_base_route}",
|
||||
"config_translation.item.delete.{$field_ui_base_route}",
|
||||
]);
|
||||
}
|
||||
|
||||
if ($entity_type_id = $entity_type->getBundleEntityType()) {
|
||||
$route_names = array_merge($route_names, [
|
||||
// @see \Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider::getRoutes()
|
||||
"entity.{$entity_type_id}.delete_form",
|
||||
// @see \Drupal\config_translation\Routing\RouteSubscriber::alterRoutes()
|
||||
"entity.{$entity_type_id}.config_translation_overview",
|
||||
// @see \Drupal\user\Entity\EntityPermissionsRouteProvider::getRoutes()
|
||||
"entity.{$entity_type_id}.entity_permissions_form",
|
||||
]);
|
||||
}
|
||||
|
||||
if ($entity_id = $entity_type->id()) {
|
||||
$route_names = array_merge($route_names, [
|
||||
// @see \Drupal\config_translation\Routing\RouteSubscriber::alterRoutes()
|
||||
"entity.field_config.config_translation_overview.{$entity_id}",
|
||||
"config_translation.item.add.entity.field_config.{$entity_id}_field_edit_form",
|
||||
"config_translation.item.edit.entity.field_config.{$entity_id}_field_edit_form",
|
||||
"config_translation.item.delete.entity.field_config.{$entity_id}_field_edit_form",
|
||||
// @see \Drupal\layout_builder\Plugin\SectionStorage\DefaultsSectionStorage::buildRoutes()
|
||||
"layout_builder.defaults.{$entity_id}.disable",
|
||||
"layout_builder.defaults.{$entity_id}.discard_changes",
|
||||
"layout_builder.defaults.{$entity_id}.view",
|
||||
]);
|
||||
}
|
||||
|
||||
return $route_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents(): array {
|
||||
$events = parent::getSubscribedEvents();
|
||||
// Go after ContentTranslationRouteSubscriber.
|
||||
$events[RoutingEvents::ALTER] = ['onAlterRoutes', -300];
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
24
templates/block-content-add-list.html.twig
Normal file
24
templates/block-content-add-list.html.twig
Normal file
@ -0,0 +1,24 @@
|
||||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to present a list of block types.
|
||||
*
|
||||
* Available variables:
|
||||
* - types: A collection of all the available content block types.
|
||||
* Each block type contains the following:
|
||||
* - link: A link to add a block of this type.
|
||||
* - description: A description of this block type.
|
||||
*
|
||||
* @see template_preprocess_block_content_add_list()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% apply spaceless %}
|
||||
<dl>
|
||||
{% for type in types %}
|
||||
<dt>{{ type.link }}</dt>
|
||||
<dd>{{ type.description }}</dd>
|
||||
{% endfor %}
|
||||
</dl>
|
||||
{% endapply %}
|
||||
540
tests/fixtures/update/views.view.block_content_2862564.yml
vendored
Normal file
540
tests/fixtures/update/views.view.block_content_2862564.yml
vendored
Normal file
@ -0,0 +1,540 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
- user
|
||||
id: block_content
|
||||
label: 'Content blocks'
|
||||
module: views
|
||||
description: 'Create and edit content blocks.'
|
||||
tag: default
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
id: default
|
||||
display_title: Default
|
||||
display_plugin: default
|
||||
position: 0
|
||||
display_options:
|
||||
title: 'Content blocks'
|
||||
fields:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: null
|
||||
entity_field: info
|
||||
plugin_id: field
|
||||
label: 'Block description'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
type:
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
plugin_id: field
|
||||
label: 'Block type'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
changed:
|
||||
id: changed
|
||||
table: block_content_field_data
|
||||
field: changed
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: changed
|
||||
plugin_id: field
|
||||
label: Updated
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
type: timestamp
|
||||
settings:
|
||||
date_format: short
|
||||
custom_date_format: ''
|
||||
timezone: ''
|
||||
operations:
|
||||
id: operations
|
||||
table: block_content
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
plugin_id: entity_operations
|
||||
label: Operations
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
destination: true
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
offset: 0
|
||||
items_per_page: 50
|
||||
total_pages: null
|
||||
id: 0
|
||||
tags:
|
||||
next: 'Next ›'
|
||||
previous: '‹ Previous'
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: true
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'administer blocks'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
empty:
|
||||
area_text_custom:
|
||||
id: area_text_custom
|
||||
table: views
|
||||
field: area_text_custom
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
plugin_id: text_custom
|
||||
empty: true
|
||||
content: 'There are no content blocks available.'
|
||||
tokenize: false
|
||||
block_content_listing_empty:
|
||||
id: block_content_listing_empty
|
||||
table: block_content
|
||||
field: block_content_listing_empty
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
plugin_id: block_content_listing_empty
|
||||
label: ''
|
||||
empty: true
|
||||
sorts: { }
|
||||
arguments: { }
|
||||
filters:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: info
|
||||
plugin_id: string
|
||||
operator: contains
|
||||
value: ''
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: info_op
|
||||
label: 'Block description'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: info_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: info
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
type:
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
plugin_id: bundle
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: type_op
|
||||
label: 'Block type'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: type_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: type
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
reusable:
|
||||
id: reusable
|
||||
table: block_content_field_data
|
||||
field: reusable
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
entity_type: block_content
|
||||
entity_field: reusable
|
||||
plugin_id: boolean
|
||||
operator: '='
|
||||
value: '1'
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
columns:
|
||||
info: info
|
||||
type: type
|
||||
changed: changed
|
||||
operations: operations
|
||||
default: changed
|
||||
info:
|
||||
info:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
type:
|
||||
sortable: true
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
changed:
|
||||
sortable: true
|
||||
default_sort_order: desc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
operations:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
override: true
|
||||
sticky: false
|
||||
summary: ''
|
||||
empty_table: true
|
||||
caption: ''
|
||||
description: ''
|
||||
row:
|
||||
type: fields
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
query_comment: ''
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_tags: { }
|
||||
relationships: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
page_1:
|
||||
id: page_1
|
||||
display_title: Page
|
||||
display_plugin: page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: admin/content/block
|
||||
menu:
|
||||
type: tab
|
||||
title: 'Blocks'
|
||||
description: 'Create and edit content blocks.'
|
||||
weight: 0
|
||||
menu_name: admin
|
||||
parent: system.admin_content
|
||||
context: '0'
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- url.query_args
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,7 @@
|
||||
name: "Content Block module tests"
|
||||
type: module
|
||||
description: "Support module for content block related testing."
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:block_content
|
||||
69
tests/modules/block_content_test/block_content_test.module
Normal file
69
tests/modules/block_content_test/block_content_test.module
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* A dummy module for testing content block related hooks.
|
||||
*
|
||||
* This is a dummy module that implements content block related hooks to test API
|
||||
* interaction with the block_content module.
|
||||
*/
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
|
||||
/**
|
||||
* Implements hook_block_content_view().
|
||||
*/
|
||||
function block_content_test_block_content_view(array &$build, BlockContent $block_content, $view_mode) {
|
||||
// Add extra content.
|
||||
$build['extra_content'] = [
|
||||
'#markup' => '<blink>Wow</blink>',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_content_presave().
|
||||
*/
|
||||
function block_content_test_block_content_presave(BlockContent $block_content) {
|
||||
if ($block_content->label() == 'testing_block_content_presave') {
|
||||
$block_content->setInfo($block_content->label() . '_presave');
|
||||
}
|
||||
// Determine changes.
|
||||
if (!empty($block_content->original) && $block_content->original->label() == 'test_changes') {
|
||||
if ($block_content->original->label() != $block_content->label()) {
|
||||
$block_content->setInfo($block_content->label() . '_presave');
|
||||
// Drupal 1.0 release.
|
||||
$block_content->changed = 979534800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_content_update().
|
||||
*/
|
||||
function block_content_test_block_content_update(BlockContent $block_content) {
|
||||
// Determine changes on update.
|
||||
if (!empty($block_content->original) && $block_content->original->label() == 'test_changes') {
|
||||
if ($block_content->original->label() != $block_content->label()) {
|
||||
$block_content->setInfo($block_content->label() . '_update');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_block_content_insert().
|
||||
*
|
||||
* This tests saving a block_content on block_content insert.
|
||||
*
|
||||
* @see \Drupal\block_content\Tests\BlockContentSaveTest::testBlockContentSaveOnInsert()
|
||||
*/
|
||||
function block_content_test_block_content_insert(BlockContent $block_content) {
|
||||
// Set the block_content title to the block_content ID and save.
|
||||
if ($block_content->label() == 'new') {
|
||||
$block_content->setInfo('BlockContent ' . $block_content->id());
|
||||
$block_content->setNewRevision(FALSE);
|
||||
$block_content->save();
|
||||
}
|
||||
if ($block_content->label() == 'fail_creation') {
|
||||
throw new Exception('Test exception for rollback.');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
block_content_test.block_content_view:
|
||||
path: '/block-content/{block_content}'
|
||||
defaults:
|
||||
_entity_view: 'block_content'
|
||||
requirements:
|
||||
_entity_access: 'block_content.view'
|
||||
@ -0,0 +1,26 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
theme:
|
||||
- stark
|
||||
id: foobar_gorilla
|
||||
theme: stark
|
||||
region: content
|
||||
weight: null
|
||||
provider: null
|
||||
plugin: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e'
|
||||
settings:
|
||||
id: 'block_content:fb5e8434-3617-4a1d-a252-8273e95ec30e'
|
||||
label: 'Foobar Gorilla'
|
||||
label_display: visible
|
||||
provider: block_content
|
||||
status: true
|
||||
info: ''
|
||||
view_mode: default
|
||||
visibility:
|
||||
request_path:
|
||||
id: request_path
|
||||
negate: false
|
||||
pages: ''
|
||||
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content_test\Plugin\EntityReferenceSelection;
|
||||
|
||||
use Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection;
|
||||
|
||||
/**
|
||||
* Test EntityReferenceSelection with conditions on the 'reusable' field.
|
||||
*/
|
||||
class TestSelection extends DefaultSelection {
|
||||
|
||||
/**
|
||||
* The condition type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $conditionType;
|
||||
|
||||
/**
|
||||
* Whether to set the condition for reusable or non-reusable blocks.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isReusable;
|
||||
|
||||
/**
|
||||
* Sets the test mode.
|
||||
*
|
||||
* @param string $condition_type
|
||||
* The condition type.
|
||||
* @param bool $is_reusable
|
||||
* Whether to set the condition for reusable or non-reusable blocks.
|
||||
*/
|
||||
public function setTestMode($condition_type = NULL, $is_reusable = NULL) {
|
||||
$this->conditionType = $condition_type;
|
||||
$this->isReusable = $is_reusable;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
if ($this->conditionType) {
|
||||
/** @var \Drupal\Core\Database\Query\ConditionInterface $add_condition */
|
||||
$add_condition = NULL;
|
||||
switch ($this->conditionType) {
|
||||
case 'base':
|
||||
$add_condition = $query;
|
||||
break;
|
||||
|
||||
case 'group':
|
||||
$group = $query->andConditionGroup()
|
||||
->exists('type');
|
||||
$add_condition = $group;
|
||||
$query->condition($group);
|
||||
break;
|
||||
|
||||
case "nested_group":
|
||||
$query->exists('type');
|
||||
$sub_group = $query->andConditionGroup()
|
||||
->exists('type');
|
||||
$add_condition = $sub_group;
|
||||
$group = $query->andConditionGroup()
|
||||
->exists('type')
|
||||
->condition($sub_group);
|
||||
$query->condition($group);
|
||||
break;
|
||||
}
|
||||
if ($this->isReusable) {
|
||||
$add_condition->condition('reusable', 1);
|
||||
}
|
||||
else {
|
||||
$add_condition->condition('reusable', 0);
|
||||
}
|
||||
}
|
||||
return $query;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
name: 'Block Content test views'
|
||||
type: module
|
||||
description: 'Provides default views for views block_content tests.'
|
||||
package: Testing
|
||||
version: VERSION
|
||||
dependencies:
|
||||
- drupal:block_content
|
||||
- drupal:views
|
||||
@ -0,0 +1,233 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_block_content_redirect_destination
|
||||
label: 'Redirect destination'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: mini
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: ‹‹
|
||||
next: ››
|
||||
style:
|
||||
type: table
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
override: true
|
||||
sticky: false
|
||||
caption: ''
|
||||
summary: ''
|
||||
description: ''
|
||||
columns:
|
||||
info: info
|
||||
info:
|
||||
info:
|
||||
sortable: false
|
||||
default_sort_order: asc
|
||||
align: ''
|
||||
separator: ''
|
||||
empty_column: false
|
||||
responsive: ''
|
||||
default: '-1'
|
||||
empty_table: false
|
||||
row:
|
||||
type: 'entity:block_content'
|
||||
fields:
|
||||
info:
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
id: info
|
||||
entity_type: null
|
||||
entity_field: info
|
||||
plugin_id: field
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: string
|
||||
settings: { }
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
operations:
|
||||
id: operations
|
||||
table: block_content
|
||||
field: operations
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: 'Operations links'
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
destination: true
|
||||
entity_type: block_content
|
||||
plugin_id: entity_operations
|
||||
filters: { }
|
||||
sorts: { }
|
||||
title: 'Redirect destination'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: 1
|
||||
display_options:
|
||||
display_extenders: { }
|
||||
path: /admin/content/redirect_destination
|
||||
cache_metadata:
|
||||
max-age: 0
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url.query_args
|
||||
tags: { }
|
||||
@ -0,0 +1,66 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_block_content_revision_id
|
||||
label: test_block_content_revision_id
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_revision
|
||||
base_field: revision_id
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
relationships:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
required: true
|
||||
plugin_id: standard
|
||||
fields:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
id_1:
|
||||
id: id_1
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
arguments:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: numeric
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
sorts:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
order: ASC
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,69 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_block_content_revision_revision_id
|
||||
label: test_block_content_revision_revision_id
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_revision
|
||||
base_field: revision_id
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
relationships:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
required: true
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
plugin_id: standard
|
||||
fields:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
id_1:
|
||||
id: id_1
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: revision_id
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
arguments:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
plugin_id: block_content_id
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
sorts:
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
order: ASC
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
display_extenders: { }
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
@ -0,0 +1,324 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
- user
|
||||
id: test_block_content_revision_user
|
||||
label: 'Test block content revision user'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: perm
|
||||
options:
|
||||
perm: 'access content'
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
options:
|
||||
grouping: { }
|
||||
row_class: ''
|
||||
default_row_class: true
|
||||
uses_fields: false
|
||||
row:
|
||||
type: fields
|
||||
options:
|
||||
inline: { }
|
||||
separator: ''
|
||||
hide_empty: false
|
||||
default_field_elements: true
|
||||
fields:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_revision
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
plugin_id: field
|
||||
revision_id:
|
||||
id: revision_id
|
||||
table: block_content_field_revision
|
||||
field: revision_id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: value
|
||||
type: number_integer
|
||||
settings:
|
||||
thousand_separator: ''
|
||||
prefix_suffix: false
|
||||
group_column: value
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: block_content
|
||||
entity_field: revision_id
|
||||
plugin_id: field
|
||||
revision_user:
|
||||
id: revision_user
|
||||
table: block_content_revision
|
||||
field: revision_user
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: ''
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: false
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
click_sort_column: target_id
|
||||
type: entity_reference_label
|
||||
settings:
|
||||
link: false
|
||||
group_column: target_id
|
||||
group_columns: { }
|
||||
group_rows: true
|
||||
delta_limit: 0
|
||||
delta_offset: 0
|
||||
delta_reversed: false
|
||||
delta_first_last: false
|
||||
multi_type: separator
|
||||
separator: ', '
|
||||
field_api_classes: false
|
||||
entity_type: block_content
|
||||
entity_field: revision_user
|
||||
plugin_id: field
|
||||
filters:
|
||||
revision_user:
|
||||
id: revision_user
|
||||
table: block_content_revision
|
||||
field: revision_user
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: in
|
||||
value: { }
|
||||
group: 1
|
||||
exposed: true
|
||||
expose:
|
||||
operator_id: revision_user_op
|
||||
label: 'Revision user'
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: revision_user_op
|
||||
operator_limit_selection: false
|
||||
operator_list: { }
|
||||
identifier: revision_user
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
anonymous: '0'
|
||||
administrator: '0'
|
||||
reduce: false
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
entity_type: block_content
|
||||
entity_field: revision_user
|
||||
plugin_id: user_name
|
||||
sorts: { }
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
display_extenders: { }
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups: { }
|
||||
cache_metadata:
|
||||
max-age: -1
|
||||
contexts:
|
||||
- 'languages:language_content'
|
||||
- 'languages:language_interface'
|
||||
- url
|
||||
- 'user.block_content_grants:view'
|
||||
- user.permissions
|
||||
tags: { }
|
||||
@ -0,0 +1,190 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_block_content_view
|
||||
label: test_block_content_view
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: null
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: full
|
||||
options:
|
||||
items_per_page: 10
|
||||
offset: 0
|
||||
id: 0
|
||||
total_pages: null
|
||||
expose:
|
||||
items_per_page: false
|
||||
items_per_page_label: 'Items per page'
|
||||
items_per_page_options: '5, 10, 25, 50'
|
||||
items_per_page_options_all: false
|
||||
items_per_page_options_all_label: '- All -'
|
||||
offset: false
|
||||
offset_label: Offset
|
||||
tags:
|
||||
previous: '‹ Previous'
|
||||
next: 'Next ›'
|
||||
first: '« First'
|
||||
last: 'Last »'
|
||||
quantity: 9
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: fields
|
||||
fields:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
label: Id
|
||||
exclude: false
|
||||
alter:
|
||||
alter_text: false
|
||||
text: ''
|
||||
make_link: false
|
||||
path: ''
|
||||
absolute: false
|
||||
external: false
|
||||
replace_spaces: false
|
||||
path_case: none
|
||||
trim_whitespace: false
|
||||
alt: ''
|
||||
rel: ''
|
||||
link_class: ''
|
||||
prefix: ''
|
||||
suffix: ''
|
||||
target: ''
|
||||
nl2br: false
|
||||
max_length: 0
|
||||
word_boundary: true
|
||||
ellipsis: true
|
||||
more_link: false
|
||||
more_link_text: ''
|
||||
more_link_path: ''
|
||||
strip_tags: false
|
||||
trim: false
|
||||
preserve_tags: ''
|
||||
html: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
hide_alter_empty: true
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
sorts:
|
||||
id:
|
||||
id: id
|
||||
table: block_content_field_data
|
||||
field: id
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
order: ASC
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
entity_type: block_content
|
||||
entity_field: id
|
||||
plugin_id: standard
|
||||
title: test_block_content_view
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
display_extenders: { }
|
||||
arguments:
|
||||
type:
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
field: type
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
default_action: 'not found'
|
||||
exception:
|
||||
value: all
|
||||
title_enable: false
|
||||
title: All
|
||||
title_enable: false
|
||||
title: ''
|
||||
default_argument_type: fixed
|
||||
default_argument_options:
|
||||
argument: ''
|
||||
summary_options:
|
||||
base_path: ''
|
||||
count: true
|
||||
items_per_page: 25
|
||||
override: false
|
||||
summary:
|
||||
sort_order: asc
|
||||
number_of_records: 0
|
||||
format: default_summary
|
||||
specify_validation: false
|
||||
validate:
|
||||
type: none
|
||||
fail: 'not found'
|
||||
validate_options: { }
|
||||
glossary: false
|
||||
limit: 0
|
||||
case: none
|
||||
path_case: none
|
||||
transform_dash: false
|
||||
break_phrase: false
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
plugin_id: string
|
||||
page_1:
|
||||
display_plugin: page
|
||||
id: page_1
|
||||
display_title: Page
|
||||
position: null
|
||||
display_options:
|
||||
path: test-block_content-view
|
||||
display_extenders: { }
|
||||
@ -0,0 +1,338 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_field_filters
|
||||
label: 'Test field filters'
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
display_plugin: default
|
||||
id: default
|
||||
display_title: Default
|
||||
position: 0
|
||||
display_options:
|
||||
access:
|
||||
type: none
|
||||
options: { }
|
||||
cache:
|
||||
type: tag
|
||||
options: { }
|
||||
query:
|
||||
type: views_query
|
||||
options:
|
||||
disable_sql_rewrite: false
|
||||
distinct: false
|
||||
replica: false
|
||||
query_comment: ''
|
||||
query_tags: { }
|
||||
exposed_form:
|
||||
type: basic
|
||||
options:
|
||||
submit_button: Apply
|
||||
reset_button: false
|
||||
reset_button_label: Reset
|
||||
exposed_sorts_label: 'Sort by'
|
||||
expose_sort_order: true
|
||||
sort_asc_label: Asc
|
||||
sort_desc_label: Desc
|
||||
pager:
|
||||
type: none
|
||||
options:
|
||||
items_per_page: 0
|
||||
offset: 0
|
||||
style:
|
||||
type: default
|
||||
row:
|
||||
type: 'entity:block_content'
|
||||
options:
|
||||
relationship: none
|
||||
view_mode: default
|
||||
fields:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
label: ''
|
||||
alter:
|
||||
alter_text: false
|
||||
make_link: false
|
||||
absolute: false
|
||||
trim: false
|
||||
word_boundary: false
|
||||
ellipsis: false
|
||||
strip_tags: false
|
||||
html: false
|
||||
hide_empty: false
|
||||
empty_zero: false
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exclude: false
|
||||
element_type: ''
|
||||
element_class: ''
|
||||
element_label_type: ''
|
||||
element_label_class: ''
|
||||
element_label_colon: true
|
||||
element_wrapper_type: ''
|
||||
element_wrapper_class: ''
|
||||
element_default_classes: true
|
||||
empty: ''
|
||||
hide_alter_empty: true
|
||||
entity_type: block_content
|
||||
type: string
|
||||
settings:
|
||||
link_to_entity: true
|
||||
entity_field: title
|
||||
plugin_id: field
|
||||
filters:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: Paris
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
entity_type: block_content
|
||||
entity_field: info
|
||||
sorts:
|
||||
changed:
|
||||
id: changed
|
||||
table: block_content_field_data
|
||||
field: changed
|
||||
order: DESC
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
exposed: false
|
||||
expose:
|
||||
label: ''
|
||||
granularity: second
|
||||
plugin_id: date
|
||||
entity_type: block_content
|
||||
entity_field: changed
|
||||
title: 'Test field filters'
|
||||
header: { }
|
||||
footer: { }
|
||||
empty: { }
|
||||
relationships: { }
|
||||
arguments: { }
|
||||
rendering_language: '***LANGUAGE_entity_translation***'
|
||||
display_extenders: { }
|
||||
page_bf:
|
||||
display_plugin: page
|
||||
id: page_bf
|
||||
display_title: 'Body filter page'
|
||||
position: 1
|
||||
display_options:
|
||||
path: test-body-filter
|
||||
display_description: ''
|
||||
title: 'Test body filters'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
body_value:
|
||||
id: body_value
|
||||
table: block_content__body
|
||||
field: body_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: Comida
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
entity_type: block_content
|
||||
entity_field: body
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
display_extenders: { }
|
||||
page_bfp:
|
||||
display_plugin: page
|
||||
id: page_bfp
|
||||
display_title: 'Body filter page Paris'
|
||||
position: 1
|
||||
display_options:
|
||||
path: test-body-paris
|
||||
display_description: ''
|
||||
title: 'Test body filters'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
body_value:
|
||||
id: body_value
|
||||
table: block_content__body
|
||||
field: body_value
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: Paris
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
entity_type: block_content
|
||||
entity_field: body
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
display_extenders: { }
|
||||
page_if:
|
||||
display_plugin: page
|
||||
id: page_if
|
||||
display_title: 'Info filter page'
|
||||
position: 1
|
||||
display_options:
|
||||
path: test-info-filter
|
||||
display_description: ''
|
||||
title: 'Test info filter'
|
||||
defaults:
|
||||
title: false
|
||||
filters: false
|
||||
filter_groups: false
|
||||
filters:
|
||||
info:
|
||||
id: info
|
||||
table: block_content_field_data
|
||||
field: info
|
||||
relationship: none
|
||||
group_type: group
|
||||
admin_label: ''
|
||||
operator: contains
|
||||
value: Comida
|
||||
group: 1
|
||||
exposed: false
|
||||
expose:
|
||||
operator_id: ''
|
||||
label: ''
|
||||
description: ''
|
||||
use_operator: false
|
||||
operator: ''
|
||||
identifier: ''
|
||||
required: false
|
||||
remember: false
|
||||
multiple: false
|
||||
remember_roles:
|
||||
authenticated: authenticated
|
||||
is_grouped: false
|
||||
group_info:
|
||||
label: ''
|
||||
description: ''
|
||||
identifier: ''
|
||||
optional: true
|
||||
widget: select
|
||||
multiple: false
|
||||
remember: false
|
||||
default_group: All
|
||||
default_group_multiple: { }
|
||||
group_items: { }
|
||||
plugin_id: string
|
||||
entity_type: block_content
|
||||
entity_field: info
|
||||
filter_groups:
|
||||
operator: AND
|
||||
groups:
|
||||
1: AND
|
||||
display_extenders: { }
|
||||
page_ifp:
|
||||
display_plugin: page
|
||||
id: page_ifp
|
||||
display_title: 'Info filter page Paris'
|
||||
position: 1
|
||||
display_options:
|
||||
path: test-info-paris
|
||||
display_description: ''
|
||||
title: 'Test info filter'
|
||||
defaults:
|
||||
title: false
|
||||
display_extenders: { }
|
||||
@ -0,0 +1,27 @@
|
||||
langcode: en
|
||||
status: true
|
||||
dependencies:
|
||||
module:
|
||||
- block_content
|
||||
id: test_field_type
|
||||
label: test_field_type
|
||||
module: views
|
||||
description: ''
|
||||
tag: ''
|
||||
base_table: block_content_field_data
|
||||
base_field: id
|
||||
display:
|
||||
default:
|
||||
display_options:
|
||||
fields:
|
||||
type:
|
||||
field: type
|
||||
id: type
|
||||
table: block_content_field_data
|
||||
plugin_id: field
|
||||
entity_type: block_content
|
||||
entity_field: type
|
||||
display_plugin: default
|
||||
display_title: Default
|
||||
id: default
|
||||
position: 0
|
||||
100
tests/src/Functional/BlockContentCacheTagsTest.php
Normal file
100
tests/src/Functional/BlockContentCacheTagsTest.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheableMetadata;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase;
|
||||
|
||||
/**
|
||||
* Tests the Content Block entity's cache tags.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentCacheTagsTest extends EntityCacheTagsTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block_content'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
$block_content_type = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'basic',
|
||||
'revision' => FALSE,
|
||||
]);
|
||||
$block_content_type->save();
|
||||
block_content_add_body_field($block_content_type->id());
|
||||
|
||||
// Create a "Llama" content block.
|
||||
$block_content = BlockContent::create([
|
||||
'info' => 'Llama',
|
||||
'type' => 'basic',
|
||||
'body' => [
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
]);
|
||||
$block_content->save();
|
||||
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see \Drupal\block_content\BlockContentAccessControlHandler::checkAccess()
|
||||
*/
|
||||
protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Each comment must have a comment body, which always has a text format.
|
||||
*/
|
||||
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
|
||||
return ['config:filter.format.plain_text'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the block is cached with the correct contexts and tags.
|
||||
*/
|
||||
public function testBlock(): void {
|
||||
$block = $this->drupalPlaceBlock('block_content:' . $this->entity->uuid());
|
||||
$build = $this->container->get('entity_type.manager')->getViewBuilder('block')->view($block, 'block');
|
||||
|
||||
// Render the block.
|
||||
$this->container->get('renderer')->renderRoot($build);
|
||||
|
||||
// Expected keys, contexts, and tags for the block.
|
||||
// @see \Drupal\block\BlockViewBuilder::viewMultiple()
|
||||
$expected_block_cache_keys = ['entity_view', 'block', $block->id()];
|
||||
$expected_block_cache_tags = Cache::mergeTags(['block_view', 'rendered'], $block->getCacheTags());
|
||||
$expected_block_cache_tags = Cache::mergeTags($expected_block_cache_tags, $block->getPlugin()->getCacheTags());
|
||||
|
||||
// Expected contexts and tags for the BlockContent entity.
|
||||
// @see \Drupal\Core\Entity\EntityViewBuilder::getBuildDefaults().
|
||||
$expected_entity_cache_tags = Cache::mergeTags(['block_content_view'], $this->entity->getCacheTags());
|
||||
$expected_entity_cache_tags = Cache::mergeTags($expected_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
|
||||
|
||||
// Verify that what was render cached matches the above expectations.
|
||||
$this->verifyRenderCache($expected_block_cache_keys, Cache::mergeTags($expected_block_cache_tags, $expected_entity_cache_tags), CacheableMetadata::createFromRenderArray($build));
|
||||
}
|
||||
|
||||
}
|
||||
44
tests/src/Functional/BlockContentContextualLinksTest.php
Normal file
44
tests/src/Functional/BlockContentContextualLinksTest.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Tests views contextual links on block content.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentContextualLinksTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'contextual',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests contextual links.
|
||||
*/
|
||||
public function testBlockContentContextualLinks(): void {
|
||||
$block_content = $this->createBlockContent();
|
||||
|
||||
$block = $this->placeBlock('block_content:' . $block_content->uuid());
|
||||
|
||||
$user = $this->drupalCreateUser([
|
||||
'administer blocks',
|
||||
'access contextual links',
|
||||
]);
|
||||
$this->drupalLogin($user);
|
||||
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertSession()->elementAttributeContains('css', 'div[data-contextual-id]', 'data-contextual-id', 'block:block=' . $block->id() . ':langcode=en|block_content:block_content=' . $block_content->id() . ':');
|
||||
}
|
||||
|
||||
}
|
||||
336
tests/src/Functional/BlockContentCreationTest.php
Normal file
336
tests/src/Functional/BlockContentCreationTest.php
Normal file
@ -0,0 +1,336 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\Core\Database\Database;
|
||||
|
||||
// cspell:ignore testblock
|
||||
|
||||
/**
|
||||
* Create a block and test saving it.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentCreationTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* Modules to install.
|
||||
*
|
||||
* Enable dummy module that implements hook_block_insert() for exceptions and
|
||||
* field_ui to edit display settings.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $modules = ['block_content_test', 'dblog', 'field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer blocks',
|
||||
'administer block_content display',
|
||||
'access block library',
|
||||
'administer block content',
|
||||
];
|
||||
|
||||
/**
|
||||
* Sets the test up.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "Basic block" block and verifies its consistency in the database.
|
||||
*/
|
||||
public function testBlockContentCreation(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create a block.
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = 'Test Block';
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the Basic block has been created.
|
||||
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
|
||||
|
||||
// Check that the view mode setting is hidden because only one exists.
|
||||
$this->assertSession()->fieldNotExists('settings[view_mode]');
|
||||
|
||||
// Check that the block exists in the database.
|
||||
$block = $this->getBlockByLabel($edit['info[0][value]']);
|
||||
$this->assertNotEmpty($block, 'Content Block found in database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a "Basic page" block with multiple view modes.
|
||||
*/
|
||||
public function testBlockContentCreationMultipleViewModes(): void {
|
||||
// Add a new view mode and verify if it is selected as expected.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer display modes']));
|
||||
$this->drupalGet('admin/structure/display-modes/view/add/block_content');
|
||||
$edit = [
|
||||
'id' => 'test_view_mode',
|
||||
'label' => 'Test View Mode',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->pageTextContains('Saved the ' . $edit['label'] . ' view mode.');
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create a block.
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = 'Test Block';
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit, 'Save and configure');
|
||||
|
||||
// Save our block permanently
|
||||
$this->submitForm(['region' => 'content'], 'Save block');
|
||||
|
||||
// Set test_view_mode as a custom display to be available on the list.
|
||||
$this->drupalGet('admin/structure/block-content/manage/basic/display');
|
||||
$custom_view_mode = [
|
||||
'display_modes_custom[test_view_mode]' => 1,
|
||||
];
|
||||
$this->submitForm($custom_view_mode, 'Save');
|
||||
|
||||
// Go to the configure page and change the view mode.
|
||||
$this->drupalGet('admin/structure/block/manage/stark_testblock');
|
||||
|
||||
// Test the available view mode options.
|
||||
// Verify that the default view mode is available.
|
||||
$this->assertSession()->optionExists('edit-settings-view-mode', 'default');
|
||||
// Verify that the test view mode is available.
|
||||
$this->assertSession()->optionExists('edit-settings-view-mode', 'test_view_mode');
|
||||
|
||||
$view_mode['settings[view_mode]'] = 'test_view_mode';
|
||||
$this->submitForm($view_mode, 'Save block');
|
||||
|
||||
// Check that the view mode setting is shown because more than one exists.
|
||||
$this->drupalGet('admin/structure/block/manage/stark_testblock');
|
||||
$this->assertSession()->fieldExists('settings[view_mode]');
|
||||
|
||||
// Change the view mode.
|
||||
$view_mode['region'] = 'content';
|
||||
$view_mode['settings[view_mode]'] = 'test_view_mode';
|
||||
$this->submitForm($view_mode, 'Save block');
|
||||
|
||||
// Go to the configure page and verify the view mode has changed.
|
||||
$this->drupalGet('admin/structure/block/manage/stark_testblock');
|
||||
$this->assertSession()->fieldValueEquals('settings[view_mode]', 'test_view_mode');
|
||||
|
||||
// Check that the block exists in the database.
|
||||
$block = $this->getBlockByLabel($edit['info[0][value]']);
|
||||
$this->assertNotEmpty($block, 'Content Block found in database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the redirect workflow of creating a block_content and block.
|
||||
*/
|
||||
public function testBlockContentFormSubmitHandlers(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Create a block and place in block layout.
|
||||
$this->drupalGet('/admin/content/block');
|
||||
$this->clickLink('Add content block');
|
||||
// Verify destination URL, when clicking "Save and configure" this
|
||||
// destination will be ignored.
|
||||
$base = base_path();
|
||||
$url = 'block/add?destination=' . $base . 'admin/content/block';
|
||||
$this->assertSession()->addressEquals($url);
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = 'Test Block';
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->submitForm($edit, 'Save and configure');
|
||||
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
|
||||
$this->assertSession()->pageTextContains('Configure block');
|
||||
|
||||
// Verify when editing a block "Save and configure" does not appear.
|
||||
$this->drupalGet('/admin/content/block/1');
|
||||
$this->assertSession()->buttonNotExists('Save and configure');
|
||||
|
||||
// Create a block but go back to block library.
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = 'Test Block';
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit, 'Save');
|
||||
// Check that the Basic block has been created.
|
||||
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
|
||||
$this->assertSession()->addressEquals('/admin/content/block');
|
||||
|
||||
// Check that the user is redirected to the block library on edit.
|
||||
$block = $this->getBlockByLabel($edit['info[0][value]']);
|
||||
$this->drupalGet($block->toUrl('edit-form'));
|
||||
$this->submitForm([
|
||||
'info[0][value]' => 'Test Block Updated',
|
||||
], 'Save');
|
||||
$this->assertSession()->addressEquals('admin/content/block');
|
||||
|
||||
// Test with user who doesn't have permission to place a block.
|
||||
$this->drupalLogin($this->drupalCreateUser(['administer block content']));
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->assertSession()->buttonNotExists('Save and configure');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a default content block.
|
||||
*
|
||||
* Creates a content block from defaults and ensures that the 'basic block'
|
||||
* type is being used.
|
||||
*/
|
||||
public function testDefaultBlockContentCreation(): void {
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $this->randomMachineName(8);
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
// Don't pass the content block type in the URL so the default is forced.
|
||||
$this->drupalGet('block/add');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the block has been created and that it is a basic block.
|
||||
$this->assertSession()->pageTextContains('basic ' . $edit['info[0][value]'] . ' has been created.');
|
||||
|
||||
// Check that the block exists in the database.
|
||||
$block = $this->getBlockByLabel($edit['info[0][value]']);
|
||||
$this->assertNotEmpty($block, 'Default Content Block found in database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a transaction rolls back the failed creation.
|
||||
*/
|
||||
public function testFailedBlockCreation(): void {
|
||||
// Create a block.
|
||||
try {
|
||||
$this->createBlockContent('fail_creation');
|
||||
$this->fail('Expected exception has not been thrown.');
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
// Expected exception; just continue testing.
|
||||
}
|
||||
|
||||
$connection = Database::getConnection();
|
||||
|
||||
// Check that the block does not exist in the database.
|
||||
$id = $connection->select('block_content_field_data', 'b')
|
||||
->fields('b', ['id'])
|
||||
->condition('info', 'fail_creation')
|
||||
->execute()
|
||||
->fetchField();
|
||||
$this->assertFalse($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a block.
|
||||
*/
|
||||
public function testBlockDelete(): void {
|
||||
// Create a block.
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $this->randomMachineName(8);
|
||||
$body = $this->randomMachineName(16);
|
||||
$edit['body[0][value]'] = $body;
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Place the block.
|
||||
$instance = [
|
||||
'id' => mb_strtolower($edit['info[0][value]']),
|
||||
'settings[label]' => $edit['info[0][value]'],
|
||||
'region' => 'sidebar_first',
|
||||
];
|
||||
$block = BlockContent::load(1);
|
||||
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm($instance, 'Save block');
|
||||
|
||||
$block = BlockContent::load(1);
|
||||
|
||||
// Test getInstances method.
|
||||
$this->assertCount(1, $block->getInstances());
|
||||
|
||||
// Navigate to home page.
|
||||
$this->drupalGet('');
|
||||
$this->assertSession()->pageTextContains($body);
|
||||
|
||||
// Delete the block.
|
||||
$this->drupalGet('admin/content/block/1/delete');
|
||||
$this->assertSession()->pageTextContains('This will also remove 1 placed block instance.');
|
||||
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertSession()->pageTextContains('The content block ' . $edit['info[0][value]'] . ' has been deleted.');
|
||||
|
||||
// Create another block and force the plugin cache to flush.
|
||||
$edit2 = [];
|
||||
$edit2['info[0][value]'] = $this->randomMachineName(8);
|
||||
$body2 = $this->randomMachineName(16);
|
||||
$edit2['body[0][value]'] = $body2;
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit2, 'Save');
|
||||
|
||||
$this->assertSession()->responseNotContains('Error message');
|
||||
|
||||
// Create another block with no instances, and test we don't get a
|
||||
// confirmation message about deleting instances.
|
||||
$edit3 = [];
|
||||
$edit3['info[0][value]'] = $this->randomMachineName(8);
|
||||
$body = $this->randomMachineName(16);
|
||||
$edit3['body[0][value]'] = $body;
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit3, 'Save');
|
||||
|
||||
// Show the delete confirm form.
|
||||
$this->drupalGet('admin/content/block/3/delete');
|
||||
$this->assertSession()->pageTextNotContains('This will also remove');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests placed content blocks create a dependency in the block placement.
|
||||
*/
|
||||
public function testConfigDependencies(): void {
|
||||
$block = $this->createBlockContent();
|
||||
// Place the block.
|
||||
$block_placement_id = mb_strtolower($block->label());
|
||||
$instance = [
|
||||
'id' => $block_placement_id,
|
||||
'settings[label]' => $block->label(),
|
||||
'region' => 'sidebar_first',
|
||||
];
|
||||
$block = BlockContent::load(1);
|
||||
$url = 'admin/structure/block/add/block_content:' . $block->uuid() . '/' . $this->config('system.theme')->get('default');
|
||||
$this->drupalGet($url);
|
||||
$this->submitForm($instance, 'Save block');
|
||||
|
||||
$dependencies = \Drupal::service('config.manager')->findConfigEntityDependenciesAsEntities('content', [$block->getConfigDependencyName()]);
|
||||
$block_placement = reset($dependencies);
|
||||
$this->assertEquals($block_placement_id, $block_placement->id(), "The block placement config entity has a dependency on the block content entity.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a block based on the label.
|
||||
*/
|
||||
private function getBlockByLabel(string $label): ?BlockContentInterface {
|
||||
$blocks = \Drupal::entityTypeManager()
|
||||
->getStorage('block_content')
|
||||
->loadByProperties(['info' => $label]);
|
||||
if (empty($blocks)) {
|
||||
return NULL;
|
||||
}
|
||||
return reset($blocks);
|
||||
}
|
||||
|
||||
}
|
||||
203
tests/src/Functional/BlockContentListTest.php
Normal file
203
tests/src/Functional/BlockContentListTest.php
Normal file
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
|
||||
/**
|
||||
* Tests the listing of content blocks.
|
||||
*
|
||||
* Tests the fallback block content list when Views is disabled.
|
||||
*
|
||||
* @group block_content
|
||||
* @see \Drupal\block\BlockContentListBuilder
|
||||
* @see \Drupal\block_content\Tests\BlockContentListViewsTest
|
||||
*/
|
||||
class BlockContentListTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* A user with 'access block library' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $baseUser1;
|
||||
|
||||
/**
|
||||
* A user with access to create and edit custom basic blocks.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $baseUser2;
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer blocks',
|
||||
'access block library',
|
||||
'create basic block content',
|
||||
'edit any basic block content',
|
||||
'delete any basic block content',
|
||||
'translate configuration',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'block_content', 'config_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->baseUser1 = $this->drupalCreateUser(['access block library']);
|
||||
$this->baseUser2 = $this->drupalCreateUser([
|
||||
'access block library',
|
||||
'create basic block content',
|
||||
'edit any basic block content',
|
||||
'delete any basic block content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the region value when a new block is saved.
|
||||
*/
|
||||
public function testBlockRegionPlacement(): void {
|
||||
$this->drupalLogin($this->drupalCreateUser($this->permissions));
|
||||
$this->drupalGet("admin/structure/block/library/stark", ['query' => ['region' => 'content']]);
|
||||
|
||||
$this->clickLink('Add content block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [
|
||||
'info[0][value]' => 'foo',
|
||||
];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$this->assertSession()->fieldValueEquals('region', 'content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content block listing page with different permissions.
|
||||
*/
|
||||
public function testListing(): void {
|
||||
// Test with the admin user.
|
||||
$this->drupalLogin($this->drupalCreateUser(['access block library', 'administer block content']));
|
||||
$this->drupalGet('admin/content/block');
|
||||
|
||||
// Test for the page title.
|
||||
$this->assertSession()->titleEquals('Content blocks | Drupal');
|
||||
|
||||
// Test for the table.
|
||||
$this->assertSession()->elementExists('xpath', '//div[@class="layout-content"]//table');
|
||||
|
||||
// Test the table header, two cells should be present.
|
||||
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/thead/tr/th', 2);
|
||||
|
||||
// Test the contents of each th cell.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[1]', 'Block description');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[2]', 'Operations');
|
||||
|
||||
$label = 'Antelope';
|
||||
$new_label = 'Albatross';
|
||||
// Add a new entity using the operations link.
|
||||
$this->clickLink('Add content block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $label;
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Confirm that once the user returns to the listing, the text of the label
|
||||
// (versus elsewhere on the page).
|
||||
$this->assertSession()->elementTextContains('xpath', '//td', $label);
|
||||
|
||||
// Check the number of table row cells.
|
||||
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/tbody/tr[1]/td', 2);
|
||||
// Check the contents of the row. The first cell contains the label,
|
||||
// and the second contains the operations list.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/tbody/tr[1]/td[1]', $label);
|
||||
|
||||
// Edit the entity using the operations link.
|
||||
$blocks = $this->container
|
||||
->get('entity_type.manager')
|
||||
->getStorage('block_content')
|
||||
->loadByProperties(['info' => $label]);
|
||||
$block = reset($blocks);
|
||||
if (!empty($block)) {
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id());
|
||||
$this->clickLink('Edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Edit content block $label | Drupal");
|
||||
$edit = ['info[0][value]' => $new_label];
|
||||
$this->submitForm($edit, 'Save');
|
||||
}
|
||||
else {
|
||||
$this->fail('Did not find Albatross block in the database.');
|
||||
}
|
||||
|
||||
// Confirm that once the user returns to the listing, the text of the label
|
||||
// (versus elsewhere on the page).
|
||||
$this->assertSession()->elementTextContains('xpath', '//td', $new_label);
|
||||
|
||||
// Delete the added entity using the operations link.
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// Verify that the text of the label and machine name does not appear in
|
||||
// the list (though it may appear elsewhere on the page).
|
||||
$this->assertSession()->elementTextNotContains('xpath', '//td', $new_label);
|
||||
|
||||
// Confirm that the empty text is displayed.
|
||||
$this->assertSession()->pageTextContains('There are no content blocks yet.');
|
||||
|
||||
$block_content = BlockContent::create([
|
||||
'info' => 'Non-reusable block',
|
||||
'type' => 'basic',
|
||||
'reusable' => FALSE,
|
||||
]);
|
||||
$block_content->save();
|
||||
|
||||
$this->drupalGet('admin/content/block');
|
||||
// Confirm that the empty text is displayed.
|
||||
$this->assertSession()->pageTextContains('There are no content blocks yet.');
|
||||
// Confirm the non-reusable block is not on the page.
|
||||
$this->assertSession()->pageTextNotContains('Non-reusable block');
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
// Create test block for other user tests.
|
||||
$test_block = $this->createBlockContent($label);
|
||||
|
||||
$link_text = t('Add content block');
|
||||
// Test as a user with view only permissions.
|
||||
$this->drupalLogin($this->baseUser1);
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->linkNotExists($link_text);
|
||||
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id());
|
||||
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id() . '/delete');
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
// Test as a user with permission to create/edit/delete basic blocks.
|
||||
$this->drupalLogin($this->baseUser2);
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->linkExists($link_text);
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id());
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id() . '/delete');
|
||||
}
|
||||
|
||||
}
|
||||
205
tests/src/Functional/BlockContentListViewsTest.php
Normal file
205
tests/src/Functional/BlockContentListViewsTest.php
Normal file
@ -0,0 +1,205 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
|
||||
/**
|
||||
* Tests the Views-powered listing of content blocks.
|
||||
*
|
||||
* @group block_content
|
||||
* @see \Drupal\block\BlockContentListBuilder
|
||||
* @see \Drupal\block_content\Tests\BlockContentListTest
|
||||
*/
|
||||
class BlockContentListViewsTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* A user with 'access block library' permission.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $baseUser1;
|
||||
|
||||
/**
|
||||
* A user with access to create and edit custom basic blocks.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $baseUser2;
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer blocks',
|
||||
'access block library',
|
||||
'create basic block content',
|
||||
'edit any basic block content',
|
||||
'delete any basic block content',
|
||||
'translate configuration',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'block',
|
||||
'block_content',
|
||||
'config_translation',
|
||||
'views',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->baseUser1 = $this->drupalCreateUser(['access block library']);
|
||||
$this->baseUser2 = $this->drupalCreateUser([
|
||||
'access block library',
|
||||
'create basic block content',
|
||||
'edit any basic block content',
|
||||
'delete any basic block content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the content block listing page.
|
||||
*/
|
||||
public function testListing(): void {
|
||||
// Test with an admin user.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('admin/content/block');
|
||||
|
||||
// Test for the page title.
|
||||
$this->assertSession()->titleEquals('Content blocks | Drupal');
|
||||
|
||||
// Test for the exposed filters.
|
||||
$this->assertSession()->fieldExists('info');
|
||||
$this->assertSession()->fieldExists('type');
|
||||
|
||||
// Test for the table.
|
||||
$this->assertSession()->elementExists('xpath', '//div[@class="layout-content"]//table');
|
||||
|
||||
// Test the table header, four cells should be present.
|
||||
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/thead/tr/th', 4);
|
||||
|
||||
// Test the contents of each th cell.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[1]', 'Block description');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[2]', 'Block type');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[3]', 'Updated Sort ascending');
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/thead/tr/th[4]', 'Operations');
|
||||
|
||||
$label = 'Antelope';
|
||||
$new_label = 'Albatross';
|
||||
// Add a new entity using the operations link.
|
||||
$this->clickLink('Add content block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $label;
|
||||
$edit['body[0][value]'] = $this->randomMachineName(16);
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Confirm that once the user returns to the listing, the text of the label
|
||||
// (versus elsewhere on the page).
|
||||
$this->assertSession()->elementTextContains('xpath', '//td/a', $label);
|
||||
|
||||
// Check the number of table row cells.
|
||||
$this->assertSession()->elementsCount('xpath', '//div[@class="layout-content"]//table/tbody/tr/td', 4);
|
||||
// Check the contents of each row cell. The first cell contains the label,
|
||||
// the second contains the machine name, and the third contains the
|
||||
// operations list.
|
||||
$this->assertSession()->elementTextEquals('xpath', '//div[@class="layout-content"]//table/tbody/tr/td/a', $label);
|
||||
|
||||
// Edit the entity using the operations link.
|
||||
$blocks = $this->container
|
||||
->get('entity_type.manager')
|
||||
->getStorage('block_content')
|
||||
->loadByProperties(['info' => $label]);
|
||||
$block = reset($blocks);
|
||||
if (!empty($block)) {
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id());
|
||||
$this->clickLink('Edit');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Edit content block $label | Drupal");
|
||||
$edit = ['info[0][value]' => $new_label];
|
||||
$this->submitForm($edit, 'Save');
|
||||
}
|
||||
else {
|
||||
$this->fail('Did not find Albatross block in the database.');
|
||||
}
|
||||
|
||||
// Confirm that once the user returns to the listing, the text of the label
|
||||
// (versus elsewhere on the page).
|
||||
$this->assertSession()->elementTextContains('xpath', '//td/a', $new_label);
|
||||
|
||||
// Delete the added entity using the operations link.
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $block->id() . '/delete');
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->titleEquals("Are you sure you want to delete the content block $new_label? | Drupal");
|
||||
$this->submitForm([], 'Delete');
|
||||
|
||||
// Verify that the text of the label and machine name does not appear in
|
||||
// the list (though it may appear elsewhere on the page).
|
||||
$this->assertSession()->elementTextNotContains('xpath', '//td', $new_label);
|
||||
|
||||
// Confirm that the empty text is displayed.
|
||||
$this->assertSession()->pageTextContains('There are no content blocks available.');
|
||||
$this->assertSession()->linkExists('content block');
|
||||
|
||||
$block_content = BlockContent::create([
|
||||
'info' => 'Non-reusable block',
|
||||
'type' => 'basic',
|
||||
'reusable' => FALSE,
|
||||
]);
|
||||
$block_content->save();
|
||||
|
||||
$this->drupalGet('admin/content/block');
|
||||
// Confirm that the empty text is displayed.
|
||||
$this->assertSession()->pageTextContains('There are no content blocks available.');
|
||||
// Confirm the non-reusable block is not on the page.
|
||||
$this->assertSession()->pageTextNotContains('Non-reusable block');
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
// Create test block for other user tests.
|
||||
$test_block = $this->createBlockContent($label);
|
||||
|
||||
$link_text = t('Add content block');
|
||||
// Test as a user with view only permissions.
|
||||
$this->drupalLogin($this->baseUser1);
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->linkNotExists($link_text);
|
||||
$matches = $this->xpath('//td[1]');
|
||||
$actual = $matches[0]->getText();
|
||||
$this->assertEquals($label, $actual, 'Label found for test block.');
|
||||
$this->assertSession()->linkNotExists('Edit');
|
||||
$this->assertSession()->linkNotExists('Delete');
|
||||
$this->assertSession()->linkByHrefNotExists('admin/content/block/' . $test_block->id() . '/delete');
|
||||
|
||||
$this->drupalLogout();
|
||||
|
||||
// Test as a user with permission to create/edit/delete basic blocks.
|
||||
$this->drupalLogin($this->baseUser2);
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->linkExists($link_text);
|
||||
$matches = $this->xpath('//td/a');
|
||||
$actual = $matches[0]->getText();
|
||||
$this->assertEquals($label, $actual, 'Label found for test block.');
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id());
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $test_block->id() . '/delete');
|
||||
}
|
||||
|
||||
}
|
||||
40
tests/src/Functional/BlockContentPageViewTest.php
Normal file
40
tests/src/Functional/BlockContentPageViewTest.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Create a block and test block access by attempting to view the block.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentPageViewTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block_content_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Checks block edit and fallback functionality.
|
||||
*/
|
||||
public function testPageEdit(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$block = $this->createBlockContent();
|
||||
|
||||
// Attempt to view the block.
|
||||
$this->drupalGet('block-content/' . $block->id());
|
||||
|
||||
// Ensure user was able to view the block.
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->drupalGet('<front>');
|
||||
$this->assertSession()->pageTextContains('This block is broken or missing. You may be missing content or you might need to install the original module.');
|
||||
}
|
||||
|
||||
}
|
||||
74
tests/src/Functional/BlockContentRedirectTest.php
Normal file
74
tests/src/Functional/BlockContentRedirectTest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Ensures that custom block type functions work correctly.
|
||||
*
|
||||
* @group block_content
|
||||
* @group legacy
|
||||
*/
|
||||
class BlockContentRedirectTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Tests the deprecation message from the old block-type page.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testBlockContentTypeRedirect(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->expectDeprecation('The path /admin/structure/block/block-content/types is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/structure/block-content. See https://www.drupal.org/node/3320855');
|
||||
$this->drupalGet('/admin/structure/block/block-content/types');
|
||||
$this->assertSession()
|
||||
->pageTextContains("You have been redirected from admin/structure/block/block-content/types. Update links, shortcuts, and bookmarks to use admin/structure/block-content.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deprecation message from the old block library page.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testBlockLibraryRedirect(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->expectDeprecation('The path /admin/structure/block/block-content is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block. See https://www.drupal.org/node/3320855');
|
||||
$this->drupalGet('admin/structure/block/block-content');
|
||||
$this->assertSession()
|
||||
->pageTextContains("You have been redirected from admin/structure/block/block-content. Update links, shortcuts, and bookmarks to use admin/content/block.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deprecation message from the old block edit page.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testBlockContentEditRedirect(): void {
|
||||
$block = $this->createBlockContent();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855');
|
||||
$this->drupalGet("/block/{$block->id()}");
|
||||
$this->assertSession()
|
||||
->pageTextContains("You have been redirected from block/{$block->id()}. Update links, shortcuts, and bookmarks to use admin/content/block/{$block->id()}.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the deprecation message from the old block delete page.
|
||||
*
|
||||
* @group legacy
|
||||
*/
|
||||
public function testBlockContentDeleteRedirect(): void {
|
||||
$block = $this->createBlockContent();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->expectDeprecation('The path /block/{block_content} is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Use /admin/content/block/{block_content}. See https://www.drupal.org/node/3320855');
|
||||
$this->drupalGet("/block/{$block->id()}/delete");
|
||||
$this->assertSession()
|
||||
->pageTextContains("You have been redirected from block/{$block->id()}/delete. Update links, shortcuts, and bookmarks to use admin/content/block/{$block->id()}/delete.");
|
||||
}
|
||||
|
||||
}
|
||||
96
tests/src/Functional/BlockContentRevisionDeleteTest.php
Normal file
96
tests/src/Functional/BlockContentRevisionDeleteTest.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Block content revision delete form test.
|
||||
*
|
||||
* @group block_content
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionDeleteForm
|
||||
*/
|
||||
class BlockContentRevisionDeleteTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $permissions = [
|
||||
'view any basic block content history',
|
||||
'delete any basic block content revisions',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision delete.
|
||||
*/
|
||||
public function testDeleteForm(): void {
|
||||
$entity = $this->createBlockContent(save: FALSE)
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
|
||||
->setRevisionTranslationAffected(TRUE);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
// Cannot delete latest revision.
|
||||
$this->drupalGet($entity->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Create a new non default revision.
|
||||
$entity
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
|
||||
->setRevisionTranslationAffected(TRUE)
|
||||
->setNewRevision();
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->save();
|
||||
$nonDefaultRevisionId = $entity->getRevisionId();
|
||||
|
||||
// Reload the default entity.
|
||||
$revision = \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->loadRevision($revisionId);
|
||||
// Cannot delete default revision.
|
||||
$this->drupalGet($revision->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertFalse($revision->access('delete revision', $this->adminUser, FALSE));
|
||||
|
||||
// Reload the non default entity.
|
||||
$revision2 = \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->loadRevision($nonDefaultRevisionId);
|
||||
$this->drupalGet($revision2->toUrl('revision-delete-form'));
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete the revision from Sun, 01/11/2009 - 17:00?');
|
||||
$this->assertSession()->buttonExists('Delete');
|
||||
$this->assertSession()->linkExists('Cancel');
|
||||
$this->assertTrue($revision2->access('delete revision', $this->adminUser, FALSE));
|
||||
|
||||
$countRevisions = static function (): int {
|
||||
return (int) \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->allRevisions()
|
||||
->count()
|
||||
->execute();
|
||||
};
|
||||
|
||||
$count = $countRevisions();
|
||||
$this->submitForm([], 'Delete');
|
||||
$this->assertEquals($count - 1, $countRevisions());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->addressEquals(sprintf('admin/content/block/%s/revisions', $entity->id()));
|
||||
$this->assertSession()->pageTextContains(sprintf('Revision from Sun, 01/11/2009 - 17:00 of basic %s has been deleted.', $entity->label()));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 1);
|
||||
}
|
||||
|
||||
}
|
||||
98
tests/src/Functional/BlockContentRevisionRevertTest.php
Normal file
98
tests/src/Functional/BlockContentRevisionRevertTest.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Block content revision form test.
|
||||
*
|
||||
* @group block_content
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Form\RevisionRevertForm
|
||||
*/
|
||||
class BlockContentRevisionRevertTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $permissions = [
|
||||
'view any basic block content history',
|
||||
'revert any basic block content revisions',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests revision revert.
|
||||
*/
|
||||
public function testRevertForm(): void {
|
||||
$entity = $this->createBlockContent(save: FALSE)
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 4pm'))->getTimestamp())
|
||||
->setRevisionTranslationAffected(TRUE);
|
||||
$entity->setNewRevision();
|
||||
$entity->save();
|
||||
$revisionId = $entity->getRevisionId();
|
||||
|
||||
// Cannot revert latest revision.
|
||||
$this->drupalGet($entity->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
|
||||
// Create a new non default revision.
|
||||
$entity
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('11 January 2009 5pm'))->getTimestamp())
|
||||
->setRevisionTranslationAffected(TRUE)
|
||||
->setNewRevision();
|
||||
$entity->isDefaultRevision(FALSE);
|
||||
$entity->save();
|
||||
$nonDefaultRevisionId = $entity->getRevisionId();
|
||||
|
||||
// Reload the default entity.
|
||||
$revision = \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->loadRevision($revisionId);
|
||||
// Cannot revert default revision.
|
||||
$this->drupalGet($revision->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->statusCodeEquals(403);
|
||||
$this->assertFalse($revision->access('revert', $this->adminUser, FALSE));
|
||||
|
||||
// Reload the non default entity.
|
||||
$revision2 = \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->loadRevision($nonDefaultRevisionId);
|
||||
$this->drupalGet($revision2->toUrl('revision-revert-form'));
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to revert to the revision from Sun, 01/11/2009 - 17:00?');
|
||||
$this->assertSession()->buttonExists('Revert');
|
||||
$this->assertSession()->linkExists('Cancel');
|
||||
$this->assertTrue($revision2->access('revert', $this->adminUser, FALSE));
|
||||
|
||||
$countRevisions = static function (): int {
|
||||
return (int) \Drupal::entityTypeManager()->getStorage('block_content')
|
||||
->getQuery()
|
||||
->accessCheck(FALSE)
|
||||
->allRevisions()
|
||||
->count()
|
||||
->execute();
|
||||
};
|
||||
|
||||
$count = $countRevisions();
|
||||
$this->submitForm([], 'Revert');
|
||||
$this->assertEquals($count + 1, $countRevisions());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->addressEquals(sprintf('admin/content/block/%s/revisions', $entity->id()));
|
||||
$this->assertSession()->pageTextContains(sprintf('basic %s has been reverted to the revision from Sun, 01/11/2009 - 17:00.', $entity->label()));
|
||||
// Three rows, from the top: the newly reverted revision, the revision from
|
||||
// 5pm, and the revision from 4pm.
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
/**
|
||||
* Block content version history test.
|
||||
*
|
||||
* @group block_content
|
||||
* @coversDefaultClass \Drupal\Core\Entity\Controller\VersionHistoryController
|
||||
*/
|
||||
class BlockContentRevisionVersionHistoryTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $permissions = [
|
||||
'view any basic block content history',
|
||||
'revert any basic block content revisions',
|
||||
'delete any basic block content revisions',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests version history page.
|
||||
*/
|
||||
public function testVersionHistory(): void {
|
||||
$entity = $this->createBlockContent(save: FALSE);
|
||||
|
||||
$entity
|
||||
->setInfo('first revision')
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('1st June 2020 7am'))->getTimestamp())
|
||||
->setRevisionLogMessage('first revision log')
|
||||
->setRevisionUser($this->drupalCreateUser(name: 'first author'))
|
||||
->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity
|
||||
->setInfo('second revision')
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('2nd June 2020 8am'))->getTimestamp())
|
||||
->setRevisionLogMessage('second revision log')
|
||||
->setRevisionUser($this->drupalCreateUser(name: 'second author'))
|
||||
->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$entity
|
||||
->setInfo('third revision')
|
||||
->setRevisionCreationTime((new \DateTimeImmutable('3rd June 2020 9am'))->getTimestamp())
|
||||
->setRevisionLogMessage('third revision log')
|
||||
->setRevisionUser($this->drupalCreateUser(name: 'third author'))
|
||||
->setNewRevision();
|
||||
$entity->save();
|
||||
|
||||
$this->drupalGet($entity->toUrl('version-history'));
|
||||
$this->assertSession()->elementsCount('css', 'table tbody tr', 3);
|
||||
|
||||
// Order is newest to oldest revision by creation order.
|
||||
$row1 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(1)');
|
||||
// Latest revision does not have revert or delete revision operation.
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Revert'], $row1);
|
||||
$this->assertSession()->elementNotExists('named', ['link', 'Delete'], $row1);
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'Current revision');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', 'third revision log');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(1)', '06/03/2020 - 09:00 by third author');
|
||||
|
||||
$row2 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(2)');
|
||||
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row2);
|
||||
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row2);
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', 'second revision log');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(2)', '06/02/2020 - 08:00 by second author');
|
||||
|
||||
$row3 = $this->assertSession()->elementExists('css', 'table tbody tr:nth-child(3)');
|
||||
$this->assertSession()->elementExists('named', ['link', 'Revert'], $row3);
|
||||
$this->assertSession()->elementExists('named', ['link', 'Delete'], $row3);
|
||||
$this->assertSession()->elementTextNotContains('css', 'table tbody tr:nth-child(2)', 'Current revision');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', 'first revision log');
|
||||
$this->assertSession()->elementTextContains('css', 'table tbody tr:nth-child(3)', '06/01/2020 - 07:00 by first author');
|
||||
}
|
||||
|
||||
}
|
||||
111
tests/src/Functional/BlockContentRevisionsTest.php
Normal file
111
tests/src/Functional/BlockContentRevisionsTest.php
Normal file
@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\user\UserInterface;
|
||||
|
||||
/**
|
||||
* Create a block with revisions.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentRevisionsTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Stores blocks created during the test.
|
||||
* @var array
|
||||
*/
|
||||
protected $blocks;
|
||||
|
||||
/**
|
||||
* Stores log messages used during the test.
|
||||
* @var array
|
||||
*/
|
||||
protected $revisionLogs;
|
||||
|
||||
/**
|
||||
* Sets the test up.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
// Create initial block.
|
||||
$block = $this->createBlockContent('initial');
|
||||
|
||||
$blocks = [];
|
||||
$logs = [];
|
||||
|
||||
// Get original block.
|
||||
$blocks[] = $block->getRevisionId();
|
||||
$logs[] = '';
|
||||
|
||||
// Create three revisions.
|
||||
$revision_count = 3;
|
||||
for ($i = 0; $i < $revision_count; $i++) {
|
||||
$block->setNewRevision(TRUE);
|
||||
$block->setRevisionLogMessage($this->randomMachineName(32));
|
||||
$block->setRevisionUser($this->adminUser);
|
||||
$block->setRevisionCreationTime(time());
|
||||
$logs[] = $block->getRevisionLogMessage();
|
||||
$block->save();
|
||||
$blocks[] = $block->getRevisionId();
|
||||
}
|
||||
|
||||
$this->blocks = $blocks;
|
||||
$this->revisionLogs = $logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks block revision related operations.
|
||||
*/
|
||||
public function testRevisions(): void {
|
||||
$blocks = $this->blocks;
|
||||
$logs = $this->revisionLogs;
|
||||
|
||||
foreach ($blocks as $delta => $revision_id) {
|
||||
// Confirm the correct revision text appears.
|
||||
/** @var \Drupal\block_content\BlockContentInterface $loaded */
|
||||
$loaded = $this->container->get('entity_type.manager')
|
||||
->getStorage('block_content')
|
||||
->loadRevision($revision_id);
|
||||
// Verify revision log is the same.
|
||||
$this->assertEquals($logs[$delta], $loaded->getRevisionLogMessage(), "Correct log message found for revision $revision_id");
|
||||
if ($delta > 0) {
|
||||
$this->assertInstanceOf(UserInterface::class, $loaded->getRevisionUser());
|
||||
$this->assertIsNumeric($loaded->getRevisionUserId());
|
||||
$this->assertIsNumeric($loaded->getRevisionCreationTime());
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm that this is the default revision.
|
||||
$this->assertTrue($loaded->isDefaultRevision(), 'Third block revision is the default one.');
|
||||
|
||||
// Make a new revision and set it to not be default.
|
||||
// This will create a new revision that is not "front facing".
|
||||
// Save this as a non-default revision.
|
||||
$loaded->setNewRevision();
|
||||
$loaded->isDefaultRevision(FALSE);
|
||||
$loaded->body = $this->randomMachineName(8);
|
||||
$loaded->save();
|
||||
|
||||
// Confirm that revision body text is not present on default version of
|
||||
// block.
|
||||
$this->drupalGet('admin/content/block/' . $loaded->id());
|
||||
$this->assertSession()->pageTextNotContains($loaded->body->value);
|
||||
|
||||
// Verify that the non-default revision id is greater than the default
|
||||
// revision id.
|
||||
$default_revision = BlockContent::load($loaded->id());
|
||||
// Verify that the revision ID is greater than the default revision ID.
|
||||
$this->assertGreaterThan($default_revision->getRevisionId(), $loaded->getRevisionId());
|
||||
}
|
||||
|
||||
}
|
||||
112
tests/src/Functional/BlockContentSaveTest.php
Normal file
112
tests/src/Functional/BlockContentSaveTest.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
|
||||
/**
|
||||
* Tests $block_content->save() for saving content.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentSaveTest extends BlockContentTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block_content_test'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Sets the test up.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether content block IDs are saved properly during an import.
|
||||
*/
|
||||
public function testImport(): void {
|
||||
// Content block ID must be a number that is not in the database.
|
||||
$max_id = (int) \Drupal::entityQueryAggregate('block_content')
|
||||
->accessCheck(FALSE)
|
||||
->aggregate('id', 'max')
|
||||
->execute()[0]['id_max'];
|
||||
$test_id = $max_id + mt_rand(1000, 1000000);
|
||||
$info = $this->randomMachineName(8);
|
||||
$block_array = [
|
||||
'info' => $info,
|
||||
'body' => ['value' => $this->randomMachineName(32)],
|
||||
'type' => 'basic',
|
||||
'id' => $test_id,
|
||||
];
|
||||
$block = BlockContent::create($block_array);
|
||||
$block->enforceIsNew(TRUE);
|
||||
$block->save();
|
||||
|
||||
// Verify that block_submit did not wipe the provided id.
|
||||
$this->assertEquals($test_id, $block->id(), 'Block imported using provide id');
|
||||
|
||||
// Test the import saved.
|
||||
$block_by_id = BlockContent::load($test_id);
|
||||
$this->assertNotEmpty($block_by_id, 'Content block load by block ID.');
|
||||
$this->assertSame($block_array['body']['value'], $block_by_id->body->value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests determining changes in hook_block_presave().
|
||||
*
|
||||
* Verifies the static block load cache is cleared upon save.
|
||||
*/
|
||||
public function testDeterminingChanges(): void {
|
||||
// Initial creation.
|
||||
$block = $this->createBlockContent('test_changes');
|
||||
// Creating a block should set the changed date to the current time
|
||||
// which is always greater than the time set by hooks we're testing.
|
||||
$this->assertGreaterThan(979534800, $block->getChangedTime(), 'Creating a block sets default "changed" timestamp.');
|
||||
|
||||
// Update the block without applying changes.
|
||||
$block->save();
|
||||
$this->assertEquals('test_changes', $block->label(), 'No changes have been determined.');
|
||||
|
||||
// Apply changes.
|
||||
$block->setInfo('updated');
|
||||
$block->save();
|
||||
|
||||
// The hook implementations block_content_test_block_content_presave() and
|
||||
// block_content_test_block_content_update() determine changes and change
|
||||
// the title as well as programmatically set the 'changed' timestamp.
|
||||
$this->assertEquals('updated_presave_update', $block->label(), 'Changes have been determined.');
|
||||
$this->assertEquals(979534800, $block->getChangedTime(), 'Saving a content block uses "changed" timestamp set in presave hook.');
|
||||
|
||||
// Test the static block load cache to be cleared.
|
||||
$block = BlockContent::load($block->id());
|
||||
$this->assertEquals('updated_presave', $block->label(), 'Static cache has been cleared.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests saving a block on block insert.
|
||||
*
|
||||
* This test ensures that a block has been fully saved when
|
||||
* hook_block_content_insert() is invoked, so that the block can be saved again
|
||||
* in a hook implementation without errors.
|
||||
*
|
||||
* @see block_test_block_insert()
|
||||
*/
|
||||
public function testBlockContentSaveOnInsert(): void {
|
||||
// block_content_test_block_content_insert() triggers a save on insert if the
|
||||
// title equals 'new'.
|
||||
$block = $this->createBlockContent('new');
|
||||
$this->assertEquals('BlockContent ' . $block->id(), $block->label(), 'Content block saved on block insert.');
|
||||
}
|
||||
|
||||
}
|
||||
138
tests/src/Functional/BlockContentTestBase.php
Normal file
138
tests/src/Functional/BlockContentTestBase.php
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Tests\BrowserTestBase;
|
||||
|
||||
/**
|
||||
* Sets up block content types.
|
||||
*/
|
||||
abstract class BlockContentTestBase extends BrowserTestBase {
|
||||
|
||||
/**
|
||||
* Profile to use.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $profile = 'testing';
|
||||
|
||||
/**
|
||||
* Admin user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $adminUser;
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer blocks',
|
||||
'access block library',
|
||||
'administer block types',
|
||||
'administer block content',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block', 'block_content'];
|
||||
|
||||
/**
|
||||
* Whether or not to auto-create the basic block type during setup.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoCreateBasicBlockType = TRUE;
|
||||
|
||||
/**
|
||||
* Sets the test up.
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
if ($this->autoCreateBasicBlockType) {
|
||||
$this->createBlockContentType('basic', TRUE);
|
||||
}
|
||||
|
||||
$this->adminUser = $this->drupalCreateUser($this->permissions);
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a content block.
|
||||
*
|
||||
* @param bool|string $title
|
||||
* (optional) Title of block. When no value is given uses a random name.
|
||||
* Defaults to FALSE.
|
||||
* @param string $bundle
|
||||
* (optional) Bundle name. Defaults to 'basic'.
|
||||
* @param bool $save
|
||||
* (optional) Whether to save the block. Defaults to TRUE.
|
||||
*
|
||||
* @return \Drupal\block_content\Entity\BlockContent
|
||||
* Created content block.
|
||||
*/
|
||||
protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
|
||||
$title = $title ?: $this->randomMachineName();
|
||||
$block_content = BlockContent::create([
|
||||
'info' => $title,
|
||||
'type' => $bundle,
|
||||
'langcode' => 'en',
|
||||
]);
|
||||
if ($block_content && $save === TRUE) {
|
||||
$block_content->save();
|
||||
}
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a block type (bundle).
|
||||
*
|
||||
* @param array|string $values
|
||||
* The value to create the block content type. If $values is an array
|
||||
* it should be like: ['id' => 'foo', 'label' => 'Foo']. If $values
|
||||
* is a string, it will be considered that it represents the label.
|
||||
* @param bool $create_body
|
||||
* Whether or not to create the body field
|
||||
*
|
||||
* @return \Drupal\block_content\Entity\BlockContentType
|
||||
* Created block type.
|
||||
*/
|
||||
protected function createBlockContentType($values, $create_body = FALSE) {
|
||||
if (is_array($values)) {
|
||||
if (!isset($values['id'])) {
|
||||
do {
|
||||
$id = $this->randomMachineName(8);
|
||||
} while (BlockContentType::load($id));
|
||||
}
|
||||
else {
|
||||
$id = $values['id'];
|
||||
}
|
||||
$values += [
|
||||
'id' => $id,
|
||||
'label' => $id,
|
||||
'revision' => FALSE,
|
||||
];
|
||||
$bundle = BlockContentType::create($values);
|
||||
}
|
||||
else {
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => $values,
|
||||
'label' => $values,
|
||||
'revision' => FALSE,
|
||||
]);
|
||||
}
|
||||
$bundle->save();
|
||||
if ($create_body) {
|
||||
block_content_add_body_field($bundle->id());
|
||||
}
|
||||
return $bundle;
|
||||
}
|
||||
|
||||
}
|
||||
158
tests/src/Functional/BlockContentTranslationUITest.php
Normal file
158
tests/src/Functional/BlockContentTranslationUITest.php
Normal file
@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Tests\content_translation\Functional\ContentTranslationUITestBase;
|
||||
|
||||
/**
|
||||
* Tests the block content translation UI.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentTranslationUITest extends ContentTranslationUITestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = [
|
||||
'language',
|
||||
'content_translation',
|
||||
'block',
|
||||
'field_ui',
|
||||
'block_content',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultCacheContexts = [
|
||||
'languages:language_interface',
|
||||
'session',
|
||||
'theme',
|
||||
'url.path',
|
||||
'url.query_args',
|
||||
'user.permissions',
|
||||
'user.roles:authenticated',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
$this->entityTypeId = 'block_content';
|
||||
$this->bundle = 'basic';
|
||||
$this->testLanguageSelector = FALSE;
|
||||
parent::setUp();
|
||||
$this->doSetup();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setupBundle() {
|
||||
// Create the basic bundle since it is provided by standard.
|
||||
$bundle = BlockContentType::create([
|
||||
'id' => $this->bundle,
|
||||
'label' => $this->bundle,
|
||||
'revision' => FALSE,
|
||||
]);
|
||||
$bundle->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTranslatorPermissions() {
|
||||
return array_merge(parent::getTranslatorPermissions(), [
|
||||
'translate any entity',
|
||||
'access administration pages',
|
||||
'administer blocks',
|
||||
'administer block_content fields',
|
||||
'access block library',
|
||||
'create basic block content',
|
||||
'edit any basic block content',
|
||||
'delete any basic block content',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNewEntityValues($langcode) {
|
||||
return ['info' => $this->randomMachineName()] + parent::getNewEntityValues($langcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an edit array containing the values to be posted.
|
||||
*/
|
||||
protected function getEditValues($values, $langcode, $new = FALSE) {
|
||||
$edit = parent::getEditValues($values, $langcode, $new);
|
||||
foreach ($edit as $property => $value) {
|
||||
if ($property == 'info') {
|
||||
$edit['info[0][value]'] = $value;
|
||||
unset($edit[$property]);
|
||||
}
|
||||
}
|
||||
return $edit;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doTestBasicTranslation() {
|
||||
parent::doTestBasicTranslation();
|
||||
|
||||
// Ensure that a block translation can be created using the same description
|
||||
// as in the original language.
|
||||
$default_langcode = $this->langcodes[0];
|
||||
$values = $this->getNewEntityValues($default_langcode);
|
||||
$storage = \Drupal::entityTypeManager()->getStorage($this->entityTypeId);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
|
||||
$entity = $storage->create(['type' => 'basic'] + $values);
|
||||
$entity->save();
|
||||
$entity->addTranslation('it', $values);
|
||||
|
||||
try {
|
||||
$entity->save();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->fail('Blocks can have translations with the same "info" value.');
|
||||
}
|
||||
|
||||
// Check that the translate operation link is shown.
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->assertSession()->linkByHrefExists('admin/content/block/' . $entity->id() . '/translations');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function doTestTranslationEdit() {
|
||||
$storage = $this->container->get('entity_type.manager')
|
||||
->getStorage($this->entityTypeId);
|
||||
$storage->resetCache([$this->entityId]);
|
||||
$entity = $storage->load($this->entityId);
|
||||
$languages = $this->container->get('language_manager')->getLanguages();
|
||||
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
// We only want to test the title for non-english translations.
|
||||
if ($langcode != 'en') {
|
||||
$options = ['language' => $languages[$langcode]];
|
||||
$url = $entity->toUrl('edit-form', $options);
|
||||
$this->drupalGet($url);
|
||||
$this->assertSession()->pageTextContains("Edit {$entity->bundle()} {$entity->getTranslation($langcode)->label()} [{$languages[$langcode]->getName()} translation]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
266
tests/src/Functional/BlockContentTypeTest.php
Normal file
266
tests/src/Functional/BlockContentTypeTest.php
Normal file
@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Component\Utility\Html;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* Ensures that block type functions work correctly.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class BlockContentTypeTest extends BlockContentTestBase {
|
||||
|
||||
use AssertBreadcrumbTrait;
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['field_ui'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* Permissions to grant admin user.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $permissions = [
|
||||
'administer block content',
|
||||
'administer blocks',
|
||||
'administer block_content fields',
|
||||
'administer block types',
|
||||
'administer block content',
|
||||
'access block library',
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether or not to create an initial block type.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $autoCreateBasicBlockType = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the order of the block content types on the add page.
|
||||
*/
|
||||
public function testBlockContentAddPageOrder(): void {
|
||||
$this->createBlockContentType(['id' => 'bundle_1', 'label' => 'Bundle 1']);
|
||||
$this->createBlockContentType(['id' => 'bundle_2', 'label' => 'Aaa Bundle 2']);
|
||||
$this->drupalLogin($this->adminUser);
|
||||
$this->drupalGet('block/add');
|
||||
$this->assertSession()->pageTextMatches('/Aaa Bundle 2(.*)Bundle 1/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creating a block type programmatically and via a form.
|
||||
*/
|
||||
public function testBlockContentTypeCreation(): void {
|
||||
// Log in a test user.
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Test the page with no block-types.
|
||||
$this->drupalGet('block/add');
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
$this->assertSession()->pageTextContains('You have not created any block types yet');
|
||||
$this->clickLink('block type creation page');
|
||||
|
||||
// Create a block type via the user interface.
|
||||
$edit = [
|
||||
'id' => 'foo',
|
||||
'label' => 'title for foo',
|
||||
];
|
||||
$this->submitForm($edit, 'Save and manage fields');
|
||||
|
||||
// Asserts that form submit redirects to the expected manage fields page.
|
||||
$this->assertSession()->addressEquals('admin/structure/block-content/manage/' . $edit['id'] . '/fields');
|
||||
|
||||
$block_type = BlockContentType::load('foo');
|
||||
$this->assertInstanceOf(BlockContentType::class, $block_type);
|
||||
|
||||
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'foo');
|
||||
$this->assertTrue(isset($field_definitions['body']), 'Body field created when using the UI to create block content types.');
|
||||
|
||||
// Check that the block type was created in site default language.
|
||||
$default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
|
||||
$this->assertEquals($block_type->language()->getId(), $default_langcode);
|
||||
|
||||
// Create block types programmatically.
|
||||
$this->createBlockContentType('basic', TRUE);
|
||||
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'basic');
|
||||
$this->assertTrue(isset($field_definitions['body']), "Body field for 'basic' block type created when using the testing API to create block content types.");
|
||||
|
||||
$this->createBlockContentType('other');
|
||||
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'other');
|
||||
$this->assertFalse(isset($field_definitions['body']), "Body field for 'other' block type not created when using the testing API to create block content types.");
|
||||
|
||||
$block_type = BlockContentType::load('other');
|
||||
$this->assertInstanceOf(BlockContentType::class, $block_type);
|
||||
|
||||
$this->drupalGet('block/add/' . $block_type->id());
|
||||
$this->assertSession()->statusCodeEquals(200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests editing a block type using the UI.
|
||||
*/
|
||||
public function testBlockContentTypeEditing(): void {
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
// Now create an initial block-type.
|
||||
$this->createBlockContentType('basic', TRUE);
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// We need two block types to prevent /block/add redirecting.
|
||||
$this->createBlockContentType('other');
|
||||
|
||||
$field_definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('block_content', 'other');
|
||||
$this->assertFalse(isset($field_definitions['body']), 'Body field was not created when using the API to create block content types.');
|
||||
|
||||
// Verify that title and body fields are displayed.
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->assertSession()->pageTextContains('Block description');
|
||||
$this->assertNotEmpty($this->cssSelect('#edit-body-0-value'), 'Body field was found.');
|
||||
|
||||
// Change the block type name.
|
||||
$edit = [
|
||||
'label' => 'Bar',
|
||||
];
|
||||
$this->drupalGet('admin/structure/block-content/manage/basic');
|
||||
$this->assertSession()->titleEquals('Edit basic block type | Drupal');
|
||||
$this->submitForm($edit, 'Save');
|
||||
$front_page_path = Url::fromRoute('<front>')->toString();
|
||||
$this->assertBreadcrumb('admin/structure/block-content/manage/basic/fields', [
|
||||
$front_page_path => 'Home',
|
||||
'admin/structure/block-content' => 'Block types',
|
||||
'admin/structure/block-content/manage/basic' => 'Edit Bar',
|
||||
]);
|
||||
\Drupal::service('entity_field.manager')->clearCachedFieldDefinitions();
|
||||
|
||||
$this->drupalGet('block/add');
|
||||
$this->assertSession()->pageTextContains('Bar');
|
||||
$this->clickLink('Bar');
|
||||
// Verify that the original machine name was used in the URL.
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('block_content.add_form', ['block_content_type' => 'basic']));
|
||||
|
||||
// Remove the body field.
|
||||
$this->drupalGet('admin/structure/block-content/manage/basic/fields/block_content.basic.body/delete');
|
||||
$this->submitForm([], 'Delete');
|
||||
// Resave the settings for this type.
|
||||
$this->drupalGet('admin/structure/block-content/manage/basic');
|
||||
$this->submitForm([], 'Save');
|
||||
// Check that the body field doesn't exist.
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->assertEmpty($this->cssSelect('#edit-body-0-value'), 'Body field was not found.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests deleting a block type that still has content.
|
||||
*/
|
||||
public function testBlockContentTypeDeletion(): void {
|
||||
// Now create an initial block-type.
|
||||
$this->createBlockContentType('basic', TRUE);
|
||||
|
||||
// Create a block type programmatically.
|
||||
$type = $this->createBlockContentType('foo');
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
// Add a new block of this type.
|
||||
$block = $this->createBlockContent(FALSE, 'foo');
|
||||
// Attempt to delete the block type, which should not be allowed.
|
||||
$this->drupalGet('admin/structure/block-content/manage/' . $type->id() . '/delete');
|
||||
$this->assertSession()->pageTextContains($type->label() . ' is used by 1 content block on your site. You can not remove this block type until you have removed all of the ' . $type->label() . ' blocks.');
|
||||
$this->assertSession()->pageTextNotContains('This action cannot be undone.');
|
||||
|
||||
// Delete the block.
|
||||
$block->delete();
|
||||
// Attempt to delete the block type, which should now be allowed.
|
||||
$this->drupalGet('admin/structure/block-content/manage/' . $type->id() . '/delete');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete the block type ' . $type->id() . '?');
|
||||
$this->assertSession()->pageTextContains('This action cannot be undone.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that redirects work as expected when multiple block types exist.
|
||||
*/
|
||||
public function testsBlockContentAddTypes(): void {
|
||||
// Now create an initial block-type.
|
||||
$this->createBlockContentType('basic', TRUE);
|
||||
|
||||
$this->drupalLogin($this->adminUser);
|
||||
// Create two block types programmatically.
|
||||
$this->createBlockContentType('foo');
|
||||
$this->createBlockContentType('bar');
|
||||
|
||||
// Get the content block storage.
|
||||
$storage = $this->container
|
||||
->get('entity_type.manager')
|
||||
->getStorage('block_content');
|
||||
|
||||
// Install all themes.
|
||||
$themes = ['olivero', 'stark', 'claro'];
|
||||
\Drupal::service('theme_installer')->install($themes);
|
||||
$theme_settings = $this->config('system.theme');
|
||||
foreach ($themes as $default_theme) {
|
||||
// Change the default theme.
|
||||
$theme_settings->set('default', $default_theme)->save();
|
||||
$this->drupalPlaceBlock('local_actions_block');
|
||||
|
||||
// For each installed theme, go to its block page and test the redirects.
|
||||
foreach ($themes as $theme) {
|
||||
// Test that adding a block from the 'place blocks' form sends you to the
|
||||
// block configure form.
|
||||
$path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
|
||||
$this->drupalGet($path);
|
||||
$this->clickLink('Place block');
|
||||
$this->clickLink('Add content block');
|
||||
$this->clickLink('foo');
|
||||
// Create a new block.
|
||||
$edit = ['info[0][value]' => $this->randomMachineName(8)];
|
||||
$this->submitForm($edit, 'Save and configure');
|
||||
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
|
||||
if (!empty($blocks)) {
|
||||
$block = reset($blocks);
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('block.admin_add', ['plugin_id' => 'block_content:' . $block->uuid(), 'theme' => $theme]));
|
||||
$this->submitForm(['region' => 'content'], 'Save block');
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('block.admin_display_theme', ['theme' => $theme], ['query' => ['block-placement' => $theme . '-' . Html::getClass($edit['info[0][value]'])]]));
|
||||
}
|
||||
else {
|
||||
$this->fail('Could not load created block.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that adding a block from the 'content blocks list' doesn't send you
|
||||
// to the block configure form.
|
||||
$this->drupalGet('admin/content/block');
|
||||
$this->clickLink('Add content block');
|
||||
$this->clickLink('foo');
|
||||
$edit = ['info[0][value]' => $this->randomMachineName(8)];
|
||||
$this->submitForm($edit, 'Save');
|
||||
$blocks = $storage->loadByProperties(['info' => $edit['info[0][value]']]);
|
||||
if (!empty($blocks)) {
|
||||
$this->assertSession()->addressEquals(Url::fromRoute('entity.block_content.collection'));
|
||||
}
|
||||
else {
|
||||
$this->fail('Could not load created block.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
tests/src/Functional/GenericTest.php
Normal file
14
tests/src/Functional/GenericTest.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
|
||||
|
||||
/**
|
||||
* Generic module test for block_content.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class GenericTest extends GenericModuleTestBase {}
|
||||
98
tests/src/Functional/PageEditTest.php
Normal file
98
tests/src/Functional/PageEditTest.php
Normal file
@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\Tests\system\Functional\Menu\AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* Create a block and test block edit functionality.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class PageEditTest extends BlockContentTestBase {
|
||||
|
||||
use AssertBreadcrumbTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void {
|
||||
parent::setUp();
|
||||
|
||||
$this->drupalPlaceBlock('page_title_block');
|
||||
$this->drupalPlaceBlock('system_breadcrumb_block');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks block edit functionality.
|
||||
*/
|
||||
public function testPageEdit(): void {
|
||||
$this->drupalLogin($this->adminUser);
|
||||
|
||||
$title_key = 'info[0][value]';
|
||||
$body_key = 'body[0][value]';
|
||||
// Create block to edit.
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $this->randomMachineName(8);
|
||||
$edit[$body_key] = $this->randomMachineName(16);
|
||||
$this->drupalGet('block/add/basic');
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Check that the block exists in the database.
|
||||
$blocks = \Drupal::entityQuery('block_content')
|
||||
->accessCheck(FALSE)
|
||||
->condition('info', $edit['info[0][value]'])
|
||||
->execute();
|
||||
$block = BlockContent::load(reset($blocks));
|
||||
$this->assertNotEmpty($block, 'Content block found in database.');
|
||||
|
||||
// Load the edit page.
|
||||
$this->drupalGet('admin/content/block/' . $block->id());
|
||||
$this->assertSession()->fieldValueEquals($title_key, $edit[$title_key]);
|
||||
$this->assertSession()->fieldValueEquals($body_key, $edit[$body_key]);
|
||||
|
||||
// Edit the content of the block.
|
||||
$edit = [];
|
||||
$edit[$title_key] = $this->randomMachineName(8);
|
||||
$edit[$body_key] = $this->randomMachineName(16);
|
||||
// Stay on the current page, without reloading.
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Edit the same block, creating a new revision.
|
||||
$this->drupalGet("admin/content/block/" . $block->id());
|
||||
$edit = [];
|
||||
$edit['info[0][value]'] = $this->randomMachineName(8);
|
||||
$edit[$body_key] = $this->randomMachineName(16);
|
||||
$edit['revision'] = TRUE;
|
||||
$this->submitForm($edit, 'Save');
|
||||
|
||||
// Ensure that the block revision has been created.
|
||||
\Drupal::entityTypeManager()->getStorage('block_content')->resetCache([$block->id()]);
|
||||
$revised_block = BlockContent::load($block->id());
|
||||
$this->assertNotSame($block->getRevisionId(), $revised_block->getRevisionId(), 'A new revision has been created.');
|
||||
|
||||
// Test deleting the block.
|
||||
$this->drupalGet("admin/content/block/" . $revised_block->id());
|
||||
$this->clickLink('Delete');
|
||||
$this->assertSession()->pageTextContains('Are you sure you want to delete the content block ' . $revised_block->label() . '?');
|
||||
|
||||
// Test breadcrumb.
|
||||
$trail = [
|
||||
'' => 'Home',
|
||||
'admin/content/block' => 'Content blocks',
|
||||
'admin/content/block/' . $revised_block->id() => $revised_block->label(),
|
||||
];
|
||||
$this->assertBreadcrumb(
|
||||
'admin/content/block/' . $revised_block->id() . '/delete', $trail
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
31
tests/src/Functional/Rest/BlockContentJsonAnonTest.php
Normal file
31
tests/src/Functional/Rest/BlockContentJsonAnonTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockContentJsonAnonTest extends BlockContentResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
41
tests/src/Functional/Rest/BlockContentJsonBasicAuthTest.php
Normal file
41
tests/src/Functional/Rest/BlockContentJsonBasicAuthTest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockContentJsonBasicAuthTest extends BlockContentResourceTestBase {
|
||||
|
||||
use BasicAuthResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['basic_auth'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'basic_auth';
|
||||
|
||||
}
|
||||
36
tests/src/Functional/Rest/BlockContentJsonCookieTest.php
Normal file
36
tests/src/Functional/Rest/BlockContentJsonCookieTest.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockContentJsonCookieTest extends BlockContentResourceTestBase {
|
||||
|
||||
use CookieResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $auth = 'cookie';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
239
tests/src/Functional/Rest/BlockContentResourceTestBase.php
Normal file
239
tests/src/Functional/Rest/BlockContentResourceTestBase.php
Normal file
@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional\Rest;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContent;
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Tests\rest\Functional\EntityResource\EntityResourceTestBase;
|
||||
|
||||
/**
|
||||
* ResourceTestBase for BlockContent entity.
|
||||
*/
|
||||
abstract class BlockContentResourceTestBase extends EntityResourceTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $modules = ['block_content', 'content_translation'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $entityTypeId = 'block_content';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $patchProtectedFieldNames = [
|
||||
'changed' => NULL,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var \Drupal\block_content\BlockContentInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUpAuthorization($method) {
|
||||
switch ($method) {
|
||||
case 'GET':
|
||||
case 'PATCH':
|
||||
$this->grantPermissionsToTestedRole(['access block library', 'edit any basic block content']);
|
||||
break;
|
||||
|
||||
case 'POST':
|
||||
$this->grantPermissionsToTestedRole(['access block library', 'create basic block content']);
|
||||
break;
|
||||
|
||||
case 'DELETE':
|
||||
$this->grantPermissionsToTestedRole(['delete any basic block content']);
|
||||
break;
|
||||
|
||||
default:
|
||||
$this->grantPermissionsToTestedRole(['administer block content']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function createEntity() {
|
||||
if (!BlockContentType::load('basic')) {
|
||||
$block_content_type = BlockContentType::create([
|
||||
'id' => 'basic',
|
||||
'label' => 'basic',
|
||||
'revision' => TRUE,
|
||||
]);
|
||||
$block_content_type->save();
|
||||
block_content_add_body_field($block_content_type->id());
|
||||
}
|
||||
|
||||
// Create a "Llama" content block.
|
||||
$block_content = BlockContent::create([
|
||||
'info' => 'Llama',
|
||||
'type' => 'basic',
|
||||
'body' => [
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
],
|
||||
])
|
||||
->setUnpublished();
|
||||
$block_content->save();
|
||||
return $block_content;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedNormalizedEntity() {
|
||||
return [
|
||||
'id' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'uuid' => [
|
||||
[
|
||||
'value' => $this->entity->uuid(),
|
||||
],
|
||||
],
|
||||
'langcode' => [
|
||||
[
|
||||
'value' => 'en',
|
||||
],
|
||||
],
|
||||
'reusable' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'basic',
|
||||
'target_type' => 'block_content_type',
|
||||
'target_uuid' => BlockContentType::load('basic')->uuid(),
|
||||
],
|
||||
],
|
||||
'info' => [
|
||||
[
|
||||
'value' => 'Llama',
|
||||
],
|
||||
],
|
||||
'revision_log' => [],
|
||||
'changed' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getChangedTime())
|
||||
->setTimezone(new \DateTimeZone('UTC'))
|
||||
->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'revision_id' => [
|
||||
[
|
||||
'value' => 1,
|
||||
],
|
||||
],
|
||||
'revision_created' => [
|
||||
[
|
||||
'value' => (new \DateTime())->setTimestamp((int) $this->entity->getRevisionCreationTime())
|
||||
->setTimezone(new \DateTimeZone('UTC'))
|
||||
->format(\DateTime::RFC3339),
|
||||
'format' => \DateTime::RFC3339,
|
||||
],
|
||||
],
|
||||
'revision_user' => [],
|
||||
'revision_translation_affected' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'default_langcode' => [
|
||||
[
|
||||
'value' => TRUE,
|
||||
],
|
||||
],
|
||||
'body' => [
|
||||
[
|
||||
'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
|
||||
'format' => 'plain_text',
|
||||
'summary' => NULL,
|
||||
'processed' => "<p>The name "llama" was adopted by European settlers from native Peruvians.</p>\n",
|
||||
],
|
||||
],
|
||||
'status' => [
|
||||
[
|
||||
'value' => FALSE,
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getNormalizedPostEntity() {
|
||||
return [
|
||||
'type' => [
|
||||
[
|
||||
'target_id' => 'basic',
|
||||
],
|
||||
],
|
||||
'info' => [
|
||||
[
|
||||
'value' => 'Drama llama',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedAccessMessage($method) {
|
||||
if (!$this->resourceConfigStorage->load(static::$resourceConfigId)) {
|
||||
return match ($method) {
|
||||
'GET', 'PATCH' => "The 'edit any basic block content' permission is required.",
|
||||
'POST' => "The following permissions are required: 'create basic block content' AND 'access block library'.",
|
||||
'DELETE' => "The 'delete any basic block content' permission is required.",
|
||||
default => parent::getExpectedUnauthorizedAccessMessage($method),
|
||||
};
|
||||
}
|
||||
return match ($method) {
|
||||
'GET' => "The 'access block library' permission is required.",
|
||||
'PATCH' => "The 'edit any basic block content' permission is required.",
|
||||
'POST' => "The following permissions are required: 'create basic block content' AND 'access block library'.",
|
||||
'DELETE' => "The 'delete any basic block content' permission is required.",
|
||||
default => parent::getExpectedUnauthorizedAccessMessage($method),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedUnauthorizedEntityAccessCacheability($is_authenticated) {
|
||||
// @see \Drupal\block_content\BlockContentAccessControlHandler()
|
||||
return parent::getExpectedUnauthorizedEntityAccessCacheability($is_authenticated)
|
||||
->addCacheTags(['block_content:1']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheTags() {
|
||||
return Cache::mergeTags(parent::getExpectedCacheTags(), ['config:filter.format.plain_text']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExpectedCacheContexts() {
|
||||
return Cache::mergeContexts(['url.site'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
|
||||
}
|
||||
|
||||
}
|
||||
31
tests/src/Functional/Rest/BlockContentTypeJsonAnonTest.php
Normal file
31
tests/src/Functional/Rest/BlockContentTypeJsonAnonTest.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Drupal\Tests\block_content\Functional\Rest;
|
||||
|
||||
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* @group rest
|
||||
*/
|
||||
class BlockContentTypeJsonAnonTest extends BlockContentTypeResourceTestBase {
|
||||
|
||||
use AnonResourceTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $format = 'json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected static $mimeType = 'application/json';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $defaultTheme = 'stark';
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user