Initial Drupal 11 with DDEV setup
This commit is contained in:
@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\block_content\Event\BlockContentGetDependencyEvent;
|
||||
use Drupal\Core\Access\AccessResult;
|
||||
use Drupal\Core\Access\AccessResultInterface;
|
||||
use Drupal\Core\Access\DependentAccessInterface;
|
||||
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',
|
||||
'administer block content',
|
||||
], 'OR');
|
||||
}
|
||||
|
||||
}
|
||||
31
web/core/modules/block_content/src/BlockContentEvents.php
Normal file
31
web/core/modules/block_content/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
web/core/modules/block_content/src/BlockContentForm.php
Normal file
134
web/core/modules/block_content/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
web/core/modules/block_content/src/BlockContentInterface.php
Normal file
84
web/core/modules/block_content/src/BlockContentInterface.php
Normal file
@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content;
|
||||
|
||||
use Drupal\Core\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();
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
<?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() {
|
||||
$query = $this->getStorage()->getQuery()
|
||||
->accessCheck(TRUE)
|
||||
->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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
web/core/modules/block_content/src/BlockContentTypeForm.php
Normal file
128
web/core/modules/block_content/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'));
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
|
||||
}
|
||||
@ -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');
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
web/core/modules/block_content/src/BlockContentViewsData.php
Normal file
42
web/core/modules/block_content/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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Controller;
|
||||
|
||||
use Drupal\block_content\BlockContentTypeInterface;
|
||||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
/**
|
||||
* Controller routines for custom block routes.
|
||||
*/
|
||||
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);
|
||||
$query = $request->query->all();
|
||||
return $this->redirect('block_content.add_form', ['block_content_type' => $type->id()], ['query' => $query]);
|
||||
}
|
||||
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()]);
|
||||
}
|
||||
|
||||
}
|
||||
285
web/core/modules/block_content/src/Entity/BlockContent.php
Normal file
285
web/core/modules/block_content/src/Entity/BlockContent.php
Normal file
@ -0,0 +1,285 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Entity;
|
||||
|
||||
use Drupal\block_content\BlockContentAccessControlHandler;
|
||||
use Drupal\block_content\BlockContentForm;
|
||||
use Drupal\block_content\BlockContentListBuilder;
|
||||
use Drupal\block_content\BlockContentStorageSchema;
|
||||
use Drupal\block_content\BlockContentTranslationHandler;
|
||||
use Drupal\block_content\BlockContentViewBuilder;
|
||||
use Drupal\block_content\BlockContentViewsData;
|
||||
use Drupal\block_content\Form\BlockContentDeleteForm;
|
||||
use Drupal\Core\Access\RefinableDependentAccessTrait;
|
||||
use Drupal\Core\Entity\Attribute\ContentEntityType;
|
||||
use Drupal\Core\Entity\Routing\RevisionHtmlRouteProvider;
|
||||
use Drupal\Core\Entity\Form\RevisionRevertForm;
|
||||
use Drupal\Core\Entity\Form\RevisionDeleteForm;
|
||||
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#[ContentEntityType(
|
||||
id: 'block_content',
|
||||
label: new TranslatableMarkup('Content block'),
|
||||
label_collection: new TranslatableMarkup('Content blocks'),
|
||||
label_singular: new TranslatableMarkup('content block'),
|
||||
label_plural: new TranslatableMarkup('content blocks'),
|
||||
render_cache: FALSE,
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'revision' => 'revision_id',
|
||||
'bundle' => 'type',
|
||||
'label' => 'info',
|
||||
'langcode' => 'langcode',
|
||||
'uuid' => 'uuid',
|
||||
'published' => 'status',
|
||||
],
|
||||
handlers: [
|
||||
'storage' => SqlContentEntityStorage::class,
|
||||
'storage_schema' => BlockContentStorageSchema::class,
|
||||
'access' => BlockContentAccessControlHandler::class,
|
||||
'list_builder' => BlockContentListBuilder::class,
|
||||
'view_builder' => BlockContentViewBuilder::class,
|
||||
'views_data' => BlockContentViewsData::class,
|
||||
'form' => [
|
||||
'add' => BlockContentForm::class,
|
||||
'edit' => BlockContentForm::class,
|
||||
'delete' => BlockContentDeleteForm::class,
|
||||
'default' => BlockContentForm::class,
|
||||
'revision-delete' => RevisionDeleteForm::class,
|
||||
'revision-revert' => RevisionRevertForm::class,
|
||||
],
|
||||
'route_provider' => ['revision' => RevisionHtmlRouteProvider::class],
|
||||
'translation' => BlockContentTranslationHandler::class,
|
||||
],
|
||||
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',
|
||||
],
|
||||
admin_permission: 'administer block content',
|
||||
collection_permission: 'access block library',
|
||||
bundle_entity_type: 'block_content_type',
|
||||
bundle_label: new TranslatableMarkup('Block type'),
|
||||
base_table: 'block_content',
|
||||
data_table: 'block_content_field_data',
|
||||
revision_table: 'block_content_revision',
|
||||
revision_data_table: 'block_content_field_revision',
|
||||
translatable: TRUE,
|
||||
show_revision_ui: TRUE,
|
||||
label_count: [
|
||||
'singular' => '@count content block',
|
||||
'plural' => '@count content blocks',
|
||||
],
|
||||
field_ui_base_route: 'entity.block_content_type.edit_form',
|
||||
revision_metadata_keys: [
|
||||
'revision_user' => 'revision_user',
|
||||
'revision_created' => 'revision_created',
|
||||
'revision_log_message' => 'revision_log',
|
||||
],
|
||||
)]
|
||||
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() || $this->getOriginal()?->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() && $this->getOriginal() && 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->getOriginal()->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();
|
||||
}
|
||||
|
||||
}
|
||||
107
web/core/modules/block_content/src/Entity/BlockContentType.php
Normal file
107
web/core/modules/block_content/src/Entity/BlockContentType.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Entity;
|
||||
|
||||
use Drupal\block_content\BlockContentTypeForm;
|
||||
use Drupal\block_content\BlockContentTypeListBuilder;
|
||||
use Drupal\block_content\BlockTypeAccessControlHandler;
|
||||
use Drupal\block_content\Form\BlockContentTypeDeleteForm;
|
||||
use Drupal\Core\Entity\Attribute\ConfigEntityType;
|
||||
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;
|
||||
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityBundleBase;
|
||||
use Drupal\block_content\BlockContentTypeInterface;
|
||||
use Drupal\user\Entity\EntityPermissionsRouteProvider;
|
||||
|
||||
/**
|
||||
* Defines the block type entity.
|
||||
*/
|
||||
#[ConfigEntityType(
|
||||
id: 'block_content_type',
|
||||
label: new TranslatableMarkup('Block type'),
|
||||
label_collection: new TranslatableMarkup('Block types'),
|
||||
label_singular: new TranslatableMarkup('block type'),
|
||||
label_plural: new TranslatableMarkup('block types'),
|
||||
config_prefix: 'type',
|
||||
entity_keys: [
|
||||
'id' => 'id',
|
||||
'label' => 'label',
|
||||
],
|
||||
handlers: [
|
||||
'access' => BlockTypeAccessControlHandler::class,
|
||||
'form' => [
|
||||
'default' => BlockContentTypeForm::class,
|
||||
'add' => BlockContentTypeForm::class,
|
||||
'edit' => BlockContentTypeForm::class,
|
||||
'delete' => BlockContentTypeDeleteForm::class,
|
||||
],
|
||||
'route_provider' => [
|
||||
'html' => AdminHtmlRouteProvider::class,
|
||||
'permissions' => EntityPermissionsRouteProvider::class,
|
||||
],
|
||||
'list_builder' => BlockContentTypeListBuilder::class,
|
||||
],
|
||||
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',
|
||||
],
|
||||
admin_permission: 'administer block types',
|
||||
bundle_of: 'block_content',
|
||||
label_count: [
|
||||
'singular' => '@count block type',
|
||||
'plural' => '@count block types',
|
||||
],
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
163
web/core/modules/block_content/src/Hook/BlockContentHooks.php
Normal file
163
web/core/modules/block_content/src/Hook/BlockContentHooks.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Hook;
|
||||
|
||||
use Drupal\block\BlockInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\block_content\BlockContentInterface;
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\Database\Query\AlterableInterface;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Hook\Attribute\Hook;
|
||||
|
||||
/**
|
||||
* Hook implementations for block_content.
|
||||
*/
|
||||
class BlockContentHooks {
|
||||
|
||||
use StringTranslationTrait;
|
||||
|
||||
/**
|
||||
* Implements hook_help().
|
||||
*/
|
||||
#[Hook('help')]
|
||||
public function help($route_name, RouteMatchInterface $route_match): ?string {
|
||||
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>' . $this->t('About') . '</h2>';
|
||||
$output .= '<p>' . $this->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>' . $this->t('Uses') . '</h2>';
|
||||
$output .= '<dl>';
|
||||
$output .= '<dt>' . $this->t('Creating and managing block types') . '</dt>';
|
||||
$output .= '<dd>' . $this->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>' . $this->t('Creating content blocks') . '</dt>';
|
||||
$output .= '<dd>' . $this->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;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme().
|
||||
*/
|
||||
#[Hook('theme')]
|
||||
public function theme($existing, $type, $theme, $path) : array {
|
||||
return [
|
||||
'block_content_add_list' => [
|
||||
'variables' => [
|
||||
'content' => NULL,
|
||||
],
|
||||
'file' => 'block_content.pages.inc',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_type_alter().
|
||||
*/
|
||||
#[Hook('entity_type_alter')]
|
||||
public function entityTypeAlter(array &$entity_types) : void {
|
||||
/** @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
#[Hook('query_entity_reference_alter')]
|
||||
public function queryEntityReferenceAlter(AlterableInterface $query): void {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_theme_suggestions_HOOK_alter() for block templates.
|
||||
*/
|
||||
#[Hook('theme_suggestions_block_alter')]
|
||||
public function themeSuggestionsBlockAlter(array &$suggestions, array $variables): void {
|
||||
$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']['content']['#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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_entity_operation().
|
||||
*/
|
||||
#[Hook('entity_operation')]
|
||||
public function entityOperation(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' => $this->t('Edit block'),
|
||||
'url' => $custom_block->toUrl('edit-form')->setOptions([]),
|
||||
'weight' => 50,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $operations;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,222 @@
|
||||
<?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',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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),
|
||||
];
|
||||
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'),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createPlaceholder(): bool {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\block_content\Plugin\Menu\LocalAction;
|
||||
|
||||
use Drupal\Core\Menu\LocalActionDefault;
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Routing\RouteProviderInterface;
|
||||
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;
|
||||
}
|
||||
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): void {
|
||||
// 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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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';
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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\migrate_drupal\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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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 [];
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
web/core/modules/block_content/src/Routing/RouteSubscriber.php
Normal file
237
web/core/modules/block_content/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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user