Update to Drupal 8.0.0 beta 14. For more information, see https://drupal.org/node/2544542

This commit is contained in:
Pantheon Automation 2015-08-27 12:03:05 -07:00 committed by Greg Anderson
parent 3b2511d96d
commit 81ccda77eb
2155 changed files with 54307 additions and 46870 deletions

View file

@ -0,0 +1,146 @@
<?php
/**
* @file
* Contains install and update functions for Block.
*/
use Drupal\Core\Cache\Cache;
/**
* Implements hook_install().
*/
function block_install() {
// Because the Block module upon installation unconditionally overrides all
// HTML output by selecting a different page display variant, we must
// invalidate all cached HTML output.
Cache::invalidateTags(['rendered']);
}
/**
* @addtogroup updates-8.0.0-beta
* @{
*/
/**
* Update block visibility context mapping.
*/
function block_update_8001() {
// This update function updates blocks for the change from
// https://www.drupal.org/node/2354889.
// Core visibility context plugins are updated automatically; blocks with
// unknown plugins are disabled and their previous visibility settings are
// saved in key value storage; see change record
// https://www.drupal.org/node/2527840 for more explanation.
// These are all the contexts that Drupal core provides.
$context_service_id_map = [
'node.node' => '@node.node_route_context:node',
'user.current_user' => '@user.current_user_context:current_user',
];
foreach (array_keys(\Drupal::languageManager()->getDefinedLanguageTypesInfo()) as $language_type_id) {
$context_service_id_map['language.' . $language_type_id] = '@language.current_language_context:' . $language_type_id;
}
// Contributed modules should leverage hook_update_dependencies() in order to
// be executed before block_update_8002(), so they can update their context
// mappings, if wanted.
$config_factory = \Drupal::configFactory();
$backup_values = $update_backup = [];
foreach ($config_factory->listAll('block.block.') as $block_config_name) {
$block = $config_factory->getEditable($block_config_name);
if ($visibility = $block->get('visibility')) {
foreach ($visibility as $condition_plugin_id => &$condition) {
foreach ($condition['context_mapping'] as $key => $context) {
if (!isset($context_service_id_map[$context])) {
// Remove the visibility condition for unknown context mapping
// entries, so the update process itself runs through and users can
// fix their block placements manually OR alternatively contributed
// modules can run their own update functions to update mappings
// that they provide.
$backup_values[$context][] = $condition_plugin_id;
unset($visibility[$condition_plugin_id]);
continue;
}
// Replace the context ID based on the defined mapping.
$condition['context_mapping'][$key] = $context_service_id_map[$context];
}
}
$block->set('visibility', $visibility);
if ($backup_values) {
// We not only store the missing context mappings but also the previous
// block status, in order to allow contributed and custom modules to do
// their own updates.
$update_backup[$block->get('id')] = [
'missing_context_ids' => $backup_values,
'status' => $block->get('status')
];
}
}
// Mark the resulting configuration as trusted data. This avoids issues with
// future schema changes.
$block->save(TRUE);
}
if ($update_backup) {
\Drupal::keyValue('update_backup')->set('block_update_8001', $update_backup);
}
return t('Block context IDs updated.');
}
/**
* Disable all blocks with missing context IDs in block_update_8001().
*/
function block_update_8002() {
$block_update_8001 = \Drupal::keyValue('update_backup')->get('block_update_8001', []);
$block_ids = array_keys($block_update_8001);
$config_factory = \Drupal::configFactory();
/** @var \Drupal\Core\Config\Config[] $blocks */
$blocks = [];
foreach ($block_ids as $block_id) {
$blocks[$block_id] = $block = $config_factory->getEditable('block.block.' . $block_id);
// This block will have an invalid context mapping service and must be
// disabled in order to prevent information disclosure.
// Disable currently enabled blocks.
if ($block_update_8001[$block_id]['status']) {
$block->set('status', FALSE);
$block->save(TRUE);
}
}
// Provides a list of plugin labels, keyed by plugin ID.
$condition_plugin_id_label_map = array_column(\Drupal::service('plugin.manager.condition')->getDefinitions(), 'label', 'id');
// Override with the UI labels we are aware of. Sadly they are not machine
// accessible, see
// \Drupal\node\Plugin\Condition\NodeType::buildConfigurationForm().
$condition_plugin_id_label_map['node_type'] = t('Content types');
$condition_plugin_id_label_map['request_path'] = t('Pages');
$condition_plugin_id_label_map['user_role'] = t('Roles');
if (count($block_ids) > 0) {
$message = t('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:');
$message .= '<ul>';
foreach ($blocks as $disabled_block_id => $disabled_block) {
$message .= '<li>' . t('@label (Visibility: @plugin_ids)', array(
'@label' => $disabled_block->get('settings.label'),
'@plugin_ids' => implode(', ', array_intersect_key($condition_plugin_id_label_map, array_flip(array_keys($block_update_8001[$disabled_block_id]['missing_context_ids']))))
)) . '</li>';
}
$message .= '</ul>';
return $message;
}
}
/**
* @} End of "addtogroup updates-8.0.0-beta".
*/

View file

@ -7,7 +7,6 @@
use Drupal\block\BlockInterface;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\language\ConfigurableLanguageInterface;
@ -58,9 +57,6 @@ function block_theme() {
'block' => array(
'render element' => 'elements',
),
'block_list' => array(
'render element' => 'form',
),
);
}
@ -85,45 +81,6 @@ function block_page_top(array &$page_top) {
}
}
/**
* Returns an array of block class instances by theme.
*
* @param $theme
* The theme to rehash blocks for. If not provided, defaults to the currently
* used theme.
*
* @return
* Blocks currently exported by modules.
*/
function _block_rehash($theme = NULL) {
$theme = $theme ? $theme : \Drupal::config('system.theme')->get('default');
$regions = system_region_list($theme);
$blocks = entity_load_multiple_by_properties('block', array('theme' => $theme));
foreach ($blocks as $block_id => $block) {
// Remove any invalid block from the list.
// @todo Remove this check as part of https://www.drupal.org/node/1776830.
if (!$block->getPlugin()) {
unset($blocks[$block_id]);
continue;
}
$region = $block->getRegion();
$status = $block->status();
// Disable blocks in invalid regions.
if (!empty($region) && $region != BlockInterface::BLOCK_REGION_NONE && !isset($regions[$region]) && $status) {
drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block_id, '%region' => $region)), 'warning');
// Disabled blocks are moved into the BlockInterface::BLOCK_REGION_NONE
// later so no need to move the block to another region.
$block->disable()->save();
}
// Set region to none if not enabled.
if (!$status && $region != BlockInterface::BLOCK_REGION_NONE) {
$block->setRegion(BlockInterface::BLOCK_REGION_NONE);
$block->save();
}
}
return $blocks;
}
/**
* Initializes blocks for installed themes.
*
@ -177,9 +134,26 @@ function block_theme_initialize($theme) {
* Implements hook_rebuild().
*/
function block_rebuild() {
foreach (\Drupal::service('theme_handler')->listInfo() as $name => $data) {
foreach (\Drupal::service('theme_handler')->listInfo() as $theme => $data) {
if ($data->status) {
_block_rehash($name);
$regions = system_region_list($theme);
/** @var \Drupal\block\BlockInterface[] $blocks */
$blocks = entity_load_multiple_by_properties('block', ['theme' => $theme]);
foreach ($blocks as $block_id => $block) {
// Disable blocks in invalid regions.
$region = $block->getRegion();
if ($region !== BlockInterface::BLOCK_REGION_NONE) {
if (!empty($region) && !isset($regions[$region]) && $block->status()) {
drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', ['%info' => $block_id, '%region' => $region]), 'warning');
$block->disable();
}
// Set region to none if not enabled.
if (!$block->status()) {
$block->setRegion(BlockInterface::BLOCK_REGION_NONE);
$block->save();
}
}
}
}
}
}
@ -307,13 +281,3 @@ function block_configurable_language_delete(ConfigurableLanguageInterface $langu
}
}
}
/**
* Implements hook_install().
*/
function block_install() {
// Because the Block module upon installation unconditionally overrides all
// HTML output by selecting a different page display variant, we must
// invalidate all cached HTML output.
Cache::invalidateTags(['rendered']);
}

View file

@ -42,6 +42,15 @@ block.admin_display_theme:
_access_theme: 'TRUE'
_permission: 'administer blocks'
block.admin_library:
path: 'admin/structure/block/library/{theme}'
defaults:
_controller: '\Drupal\block\Controller\BlockLibraryController::listBlocks'
_title: 'Place block'
requirements:
_access_theme: 'TRUE'
_permission: 'administer blocks'
block.admin_add:
path: '/admin/structure/block/add/{plugin_id}/{theme}'
defaults:

View file

@ -7,21 +7,6 @@ services:
class: Drupal\block\EventSubscriber\BlockPageDisplayVariantSubscriber
tags:
- { name: event_subscriber }
block.current_user_context:
class: Drupal\block\EventSubscriber\CurrentUserContext
arguments: ['@current_user', '@entity.manager']
tags:
- { name: 'event_subscriber' }
block.current_language_context:
class: Drupal\block\EventSubscriber\CurrentLanguageContext
arguments: ['@language_manager']
tags:
- { name: 'event_subscriber' }
block.node_route_context:
class: Drupal\block\EventSubscriber\NodeRouteContext
arguments: ['@current_route_match']
tags:
- { name: 'event_subscriber' }
block.repository:
class: Drupal\block\BlockRepository
arguments: ['@entity.manager', '@theme.manager', '@context.handler']

View file

@ -1,3 +1,13 @@
/* Block listing page */
.region-title .button {
margin-left: 1em; /* LTR */
}
[dir="rtl"] .region-title .button {
margin-left: 0;
margin-right: 1em;
}
/* Block demo mode */
.block-region {
background-color: #ff6;
margin-top: 4px;
@ -22,87 +32,10 @@ a.block-demo-backlink:hover {
text-decoration: underline;
}
.layout-region {
box-sizing: border-box;
}
.block-list-secondary {
border: 1px solid #bfbfbf;
border-bottom-width: 0;
}
.block-list {
padding: 0 0.75em;
margin: 0;
}
.block-list li {
list-style: none;
padding: 0.1em 0;
}
.block-list a:before {
content: '+ ';
}
.block-list-secondary .form-type-search {
padding: 0 1em;
}
/* Configure block form - Block description */
.block-form .form-item-settings-admin-label label {
display: inline;
}
.block-form .form-item-settings-admin-label label:after {
content: ':';
}
/* Wide screens */
@media
screen and (min-width: 780px),
(orientation: landscape) and (min-device-height: 780px) {
.block-list-primary {
float: left; /* LTR */
width: 75%;
padding-right: 2em;
}
[dir="rtl"] .block-list-primary {
float: right;
padding-left: 2em;
padding-right: 0;
}
.block-list-secondary {
float: right; /* LTR */
width: 25%;
}
[dir="rtl"] .block-list-secondary {
float: left;
}
/* @todo File an issue to add a standard class to all text-like inputs */
.block-list-secondary .form-autocomplete,
.block-list-secondary .form-text,
.block-list-secondary .form-tel,
.block-list-secondary .form-email,
.block-list-secondary .form-url,
.block-list-secondary .form-search,
.block-list-secondary .form-number,
.block-list-secondary .form-color,
.block-list-secondary textarea {
box-sizing: border-box;
width: 100%;
max-width: 100%;
}
}
/**
* The vertical toolbar mode gets triggered for narrow screens, which throws off
* the intent of media queries written for the viewport width. When the vertical
* toolbar is on, we need to suppress layout for the original media width + the
* toolbar width (240px). In this case, 240px + 780px.
*/
@media
screen and (max-width: 1020px) {
.toolbar-vertical.toolbar-tray-open .block-list-primary,
.toolbar-vertical.toolbar-tray-open .block-list-secondary {
float: none;
width: auto;
padding-right: 0;
}
}

View file

@ -19,66 +19,38 @@
Drupal.behaviors.blockFilterByText = {
attach: function (context, settings) {
var $input = $('input.block-filter-text').once('block-filter-text');
var $element = $($input.attr('data-element'));
var $blocks;
var $details;
var $table = $($input.attr('data-element'));
var $filter_rows;
/**
* Hides the `<details>` element for a category if it has no visible blocks.
*
* @param {number} index
* @param {HTMLElement} element
*/
function hideCategoryDetails(index, element) {
var $catDetails = $(element);
$catDetails.toggle($catDetails.find('li:visible').length > 0);
}
/**
* Filters the block list.
*
* @param {jQuery.Event} e
*/
function filterBlockList(e) {
var query = $(e.target).val().toLowerCase();
/**
* Shows or hides the block entry based on the query.
*
* @param {number} index
* @param {HTMLElement} block
* @param {number} index The index of the block.
* @param {HTMLElement} label The label of the block.
*/
function showBlockEntry(index, block) {
var $block = $(block);
var $sources = $block.find('.block-filter-text-source');
var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
$block.toggle(textMatch);
function toggleBlockEntry(index, label) {
var $label = $(label);
var $row = $label.parent().parent();
var textMatch = $label.text().toLowerCase().indexOf(query) !== -1;
$row.toggle(textMatch);
}
// Filter if the length of the query is at least 2 characters.
if (query.length >= 2) {
$blocks.each(showBlockEntry);
// Note that we first open all <details> to be able to use ':visible'.
// Mark the <details> elements that were closed before filtering, so
// they can be reclosed when filtering is removed.
$details.not('[open]').attr('data-drupal-block-state', 'forced-open');
// Hide the category <details> if they don't have any visible rows.
$details.attr('open', 'open').each(hideCategoryDetails);
$filter_rows.each(toggleBlockEntry);
}
else {
$blocks.show();
$details.show();
// Return <details> elements that had been closed before filtering
// to a closed state.
$details.filter('[data-drupal-block-state="forced-open"]').removeAttr('open data-drupal-block-state');
$filter_rows.each(function (index) {
$(this).parent().parent().show();
});
}
}
if ($element.length) {
$details = $element.find('details');
$blocks = $details.find('li');
if ($table.length) {
$filter_rows = $table.find('div.block-filter-text-source');
$input.on('keyup', filterBlockList);
}
}

View file

@ -26,7 +26,7 @@
var $checkboxes = $(context).find('input[type="checkbox"]:checked + label');
var il = $checkboxes.length;
for (var i = 0; i < il; i++) {
vals.push($($checkboxes[i]).text());
vals.push($($checkboxes[i]).html());
}
if (!vals.length) {
vals.push(Drupal.t('Not restricted'));

View file

@ -0,0 +1,94 @@
id: d6_block
label: Drupal 6 blocks
migration_tags:
- Drupal 6
source:
plugin: d6_block
process:
# Drupal 8 does not have a status but it doesn't matter; this is here to
# skip disabled blocks.
status:
plugin: skip_on_empty
method: row
source: status
id:
# We need something unique, so aggregator, aggregator_1 etc will do.
plugin: dedupe_entity
entity_type: block
field: id
postfix: _
length: 32
source: module
plugin:
-
plugin: static_map
bypass: true
source:
- module
- delta
map:
book:
0: book_navigation
comment:
0: views_block:comments_recent-block_1
forum:
0: forum_active_block
1: forum_new_block
locale:
0: language_block
node:
0: node_syndicate_block
search:
0: search_form_block
statistics:
0: statistics_popular_block
system:
0: system_powered_by_block
user:
0: user_login_block
1: system_menu_block:tools
2: views_block:who_s_new-block_1
3: views_block:who_s_online-who_s_online_block
-
plugin: d6_block_plugin_id
theme:
plugin: d6_block_theme
source:
- theme
- default_theme
- admin_theme
region:
plugin: d6_block_region
source:
- region
- theme
- @theme
region_map:
left: sidebar_first
right: sidebar_second
sidebar_first: sidebar_first
sidebar_second: sidebar_second
help: help
header: header
footer: footer
label: title
weight: weight
settings:
plugin: d6_block_settings
source:
- @plugin
- delta
- settings
visibility:
plugin: d6_block_visibility
source:
- pages
- roles
- visibility
destination:
plugin: entity:block
migration_dependencies:
required:
- d6_menu
- d6_custom_block
- d6_user_role

View file

@ -9,6 +9,8 @@ namespace Drupal\block;
use Drupal\Component\Plugin\Exception\ContextException;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Condition\ConditionAccessResolverTrait;
use Drupal\Core\Entity\EntityAccessControlHandler;
use Drupal\Core\Entity\EntityHandlerInterface;
@ -16,6 +18,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -43,6 +46,13 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
*/
protected $contextHandler;
/**
* The context manager service.
*
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* {@inheritdoc}
*/
@ -50,7 +60,8 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
return new static(
$entity_type,
$container->get('plugin.manager.condition'),
$container->get('context.handler')
$container->get('context.handler'),
$container->get('context.repository')
);
}
@ -63,11 +74,14 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
* The ConditionManager for checking visibility of blocks.
* @param \Drupal\Core\Plugin\Context\ContextHandlerInterface $context_handler
* The ContextHandler for applying contexts to conditions properly.
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
* The lazy context repository service.
*/
public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler) {
public function __construct(EntityTypeInterface $entity_type, ExecutableManagerInterface $manager, ContextHandlerInterface $context_handler, ContextRepositoryInterface $context_repository ) {
parent::__construct($entity_type);
$this->manager = $manager;
$this->contextHandler = $context_handler;
$this->contextRepository = $context_repository;
}
@ -85,33 +99,62 @@ class BlockAccessControlHandler extends EntityAccessControlHandler implements En
return AccessResult::forbidden()->cacheUntilEntityChanges($entity);
}
else {
$contexts = $entity->getContexts();
$conditions = [];
$missing_context = FALSE;
foreach ($entity->getVisibilityConditions() as $condition_id => $condition) {
if ($condition instanceof ContextAwarePluginInterface) {
try {
$contexts = $this->contextRepository->getRuntimeContexts(array_values($condition->getContextMapping()));
$this->contextHandler->applyContextMapping($condition, $contexts);
}
catch (ContextException $e) {
return AccessResult::forbidden()->setCacheMaxAge(0);
$missing_context = TRUE;
}
}
$conditions[$condition_id] = $condition;
}
if ($this->resolveConditions($conditions, 'and') !== FALSE) {
if ($missing_context) {
// If any context is missing then we might be missing cacheable
// metadata, and don't know based on what conditions the block is
// accessible or not. For example, blocks that have a node type
// condition will have a missing context on any non-node route like the
// frontpage.
// @todo Avoid setting max-age 0 for some or all cases, for example by
// treating available contexts without value differently in
// https://www.drupal.org/node/2521956.
$access = AccessResult::forbidden()->setCacheMaxAge(0);
}
elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
// Delegate to the plugin.
$access = $entity->getPlugin()->access($account, TRUE);
}
else {
$access = AccessResult::forbidden();
}
// This should not be hardcoded to an uncacheable access check result, but
// in order to fix that, we need condition plugins to return cache contexts,
// otherwise it will be impossible to determine by which cache contexts the
// result should be varied.
// @todo Change this to use $access->cacheUntilEntityChanges($entity) once
// https://www.drupal.org/node/2375695 is resolved.
return $access->setCacheMaxAge(0);
$this->mergeCacheabilityFromConditions($access, $conditions);
// Ensure that access is evaluated again when the block changes.
return $access->cacheUntilEntityChanges($entity);
}
}
/**
* Merges cacheable metadata from conditions onto the access result object.
*
* @param \Drupal\Core\Access\AccessResult $access
* The access result object.
* @param \Drupal\Core\Condition\ConditionInterface[] $conditions
* List of visibility conditions.
*/
protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions) {
foreach ($conditions as $condition) {
if ($condition instanceof CacheableDependencyInterface) {
$access->addCacheTags($condition->getCacheTags());
$access->addCacheContexts($condition->getCacheContexts());
$access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));
}
}
}

View file

@ -7,8 +7,6 @@
namespace Drupal\block;
use Drupal\block\Event\BlockContextEvent;
use Drupal\block\Event\BlockEvents;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
@ -18,8 +16,8 @@ use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Provides form for block instance forms.
@ -68,6 +66,13 @@ class BlockForm extends EntityForm {
*/
protected $themeHandler;
/**
* The context repository service.
*
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
*/
protected $contextRepository;
/**
* Constructs a BlockForm object.
*
@ -75,17 +80,17 @@ class BlockForm extends EntityForm {
* The entity manager.
* @param \Drupal\Core\Executable\ExecutableManagerInterface $manager
* The ConditionManager for building the visibility UI.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* The EventDispatcher for gathering administrative contexts.
* @param \Drupal\Core\Plugin\Context\ContextRepositoryInterface $context_repository
* The lazy context repository service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language
* The language manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
*/
public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, EventDispatcherInterface $dispatcher, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
$this->storage = $entity_manager->getStorage('block');
$this->manager = $manager;
$this->dispatcher = $dispatcher;
$this->contextRepository = $context_repository;
$this->language = $language;
$this->themeHandler = $theme_handler;
}
@ -97,7 +102,7 @@ class BlockForm extends EntityForm {
return new static(
$container->get('entity.manager'),
$container->get('plugin.manager.condition'),
$container->get('event_dispatcher'),
$container->get('context.repository'),
$container->get('language_manager'),
$container->get('theme_handler')
);
@ -117,7 +122,7 @@ class BlockForm extends EntityForm {
// Store the gathered contexts in the form state for other objects to use
// during form building.
$form_state->setTemporaryValue('gathered_contexts', $this->dispatcher->dispatch(BlockEvents::ADMINISTRATIVE_CONTEXT, new BlockContextEvent())->getContexts());
$form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
$form['#tree'] = TRUE;
$form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
@ -165,11 +170,13 @@ class BlockForm extends EntityForm {
}
// Region settings.
$entity_region = $entity->getRegion();
$region = $entity->isNew() ? $this->getRequest()->query->get('region', $entity_region) : $entity_region;
$form['region'] = array(
'#type' => 'select',
'#title' => $this->t('Region'),
'#description' => $this->t('Select the region where this block should be displayed.'),
'#default_value' => $entity->getRegion(),
'#default_value' => $region,
'#empty_value' => BlockInterface::BLOCK_REGION_NONE,
'#options' => system_region_list($theme, REGIONS_VISIBLE),
'#prefix' => '<div id="edit-block-region-wrapper">',
@ -273,8 +280,8 @@ class BlockForm extends EntityForm {
/**
* {@inheritdoc}
*/
public function validate(array $form, FormStateInterface $form_state) {
parent::validate($form, $form_state);
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
// The Block Entity form puts all block plugin form elements in the
// settings form element, so just pass that to the block for validation.

View file

@ -95,24 +95,6 @@ interface BlockInterface extends ConfigEntityInterface {
*/
public function setVisibilityConfig($instance_id, array $configuration);
/**
* Get all available contexts.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of set contexts, keyed by context name.
*/
public function getContexts();
/**
* Set the contexts that are available for use within the block entity.
*
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
* An array of contexts to set on the block.
*
* @return $this
*/
public function setContexts(array $contexts);
/**
* Returns the weight of this block (used for sorting).
*

View file

@ -8,15 +8,16 @@
namespace Drupal\block;
use Drupal\Component\Utility\Html;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
@ -28,13 +29,6 @@ use Symfony\Component\HttpFoundation\Request;
*/
class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface {
/**
* The regions containing the blocks.
*
* @var array
*/
protected $regions;
/**
* The theme containing the blocks.
*
@ -50,11 +44,23 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
protected $request;
/**
* The block manager.
* The theme manager.
*
* @var \Drupal\Core\Block\BlockManagerInterface
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $blockManager;
protected $themeManager;
/**
* The form builder.
*
* @var \Drupal\Core\Form\FormBuilderInterface
*/
protected $formBuilder;
/**
* {@inheritdoc}
*/
protected $limit = FALSE;
/**
* Constructs a new BlockListBuilder object.
@ -63,13 +69,16 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage class.
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
* @param \Drupal\Core\Form\FormBuilderInterface $form_builder
* The form builder.
*/
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, BlockManagerInterface $block_manager) {
public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $storage, ThemeManagerInterface $theme_manager, FormBuilderInterface $form_builder) {
parent::__construct($entity_type, $storage);
$this->blockManager = $block_manager;
$this->themeManager = $theme_manager;
$this->formBuilder = $form_builder;
}
/**
@ -79,31 +88,11 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
return new static(
$entity_type,
$container->get('entity.manager')->getStorage($entity_type->id()),
$container->get('plugin.manager.block')
$container->get('theme.manager'),
$container->get('form_builder')
);
}
/**
* {@inheritdoc}
*/
public function load() {
// If no theme was specified, use the current theme.
if (!$this->theme) {
$this->theme = \Drupal::theme()->getActiveTheme()->getName();
}
// Store the region list.
$this->regions = system_region_list($this->theme, REGIONS_VISIBLE);
// Load only blocks for this theme, and sort them.
// @todo Move the functionality of _block_rehash() out of the listing page.
$entities = _block_rehash($this->theme);
// Sort the blocks using \Drupal\block\Entity\Block::sort().
uasort($entities, array($this->entityType->getClass(), 'sort'));
return $entities;
}
/**
* {@inheritdoc}
*
@ -118,10 +107,9 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
*/
public function render($theme = NULL, Request $request = NULL) {
$this->request = $request;
// If no theme was specified, use the current theme.
$this->theme = $theme ?: \Drupal::theme()->getActiveTheme()->getName();
$this->theme = $theme;
return \Drupal::formBuilder()->getForm($this);
return $this->formBuilder->getForm($this);
}
/**
@ -132,55 +120,40 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
}
/**
* Implements \Drupal\Core\Form\FormInterface::buildForm().
*
* Form constructor for the main block administration form.
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$placement = FALSE;
if ($this->request->query->has('block-placement')) {
$placement = $this->request->query->get('block-placement');
$form['#attached']['drupalSettings']['blockPlacement'] = $placement;
}
$entities = $this->load();
$form['#theme'] = array('block_list');
$form['#attached']['library'][] = 'core/drupal.tableheader';
$form['#attached']['library'][] = 'block/drupal.block';
$form['#attached']['library'][] = 'block/drupal.block.admin';
$form['#attributes']['class'][] = 'clearfix';
// Add a last region for disabled blocks.
$block_regions_with_disabled = $this->regions + array(BlockInterface::BLOCK_REGION_NONE => BlockInterface::BLOCK_REGION_NONE);
$form['block_regions'] = array(
'#type' => 'value',
'#value' => $block_regions_with_disabled,
);
// Weights range from -delta to +delta, so delta should be at least half
// of the amount of blocks present. This makes sure all blocks in the same
// region get an unique weight.
$weight_delta = round(count($entities) / 2);
// Build the form tree.
$form['edited_theme'] = array(
'#type' => 'value',
'#value' => $this->theme,
$form['blocks'] = $this->buildBlocksForm();
$form['actions'] = array(
'#tree' => FALSE,
'#type' => 'actions',
);
$form['blocks'] = array(
'#type' => 'table',
'#header' => array(
t('Block'),
t('Category'),
t('Region'),
t('Weight'),
t('Operations'),
),
'#attributes' => array(
'id' => 'blocks',
),
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Save blocks'),
'#button_type' => 'primary',
);
return $form;
}
/**
* Builds the main "Blocks" portion of the form.
*
* @return array
*/
protected function buildBlocksForm() {
// Build blocks first for each region.
$blocks = [];
$entities = $this->load();
/** @var \Drupal\block\BlockInterface[] $entities */
foreach ($entities as $entity_id => $entity) {
$definition = $entity->getPlugin()->getPluginDefinition();
$blocks[$entity->getRegion()][$entity_id] = array(
@ -192,36 +165,73 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
);
}
$form = array(
'#type' => 'table',
'#header' => array(
$this->t('Block'),
$this->t('Category'),
$this->t('Region'),
$this->t('Weight'),
$this->t('Operations'),
),
'#attributes' => array(
'id' => 'blocks',
),
);
// Weights range from -delta to +delta, so delta should be at least half
// of the amount of blocks present. This makes sure all blocks in the same
// region get an unique weight.
$weight_delta = round(count($entities) / 2);
$placement = FALSE;
if ($this->request->query->has('block-placement')) {
$placement = $this->request->query->get('block-placement');
$form['#attached']['drupalSettings']['blockPlacement'] = $placement;
}
// Loop over each region and build blocks.
$regions = $this->systemRegionList($this->getThemeName(), REGIONS_VISIBLE);
$block_regions_with_disabled = $regions + array(BlockInterface::BLOCK_REGION_NONE => $this->t('Disabled', array(), array('context' => 'Plural')));
foreach ($block_regions_with_disabled as $region => $title) {
$form['blocks']['#tabledrag'][] = array(
$form['#tabledrag'][] = array(
'action' => 'match',
'relationship' => 'sibling',
'group' => 'block-region-select',
'subgroup' => 'block-region-' . $region,
'hidden' => FALSE,
);
$form['blocks']['#tabledrag'][] = array(
$form['#tabledrag'][] = array(
'action' => 'order',
'relationship' => 'sibling',
'group' => 'block-weight',
'subgroup' => 'block-weight-' . $region,
);
$form['blocks'][$region] = array(
$form['region-' . $region] = array(
'#attributes' => array(
'class' => array('region-title', 'region-title-' . $region),
'no_striping' => TRUE,
),
);
$form['blocks'][$region]['title'] = array(
'#markup' => $region != BlockInterface::BLOCK_REGION_NONE ? $title : t('Disabled', array(), array('context' => 'Plural')),
$form['region-' . $region]['title'] = array(
'#prefix' => $region != BlockInterface::BLOCK_REGION_NONE ? $title : $block_regions_with_disabled[$region],
'#type' => 'link',
'#title' => $this->t('Place block <span class="visually-hidden">in the %region region</span>', ['%region' => $block_regions_with_disabled[$region]]),
'#url' => Url::fromRoute('block.admin_library', ['theme' => $this->getThemeName()], ['query' => ['region' => $region]]),
'#wrapper_attributes' => array(
'colspan' => 5,
),
'#attributes' => [
'class' => ['use-ajax', 'button', 'button--small'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 700,
]),
],
);
$form['blocks'][$region . '-message'] = array(
$form['region-' . $region . '-message'] = array(
'#attributes' => array(
'class' => array(
'region-message',
@ -230,8 +240,8 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
),
),
);
$form['blocks'][$region . '-message']['message'] = array(
'#markup' => '<em>' . t('No blocks in this region') . '</em>',
$form['region-' . $region . '-message']['message'] = array(
'#markup' => '<em>' . $this->t('No blocks in this region') . '</em>',
'#wrapper_attributes' => array(
'colspan' => 5,
),
@ -241,137 +251,87 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
foreach ($blocks[$region] as $info) {
$entity_id = $info['entity_id'];
$form['blocks'][$entity_id] = array(
$form[$entity_id] = array(
'#attributes' => array(
'class' => array('draggable'),
),
);
if ($placement && $placement == Html::getClass($entity_id)) {
$form['blocks'][$entity_id]['#attributes']['class'][] = 'color-warning';
$form['blocks'][$entity_id]['#attributes']['class'][] = 'js-block-placed';
$form[$entity_id]['#attributes']['class'][] = 'color-warning';
$form[$entity_id]['#attributes']['class'][] = 'js-block-placed';
}
$form['blocks'][$entity_id]['info'] = array(
$form[$entity_id]['info'] = array(
'#markup' => SafeMarkup::checkPlain($info['label']),
'#wrapper_attributes' => array(
'class' => array('block'),
),
);
$form['blocks'][$entity_id]['type'] = array(
$form[$entity_id]['type'] = array(
'#markup' => $info['category'],
);
$form['blocks'][$entity_id]['region-theme']['region'] = array(
$form[$entity_id]['region-theme']['region'] = array(
'#type' => 'select',
'#default_value' => $region,
'#empty_value' => BlockInterface::BLOCK_REGION_NONE,
'#title' => t('Region for @block block', array('@block' => $info['label'])),
'#title' => $this->t('Region for @block block', array('@block' => $info['label'])),
'#title_display' => 'invisible',
'#options' => $this->regions,
'#options' => $regions,
'#attributes' => array(
'class' => array('block-region-select', 'block-region-' . $region),
),
'#parents' => array('blocks', $entity_id, 'region'),
);
$form['blocks'][$entity_id]['region-theme']['theme'] = array(
$form[$entity_id]['region-theme']['theme'] = array(
'#type' => 'hidden',
'#value' => $this->theme,
'#value' => $this->getThemeName(),
'#parents' => array('blocks', $entity_id, 'theme'),
);
$form['blocks'][$entity_id]['weight'] = array(
$form[$entity_id]['weight'] = array(
'#type' => 'weight',
'#default_value' => $info['weight'],
'#delta' => $weight_delta,
'#title' => t('Weight for @block block', array('@block' => $info['label'])),
'#title' => $this->t('Weight for @block block', array('@block' => $info['label'])),
'#title_display' => 'invisible',
'#attributes' => array(
'class' => array('block-weight', 'block-weight-' . $region),
),
);
$form['blocks'][$entity_id]['operations'] = $this->buildOperations($info['entity']);
$form[$entity_id]['operations'] = $this->buildOperations($info['entity']);
}
}
}
// Do not allow disabling the main system content block when it is present.
if (isset($form['blocks']['system_main']['region'])) {
$form['blocks']['system_main']['region']['#required'] = TRUE;
}
$form['actions'] = array(
'#tree' => FALSE,
'#type' => 'actions',
);
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save blocks'),
'#button_type' => 'primary',
);
$form['place_blocks']['title'] = array(
'#type' => 'container',
'#markup' => '<h3>' . t('Place blocks') . '</h3>',
'#attributes' => array(
'class' => array(
'entity-meta__header',
),
),
);
$form['place_blocks']['filter'] = array(
'#type' => 'search',
'#title' => t('Filter'),
'#title_display' => 'invisible',
'#size' => 30,
'#placeholder' => t('Filter by block name'),
'#attributes' => array(
'class' => array('block-filter-text'),
'data-element' => '.entity-meta',
'title' => t('Enter a part of the block name to filter by.'),
),
);
$form['place_blocks']['list']['#type'] = 'container';
$form['place_blocks']['list']['#attributes']['class'][] = 'entity-meta';
// Only add blocks which work without any available context.
$definitions = $this->blockManager->getDefinitionsForContexts();
$sorted_definitions = $this->blockManager->getSortedDefinitions($definitions);
foreach ($sorted_definitions as $plugin_id => $plugin_definition) {
$category = SafeMarkup::checkPlain($plugin_definition['category']);
$category_key = 'category-' . $category;
if (!isset($form['place_blocks']['list'][$category_key])) {
$form['place_blocks']['list'][$category_key] = array(
'#type' => 'details',
'#title' => $category,
'#open' => TRUE,
'content' => array(
'#theme' => 'links',
'#links' => array(),
'#attributes' => array(
'class' => array(
'block-list',
),
),
),
);
}
$form['place_blocks']['list'][$category_key]['content']['#links'][$plugin_id] = array(
'title' => $plugin_definition['admin_label'],
'url' => Url::fromRoute('block.admin_add', [
'plugin_id' => $plugin_id,
'theme' => $this->theme
]),
'attributes' => array(
'class' => array('use-ajax', 'block-filter-text-source'),
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode(array(
'width' => 700,
)),
),
);
if (isset($form['system_main']['region'])) {
$form['system_main']['region']['#required'] = TRUE;
}
return $form;
}
/**
* Gets the name of the theme used for this block listing.
*
* @return string
* The name of the theme.
*/
protected function getThemeName() {
// If no theme was specified, use the current theme.
if (!$this->theme) {
$this->theme = $this->themeManager->getActiveTheme()->getName();
}
return $this->theme;
}
/**
* {@inheritdoc}
*/
protected function getEntityIds() {
return $this->getStorage()->getQuery()
->condition('theme', $this->getThemeName())
->sort($this->entityType->getKey('id'))
->execute();
}
/**
* {@inheritdoc}
*/
@ -379,26 +339,25 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
$operations = parent::getDefaultOperations($entity);
if (isset($operations['edit'])) {
$operations['edit']['title'] = t('Configure');
$operations['edit']['title'] = $this->t('Configure');
}
return $operations;
}
/**
* Implements \Drupal\Core\Form\FormInterface::validateForm().
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
// No validation.
}
/**
* Implements \Drupal\Core\Form\FormInterface::submitForm().
*
* Form submission handler for the main block administration form.
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$entities = $this->storage->loadMultiple(array_keys($form_state->getValue('blocks')));
/** @var \Drupal\block\BlockInterface[] $entities */
foreach ($entities as $entity_id => $entity) {
$entity_values = $form_state->getValue(array('blocks', $entity_id));
$entity->setWeight($entity_values['weight']);
@ -417,4 +376,11 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
$this->request->query->remove('block-placement');
}
/**
* Wraps system_region_list().
*/
protected function systemRegionList($theme, $show = REGIONS_ALL) {
return system_region_list($theme, $show);
}
}

View file

@ -9,7 +9,6 @@ namespace Drupal\block;
use Drupal\Component\Plugin\Exception\PluginException;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Plugin\DefaultSingleLazyPluginCollection;
/**
@ -56,7 +55,7 @@ class BlockPluginCollection extends DefaultSingleLazyPluginCollection {
*/
protected function initializePlugin($instance_id) {
if (!$instance_id) {
throw new PluginException(SafeMarkup::format("The block '@block' did not specify a plugin.", array('@block' => $this->blockId)));
throw new PluginException("The block '{$this->blockId}' did not specify a plugin.");
}
try {

View file

@ -7,6 +7,7 @@
namespace Drupal\block;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\ContextHandlerInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
@ -49,7 +50,7 @@ class BlockRepository implements BlockRepositoryInterface {
/**
* {@inheritdoc}
*/
public function getVisibleBlocksPerRegion(array $contexts) {
public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []) {
$active_theme = $this->themeManager->getActiveTheme();
// Build an array of the region names in the right order.
$empty = array_fill_keys($active_theme->getRegions(), array());
@ -57,9 +58,18 @@ class BlockRepository implements BlockRepositoryInterface {
$full = array();
foreach ($this->blockStorage->loadByProperties(array('theme' => $active_theme->getName())) as $block_id => $block) {
/** @var \Drupal\block\BlockInterface $block */
$access = $block->access('view', NULL, TRUE);
$region = $block->getRegion();
if (!isset($cacheable_metadata[$region])) {
$cacheable_metadata[$region] = CacheableMetadata::createFromObject($access);
}
else {
$cacheable_metadata[$region] = $cacheable_metadata[$region]->merge(CacheableMetadata::createFromObject($access));
}
// Set the contexts on the block before checking access.
if ($block->setContexts($contexts)->access('view')) {
$full[$block->getRegion()][$block_id] = $block;
if ($access->isAllowed()) {
$full[$region][$block_id] = $block;
}
}

View file

@ -12,13 +12,14 @@ interface BlockRepositoryInterface {
/**
* Returns an array of regions and their block entities.
*
* @param \Drupal\Component\Plugin\Context\ContextInterface[] $contexts
* An array of contexts to set on the blocks.
* @param \Drupal\Core\Cache\CacheableMetadata[] $cacheable_metadata
* (optional) List of CacheableMetadata objects, keyed by region. This is
* by reference and is used to pass this information back to the caller.
*
* @return array
* The array is first keyed by region machine name, with the values
* containing an array keyed by block ID, with block entities as the values.
*/
public function getVisibleBlocksPerRegion(array $contexts);
public function getVisibleBlocksPerRegion(array &$cacheable_metadata = []);
}

View file

@ -48,6 +48,9 @@ class BlockViewBuilder extends EntityViewBuilder {
$derivative_id = $plugin->getDerivativeId();
$configuration = $plugin->getConfiguration();
$cache_tags = Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags());
$cache_tags = Cache::mergeTags($cache_tags, $plugin->getCacheTags());
// Create the render array for the block as a whole.
// @see template_preprocess_block().
$build[$entity_id] = array(
@ -67,12 +70,11 @@ class BlockViewBuilder extends EntityViewBuilder {
'#id' => $entity->id(),
'#cache' => [
'keys' => ['entity_view', 'block', $entity->id()],
'contexts' => $plugin->getCacheContexts(),
'tags' => Cache::mergeTags(
$this->getCacheTags(), // Block view builder cache tag.
$entity->getCacheTags(), // Block entity cache tag.
$plugin->getCacheTags() // Block plugin cache tags.
'contexts' => Cache::mergeContexts(
$entity->getCacheContexts(),
$plugin->getCacheContexts()
),
'tags' => $cache_tags,
'max-age' => $plugin->getCacheMaxAge(),
],
'#pre_render' => [

View file

@ -0,0 +1,179 @@
<?php
/**
* @file
* Contains \Drupal\block\Controller\BlockLibraryController.
*/
namespace Drupal\block\Controller;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Block\BlockManagerInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a list of block plugins to be added to the layout.
*/
class BlockLibraryController extends ControllerBase {
/**
* The block manager.
*
* @var \Drupal\Core\Block\BlockManagerInterface
*/
protected $blockManager;
/**
* The route match.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* The local action manager.
*
* @var \Drupal\Core\Menu\LocalActionManagerInterface
*/
protected $localActionManager;
/**
* Constructs a BlockLibraryController object.
*
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The block manager.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The current route match.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager
* The local action manager.
*/
public function __construct(BlockManagerInterface $block_manager, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
$this->blockManager = $block_manager;
$this->routeMatch = $route_match;
$this->localActionManager = $local_action_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.block'),
$container->get('current_route_match'),
$container->get('plugin.manager.menu.local_action')
);
}
/**
* Shows a list of blocks that can be added to a theme's layout.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The current request.
* @param string $theme
* Theme key of the block list.
*
* @return array
* A render array as expected by the renderer.
*/
public function listBlocks(Request $request, $theme) {
// Since modals do not render any other part of the page, we need to render
// them manually as part of this listing.
if ($request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'drupal_modal') {
$build['local_actions'] = $this->buildLocalActions();
}
$headers = [
['data' => $this->t('Block')],
['data' => $this->t('Category')],
['data' => $this->t('Operations')],
];
// Only add blocks which work without any available context.
$definitions = $this->blockManager->getDefinitionsForContexts();
// Order by category, and then by admin label.
$definitions = $this->blockManager->getSortedDefinitions($definitions);
$region = $request->query->get('region');
$rows = [];
foreach ($definitions as $plugin_id => $plugin_definition) {
$row = [];
$row['title']['data'] = [
'#markup' => $plugin_definition['admin_label'],
'#prefix' => '<div class="block-filter-text-source">',
'#suffix' => '</div>',
];
$row['category']['data'] = SafeMarkup::checkPlain($plugin_definition['category']);
$links['add'] = [
'title' => $this->t('Place block'),
'url' => Url::fromRoute('block.admin_add', ['plugin_id' => $plugin_id, 'theme' => $theme]),
'attributes' => [
'class' => ['use-ajax'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 700,
]),
],
];
if ($region) {
$links['add']['query']['region'] = $region;
}
$row['operations']['data'] = [
'#type' => 'operations',
'#links' => $links,
];
$rows[] = $row;
}
$build['#attached']['library'][] = 'block/drupal.block.admin';
$build['filter'] = [
'#type' => 'search',
'#title' => $this->t('Filter'),
'#title_display' => 'invisible',
'#size' => 30,
'#placeholder' => $this->t('Filter by block name'),
'#attributes' => [
'class' => ['block-filter-text'],
'data-element' => '.block-add-table',
'title' => $this->t('Enter a part of the block name to filter by.'),
],
];
$build['blocks'] = [
'#type' => 'table',
'#header' => $headers,
'#rows' => $rows,
'#empty' => $this->t('No blocks available.'),
'#attributes' => [
'class' => ['block-add-table'],
],
];
return $build;
}
/**
* Builds the local actions for this listing.
*
* @return array
* An array of local actions for this listing.
*/
protected function buildLocalActions() {
$build = $this->localActionManager->getActionsForRoute($this->routeMatch->getRouteName());
// Without this workaround, the action links will be rendered as <li> with
// no wrapping <ul> element.
if (!empty($build)) {
$build['#prefix'] = '<ul class="action-links">';
$build['#suffix'] = '</ul>';
}
return $build;
}
}

View file

@ -246,25 +246,10 @@ class Block extends ConfigEntityBase implements BlockInterface, EntityWithPlugin
// so we must invalidate the associated block's cache tag (which includes
// the theme cache tag).
if (!$update) {
Cache::invalidateTags($this->getCacheTags());
Cache::invalidateTags($this->getCacheTagsToInvalidate());
}
}
/**
* {@inheritdoc}
*/
public function setContexts(array $contexts) {
$this->contexts = $contexts;
return $this;
}
/**
* {@inheritdoc}
*/
public function getContexts() {
return $this->contexts;
}
/**
* {@inheritdoc}
*/

View file

@ -1,53 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\Event\BlockContextEvent.
*/
namespace Drupal\block\Event;
use Drupal\Core\Plugin\Context\ContextInterface;
use Symfony\Component\EventDispatcher\Event;
/**
* Event subscribers can add context to be used by the block and its conditions.
*
* @see \Drupal\block\Event\BlockEvents::ACTIVE_CONTEXT
* @see \Drupal\block\Event\BlockEvents::ADMINISTRATIVE_CONTEXT
*/
class BlockContextEvent extends Event {
/**
* The array of available contexts for blocks.
*
* @var array
*/
protected $contexts = [];
/**
* Sets the context object for a given name.
*
* @param string $name
* The name to store the context object under.
* @param \Drupal\Core\Plugin\Context\ContextInterface $context
* The context object to set.
*
* @return $this
*/
public function setContext($name, ContextInterface $context) {
$this->contexts[$name] = $context;
return $this;
}
/**
* Returns the context objects.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of contexts that have been provided.
*/
public function getContexts() {
return $this->contexts;
}
}

View file

@ -1,55 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\Event\BlockEvents.
*/
namespace Drupal\block\Event;
/**
* Defines events for the Block module.
*/
final class BlockEvents {
/**
* Name of the event when gathering condition context for a block plugin.
*
* This event allows you to provide additional context that can be used by
* a condition plugin in order to determine the visibility of a block. The
* event listener method receives a \Drupal\block\Event\BlockContextEvent
* instance. Generally any new context is paired with a new condition plugin
* that interprets the provided context and allows the block system to
* determine whether or not the block should be displayed.
*
* @Event
*
* @see \Drupal\Core\Block\BlockBase::getConditionContexts()
* @see \Drupal\block\Event\BlockContextEvent
* @see \Drupal\block\EventSubscriber\NodeRouteContext::onBlockActiveContext()
* @see \Drupal\Core\Condition\ConditionInterface
*/
const ACTIVE_CONTEXT = 'block.active_context';
/**
* Name of the event when gathering contexts for plugin configuration.
*
* This event allows you to provide information about your context to the
* administration UI without having to provide a value for the context. For
* example, during configuration there is no specific node to pass as context.
* However, we still need to inform the system that a context named 'node' is
* available and provide a definition so that blocks can be configured to use
* it.
*
* The event listener method receives a \Drupal\block\Event\BlockContextEvent
* instance.
*
* @Event
*
* @see \Drupal\block\BlockForm::form()
* @see \Drupal\block\Event\BlockContextEvent
* @see \Drupal\block\EventSubscriber\NodeRouteContext::onBlockAdministrativeContext()
*/
const ADMINISTRATIVE_CONTEXT = 'block.administrative_context';
}

View file

@ -1,75 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\EventSubscriber\BlockContextSubscriberBase.
*/
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\block\Event\BlockEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Provides a base class for block context subscribers.
*/
abstract class BlockContextSubscriberBase implements EventSubscriberInterface {
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events[BlockEvents::ACTIVE_CONTEXT][] = 'onBlockActiveContext';
$events[BlockEvents::ADMINISTRATIVE_CONTEXT][] = 'onBlockAdministrativeContext';
return $events;
}
/**
* Determines the available run-time contexts.
*
* For blocks to render correctly, all of the contexts that they require
* must be populated with values. So this method must set a value for each
* context that it adds. For example:
* @code
* // Determine a specific node to pass as context to blocks.
* $node = ...
*
* // Set that specific node as the value of the 'node' context.
* $context = new Context(new ContextDefinition('entity:node'));
* $context->setContextValue($node);
* $event->setContext('node.node', $context);
* @endcode
*
* @param \Drupal\block\Event\BlockContextEvent $event
* The Event to which to register available contexts.
*/
abstract public function onBlockActiveContext(BlockContextEvent $event);
/**
* Determines the available configuration-time contexts.
*
* When a block is being configured, the configuration UI must know which
* named contexts are potentially available, but does not care about the
* value, since the value can be different for each request, and might not
* be available at all during the configuration UI's request.
*
* For example:
* @code
* // During configuration, there is no specific node to pass as context.
* // However, inform the system that a context named 'node.node' is
* // available, and provide its definition, so that blocks can be
* // configured to use it. When the block is rendered, the value of this
* // context will be supplied by onBlockActiveContext().
* $context = new Context(new ContextDefinition('entity:node'));
* $event->setContext('node.node', $context);
* @endcode
*
* @param \Drupal\block\Event\BlockContextEvent $event
* The Event to which to register available contexts.
*
* @see static::onBlockActiveContext()
*/
abstract public function onBlockAdministrativeContext(BlockContextEvent $event);
}

View file

@ -1,63 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\EventSubscriber\CurrentLanguageContext.
*/
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Sets the current language as a context.
*/
class CurrentLanguageContext extends BlockContextSubscriberBase {
use StringTranslationTrait;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* Constructs a new CurrentLanguageContext.
*
* @param \Drupal\Core\Language\LanguageManagerInterface
* The language manager.
*/
public function __construct(LanguageManagerInterface $language_manager) {
$this->languageManager = $language_manager;
}
/**
* {@inheritdoc}
*/
public function onBlockActiveContext(BlockContextEvent $event) {
// Add a context for each language type.
$language_types = $this->languageManager->getLanguageTypes();
$info = $this->languageManager->getDefinedLanguageTypesInfo();
foreach ($language_types as $type_key) {
if (isset($info[$type_key]['name'])) {
$context = new Context(new ContextDefinition('language', $info[$type_key]['name']));
$context->setContextValue($this->languageManager->getCurrentLanguage($type_key));
$event->setContext('language.' . $type_key, $context);
}
}
}
/**
* {@inheritdoc}
*/
public function onBlockAdministrativeContext(BlockContextEvent $event) {
$this->onBlockActiveContext($event);
}
}

View file

@ -1,69 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\EventSubscriber\CurrentUserContext.
*/
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
/**
* Sets the current user as a context.
*/
class CurrentUserContext extends BlockContextSubscriberBase {
use StringTranslationTrait;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* The user storage.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* Constructs a new CurrentUserContext.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
*/
public function __construct(AccountInterface $account, EntityManagerInterface $entity_manager) {
$this->account = $account;
$this->userStorage = $entity_manager->getStorage('user');
}
/**
* {@inheritdoc}
*/
public function onBlockActiveContext(BlockContextEvent $event) {
$current_user = $this->userStorage->load($this->account->id());
$context = new Context(new ContextDefinition('entity:user', $this->t('Current user')));
$context->setContextValue($current_user);
$event->setContext('user.current_user', $context);
}
/**
* {@inheritdoc}
*/
public function onBlockAdministrativeContext(BlockContextEvent $event) {
$this->onBlockActiveContext($event);
}
}

View file

@ -1,65 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\EventSubscriber\NodeRouteContext.
*/
namespace Drupal\block\EventSubscriber;
use Drupal\block\Event\BlockContextEvent;
use Drupal\Core\Plugin\Context\Context;
use Drupal\Core\Plugin\Context\ContextDefinition;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\Entity\Node;
/**
* Sets the current node as a context on node routes.
*/
class NodeRouteContext extends BlockContextSubscriberBase {
/**
* The route match object.
*
* @var \Drupal\Core\Routing\RouteMatchInterface
*/
protected $routeMatch;
/**
* Constructs a new NodeRouteContext.
*
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match object.
*/
public function __construct(RouteMatchInterface $route_match) {
$this->routeMatch = $route_match;
}
/**
* {@inheritdoc}
*/
public function onBlockActiveContext(BlockContextEvent $event) {
if (($route_object = $this->routeMatch->getRouteObject()) && ($route_contexts = $route_object->getOption('parameters')) && isset($route_contexts['node'])) {
$context = new Context(new ContextDefinition($route_contexts['node']['type']));
if ($node = $this->routeMatch->getParameter('node')) {
$context->setContextValue($node);
}
$event->setContext('node.node', $context);
}
elseif ($this->routeMatch->getRouteName() == 'node.add') {
$node_type = $this->routeMatch->getParameter('node_type');
$context = new Context(new ContextDefinition('entity:node'));
$context->setContextValue(Node::create(array('type' => $node_type->id())));
$event->setContext('node.node', $context);
}
}
/**
* {@inheritdoc}
*/
public function onBlockAdministrativeContext(BlockContextEvent $event) {
$context = new Context(new ContextDefinition('entity:node'));
$event->setContext('node.node', $context);
}
}

View file

@ -69,7 +69,7 @@ class ThemeLocalTask extends DeriverBase implements ContainerDeriverInterface {
}
// Default task!
if ($default_theme == $theme_name) {
$this->derivatives[$theme_name]['route_name'] = 'block.admin_display';
$this->derivatives[$theme_name]['route_name'] = $base_plugin_definition['parent_id'];
// Emulate default logic because without the base plugin id we can't
// change the base_route.
$this->derivatives[$theme_name]['weight'] = -10;

View file

@ -8,17 +8,14 @@
namespace Drupal\block\Plugin\DisplayVariant;
use Drupal\block\BlockRepositoryInterface;
use Drupal\block\Event\BlockContextEvent;
use Drupal\block\Event\BlockEvents;
use Drupal\Core\Block\MainContentBlockPluginInterface;
use Drupal\Core\Block\MessagesBlockPluginInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Display\PageVariantInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Display\VariantBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* Provides a page display variant that decorates the main content with blocks.
@ -79,16 +76,13 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
* The block repository.
* @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder
* The block view builder.
* @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $dispatcher
* The event dispatcher.
* @param string[] $block_list_cache_tags
* The Block entity type list cache tags.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, EventDispatcherInterface $dispatcher, array $block_list_cache_tags) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->blockRepository = $block_repository;
$this->blockViewBuilder = $block_view_builder;
$this->dispatcher = $dispatcher;
$this->blockListCacheTags = $block_list_cache_tags;
}
@ -102,7 +96,6 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
$plugin_definition,
$container->get('block.repository'),
$container->get('entity.manager')->getViewBuilder('block'),
$container->get('event_dispatcher'),
$container->get('entity.manager')->getDefinition('block')->getListCacheTags()
);
}
@ -128,9 +121,9 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
'tags' => $this->blockListCacheTags,
],
];
$contexts = $this->getActiveBlockContexts();
// Load all region content assigned via blocks.
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $blocks) {
$cacheable_metadata_list = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata_list) as $region => $blocks) {
/** @var $blocks \Drupal\block\BlockInterface[] */
foreach ($blocks as $key => $block) {
$block_plugin = $block->getPlugin();
@ -172,17 +165,18 @@ class BlockPageVariant extends VariantBase implements PageVariantInterface, Cont
];
}
// The access results' cacheability is currently added to the top level of the
// render array. This is done to prevent issues with empty regions being
// displayed.
// This would need to be changed to allow caching of block regions, as each
// region must then have the relevant cacheable metadata.
$merged_cacheable_metadata = CacheableMetadata::createFromRenderArray($build);
foreach ($cacheable_metadata_list as $cacheable_metadata) {
$merged_cacheable_metadata = $merged_cacheable_metadata->merge($cacheable_metadata);
}
$merged_cacheable_metadata->applyTo($build);
return $build;
}
/**
* Returns an array of context objects to set on the blocks.
*
* @return \Drupal\Component\Plugin\Context\ContextInterface[]
* An array of contexts to set on the blocks.
*/
protected function getActiveBlockContexts() {
return $this->dispatcher->dispatch(BlockEvents::ACTIVE_CONTEXT, new BlockContextEvent())->getContexts();
}
}

View file

@ -0,0 +1,100 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\process\d6\BlockPluginId.
*/
namespace Drupal\block\Plugin\migrate\process\d6;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "d6_block_plugin_id"
* )
*/
class BlockPluginId extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\migrate\Plugin\MigratePluginManager
*/
protected $processPluginManager;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $blockContentStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, MigratePluginManager $process_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->blockContentStorage = $storage;
$this->migration = $migration;
$this->processPluginManager = $process_plugin_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_manager = $container->get('entity.manager');
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$entity_manager->getDefinition('block_content') ? $entity_manager->getStorage('block_content') : NULL,
$container->get('plugin.manager.migrate.process')
);
}
/**
* {@inheritdoc}
*
* Set the block plugin id.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value)) {
list($module, $delta) = $value;
switch ($module) {
case 'aggregator':
list($type, $id) = explode('-', $delta);
if ($type == 'category') {
// @TODO skip row.
// throw new MigrateSkipRowException();
}
$value = 'aggregator_feed_block';
break;
case 'menu':
$value = "system_menu_block:$delta";
break;
case 'block':
if ($this->blockContentStorage) {
$block_ids = $this->processPluginManager
->createInstance('migration', array('migration' => 'd6_custom_block'), $this->migration)
->transform($delta, $migrate_executable, $row, $destination_property);
$value = 'block_content:' . $this->blockContentStorage->load($block_ids[0])->uuid();
}
else {
throw new MigrateSkipRowException();
}
break;
default:
throw new MigrateSkipRowException();
}
}
return $value;
}
}

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\process\d6\BlockRegion.
*/
namespace Drupal\block\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "d6_block_region"
* )
*/
class BlockRegion extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Set the destination block region, based on the source region and theme as
* well as the current destination default theme.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($region, $source_theme, $destination_theme) = $value;
// Theme is the same on both source and destination, we will assume they
// have the same regions.
if (strtolower($source_theme) == strtolower($destination_theme)) {
return $region;
}
// If the source and destination theme are different, try to use the
// mappings defined in the configuration.
$region_map = $this->configuration['region_map'];
if (isset($region_map[$region])) {
return $region_map[$region];
}
// Oh well, we tried. Put the block in the main content region.
return 'content';
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\process\d6\BlockSettings.
*/
namespace Drupal\block\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "d6_block_settings"
* )
*/
class BlockSettings extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Set the block configuration.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($plugin, $delta, $old_settings) = $value;
$settings = array();
switch ($plugin) {
case 'aggregator_feed_block':
list(, $id) = explode('-', $delta);
$settings['block_count'] = $old_settings['aggregator']['item_count'];
$settings['feed'] = $id;
break;
case 'book_navigation':
$settings['block_mode'] = $old_settings['book']['block_mode'];
break;
case 'forum_active_block':
case 'forum_new_block':
$settings['block_count'] = $old_settings['forum']['block_num'];
break;
case 'statistics_popular_block':
$settings['top_day_num'] = $old_settings['statistics']['statistics_block_top_day_num'];
$settings['top_all_num'] = $old_settings['statistics']['statistics_block_top_all_num'];
$settings['top_last_num'] = $old_settings['statistics']['statistics_block_top_last_num'];
break;
case 'views_block:who_s_new-block_1':
$settings['items_per_page'] = $old_settings['user']['block_whois_new_count'];
break;
case 'views_block:who_s_online-who_s_online_block':
$settings['items_per_page'] = $old_settings['user']['max_list_count'];
break;
}
return $settings;
}
}

View file

@ -0,0 +1,104 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\process\d6\BlockTheme.
*/
namespace Drupal\block\Plugin\migrate\process\d6;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\Core\Config\Config;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "d6_block_theme"
* )
*/
class BlockTheme extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* Contains the configuration object factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactory;
/**
* Contains the system.theme configuration object.
*
* @var \Drupal\Core\Config\Config
*/
protected $themeConfig;
/**
* Constructs a BlockTheme object.
*
* @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\migrate\Entity\MigrationInterface $migration
* The migration entity.
* @param \Drupal\Core\Config\Config $theme_config
* The system.theme configuration factory object.
* @param array $themes
* The list of themes available on the destination.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, Config $theme_config, array $themes) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->themeConfig = $theme_config;
$this->themes = $themes;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('config.factory')->get('system.theme'),
$container->get('theme_handler')->listInfo()
);
}
/**
* {@inheritdoc}
*
* Set the block theme, based on the current default theme.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($theme, $d6_default_theme, $d6_admin_theme) = $value;
// If the source theme exists on the destination, we're good.
if (isset($this->themes[$theme])) {
return $theme;
}
// If the source block is assigned to a region in the source default theme,
// then assign it to the destination default theme.
if (strtolower($theme) == strtolower($d6_default_theme)) {
return $this->themeConfig->get('default');
}
// If the source block is assigned to a region in the source admin theme,
// then assign it to the destination admin theme.
if (strtolower($theme) == strtolower($d6_admin_theme)) {
return $this->themeConfig->get('admin');
}
// We couldn't map it to a D8 theme so just return the incoming theme.
return $theme;
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\process\d6\BlockVisibility.
*/
namespace Drupal\block\Plugin\migrate\process\d6;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Plugin\MigrateProcessInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "d6_block_visibility"
* )
*/
class BlockVisibility extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The migration plugin.
*
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
*/
protected $migrationPlugin;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrateProcessInterface $migration_plugin) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
$this->migrationPlugin = $migration_plugin;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('plugin.manager.migrate.process')->createInstance('migration', array('migration' => 'd6_user_role'), $migration)
);
}
/**
* {@inheritdoc}
*
* Set the block visibility settings.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($pages, $roles, $old_visibility) = $value;
$visibility = array();
if (!empty($pages)) {
$visibility['request_path']['pages'] = $pages;
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = !$old_visibility;
}
if (!empty($roles)) {
foreach ($roles as $key => $role_id) {
$new_role = $this->migrationPlugin->transform($role_id, $migrate_executable, $row, $destination_property);
$visibility['user_role']['roles'][$new_role] = $new_role;
}
$visibility['user_role']['id'] = 'user_role';
$visibility['user_role']['context_mapping']['user'] = 'user.current_user';
}
return $visibility;
}
}

View file

@ -0,0 +1,139 @@
<?php
/**
* @file
* Contains \Drupal\block\Plugin\migrate\source\d6\Block.
*/
namespace Drupal\block\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal 6 block source from database.
*
* @MigrateSource(
* id = "d6_block"
* )
*/
class Block extends DrupalSqlBase {
/**
* The default theme name.
*
* @var string
*/
protected $defaultTheme;
/**
* The admin theme name.
*
* @var string
*/
protected $adminTheme;
/**
* {@inheritdoc}
*/
public function query() {
$query = $this->select('blocks', 'b')
->fields('b', array('bid', 'module', 'delta', 'theme', 'status', 'weight', 'region', 'visibility', 'pages', 'title', 'cache'))
->orderBy('bid');
return $query;
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
$this->defaultTheme = $this->variableGet('theme_default', 'Garland');
$this->adminTheme = $this->variableGet('admin_theme', null);
return parent::initializeIterator();
}
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'bid' => $this->t('The block numeric identifier.'),
'module' => $this->t('The module providing the block.'),
'delta' => $this->t('The block\'s delta.'),
'theme' => $this->t('Which theme the block is placed in.'),
'status' => $this->t('Whether or not the block is enabled.'),
'weight' => $this->t('Weight of the block for ordering within regions.'),
'region' => $this->t('Region the block is placed in.'),
'visibility' => $this->t('Visibility expression.'),
'pages' => $this->t('Pages list.'),
'title' => $this->t('Block title.'),
'cache' => $this->t('Cache rule.'),
);
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$row->setSourceProperty('default_theme', $this->defaultTheme);
$row->setSourceProperty('admin_theme', $this->adminTheme);
$module = $row->getSourceProperty('module');
$delta = $row->getSourceProperty('delta');
$roles = $this->select('blocks_roles', 'br')
->fields('br', array('rid'))
->condition('module', $module)
->condition('delta', $delta)
->execute()
->fetchCol();
$row->setSourceProperty('roles', $roles);
$settings = array();
// Contrib can use hook_migration_d6_block_prepare_row() to add similar
// variables via $migration->getSource()->variableGet().
switch ($module) {
case 'aggregator':
list($type, $id) = explode('-', $delta);
if ($type == 'feed') {
$item_count = $this->database->query('SELECT block FROM {aggregator_feed} WHERE fid = :fid', array(':fid' => $id))->fetchField();
}
else {
$item_count = $this->database->query('SELECT block FROM {aggregator_category} WHERE cid = :cid', array(':cid' => $id))->fetchField();
}
$settings['aggregator']['item_count'] = $item_count;
break;
case 'book':
$settings['book']['block_mode'] = $this->variableGet('book_block_mode', 'all pages');
break;
case 'forum':
$settings['forum']['block_num'] = $this->variableGet('forum_block_num_'. $delta, 5);
break;
case 'statistics':
foreach (array('statistics_block_top_day_num', 'statistics_block_top_all_num', 'statistics_block_top_last_num') as $name) {
$settings['statistics'][$name] = $this->variableGet($name, 0);
}
break;
case 'user':
switch ($delta) {
case 2:
$settings['user']['block_whois_new_count'] = $this->variableGet('user_block_whois_new_count', 5);
break;
case 3:
$settings['user']['block_seconds_online'] = $this->variableGet('user_block_seconds_online', 900);
$settings['user']['max_list_count'] = $this->variableGet('user_block_max_list_count', 10);
break;
}
break;
}
$row->setSourceProperty('settings', $settings);
return parent::prepareRow($row);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['module']['type'] = 'string';
$ids['delta']['type'] = 'string';
$ids['theme']['type'] = 'string';
return $ids;
}
}

View file

@ -119,9 +119,11 @@ class BlockCacheTest extends WebTestBase {
}
/**
* Test a cacheable block without any cache context.
* Test a cacheable block without any additional cache context.
*/
function testCacheGlobal() {
function testCachePermissions() {
// user.permissions is a required context, so a user with different
// permissions will see a different version of the block.
\Drupal::state()->set('block_test.cache_contexts', []);
$current_content = $this->randomMachineName();
@ -134,9 +136,12 @@ class BlockCacheTest extends WebTestBase {
$current_content = $this->randomMachineName();
\Drupal::state()->set('block_test.content', $current_content);
$this->drupalLogout();
$this->drupalGet('user');
$this->assertText($old_content, 'Block content served from cache.');
$this->drupalLogout();
$this->drupalGet('user');
$this->assertText($current_content, 'Block content not served from cache.');
}
/**

View file

@ -62,6 +62,7 @@ class BlockLanguageCacheTest extends WebTestBase {
// Create the block cache for all languages.
foreach ($this->langcodes as $langcode) {
$this->drupalGet('admin/structure/block', array('language' => $langcode));
$this->clickLinkPartialName('Place block');
}
// Create a menu in the default language.
@ -73,6 +74,7 @@ class BlockLanguageCacheTest extends WebTestBase {
// Check that the block is listed for all languages.
foreach ($this->langcodes as $langcode) {
$this->drupalGet('admin/structure/block', array('language' => $langcode));
$this->clickLinkPartialName('Place block');
$this->assertText($edit['label']);
}
}

View file

@ -90,6 +90,7 @@ class BlockLanguageTest extends WebTestBase {
'langcodes' => array(
'fr' => 'fr',
),
'context_mapping' => ['language' => '@language.current_language_context:language_interface'],
),
),
);
@ -141,7 +142,7 @@ class BlockLanguageTest extends WebTestBase {
// Enable a standard block and set visibility to French only.
$block_id = strtolower($this->randomMachineName(8));
$edit = [
'visibility[language][context_mapping][language]' => 'language.language_interface',
'visibility[language][context_mapping][language]' => '@language.current_language_context:language_interface',
'visibility[language][langcodes][fr]' => TRUE,
'id' => $block_id,
'region' => 'sidebar_first',
@ -167,7 +168,7 @@ class BlockLanguageTest extends WebTestBase {
// Change visibility to now depend on content language for this block.
$edit = [
'visibility[language][context_mapping][language]' => 'language.language_content'
'visibility[language][context_mapping][language]' => '@language.current_language_context:language_content'
];
$this->drupalPostForm('admin/structure/block/manage/' . $block_id, $edit, t('Save block'));

View file

@ -53,6 +53,14 @@ class BlockSystemBrandingTest extends BlockTestBase {
$this->assertTrue(!empty($site_slogan_element), 'The branding block slogan was found.');
$this->assertCacheTag('config:system.site');
// Be sure the slogan is XSS-filtered.
$this->config('system.site')
->set('slogan', '<script>alert("Community carpentry");</script>')
->save();
$this->drupalGet('');
$site_slogan_element = $this->xpath($site_slogan_xpath);
$this->assertEqual($site_slogan_element[0], 'alert("Community carpentry");', 'The site slogan was XSS-filtered.');
// Turn just the logo off.
$this->config('block.block.site-branding')
->set('settings.use_site_logo', 0)

View file

@ -334,11 +334,13 @@ class BlockTest extends BlockTestBase {
'config:block_list',
'block_view',
'config:block.block.powered',
'config:user.role.anonymous',
'rendered',
);
sort($expected_cache_tags);
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
$expected_cache_tags = array(
'block_view',
'config:block.block.powered',
@ -373,6 +375,7 @@ class BlockTest extends BlockTestBase {
'block_view',
'config:block.block.powered',
'config:block.block.powered-2',
'config:user.role.anonymous',
'rendered',
);
sort($expected_cache_tags);
@ -383,7 +386,8 @@ class BlockTest extends BlockTestBase {
'rendered',
);
sort($expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:en:classy');
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered:' . implode(':', $keys));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
$expected_cache_tags = array(
'block_view',
@ -391,7 +395,8 @@ class BlockTest extends BlockTestBase {
'rendered',
);
sort($expected_cache_tags);
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:en:classy');
$keys = \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:language_interface', 'theme', 'user.permissions'])->getKeys();
$cache_entry = \Drupal::cache('render')->get('entity_view:block:powered-2:' . implode(':', $keys));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
// Now we should have a cache hit again.

View file

@ -1,46 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\block\Tests\BlockTitleXSSTest.
*/
namespace Drupal\block\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Tests block XSS in title.
*
* @group block
*/
class BlockTitleXSSTest extends WebTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('block', 'block_test');
protected function setUp() {
parent::setUp();
$this->drupalPlaceBlock('test_xss_title', array('label' => '<script>alert("XSS label");</script>'));
}
/**
* Test XSS in title.
*/
function testXSSInTitle() {
\Drupal::state()->set('block_test.content', $this->randomMachineName());
$this->drupalGet('');
$this->assertNoRaw('<script>alert("XSS label");</script>', 'The block title was properly sanitized when rendered.');
$this->drupalLogin($this->drupalCreateUser(array('administer blocks', 'access administration pages')));
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/list/' . $default_theme);
$this->assertNoRaw("<script>alert('XSS subject');</script>", 'The block title was properly sanitized in Block Plugin UI Admin page.');
}
}

View file

@ -126,6 +126,12 @@ class BlockUiTest extends WebTestBase {
'The block "' . $label . '" has the correct weight assignment (' . $values['test_weight'] . ').'
);
}
// Add a block with a machine name the same as a region name.
$this->drupalPlaceBlock('system_powered_by_block', ['region' => 'header', 'id' => 'header']);
$this->drupalGet('admin/structure/block');
$element = $this->xpath('//tr[contains(@class, :class)]', [':class' => 'region-title-header']);
$this->assertTrue(!empty($element));
}
/**
@ -133,14 +139,15 @@ class BlockUiTest extends WebTestBase {
*/
public function testCandidateBlockList() {
$arguments = array(
':ul_class' => 'block-list',
':li_class' => 'test-block-instantiation',
':title' => 'Display message',
':category' => 'Block test',
':href' => 'admin/structure/block/add/test_block_instantiation/classy',
':text' => 'Display message',
);
$pattern = '//tr[.//td/div[text()=:title] and .//td[text()=:category] and .//td//a[contains(@href, :href)]]';
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id="edit-category-block-test"]//ul[contains(@class, :ul_class)]/li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the category for its module.');
// Trigger the custom category addition in block_test_block_alter().
@ -148,7 +155,9 @@ class BlockUiTest extends WebTestBase {
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id="edit-category-custom-category"]//ul[contains(@class, :ul_class)]/li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->clickLinkPartialName('Place block');
$arguments[':category'] = 'Custom category';
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in a custom category controlled by block_test_block_alter().');
}

View file

@ -9,7 +9,7 @@ namespace Drupal\block\Tests;
use Drupal\Component\Utility\Html;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\Context\UrlCacheContext;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
@ -27,7 +27,7 @@ class BlockViewBuilderTest extends KernelTestBase {
*
* @var array
*/
public static $modules = array('block', 'block_test', 'system');
public static $modules = array('block', 'block_test', 'system', 'user');
/**
* The block being tested.
@ -160,7 +160,7 @@ class BlockViewBuilderTest extends KernelTestBase {
// Test that a cache entry is created.
$build = $this->getBlockRenderArray();
$cid = 'entity_view:block:test_block:en:core';
$cid = 'entity_view:block:test_block:' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$this->renderer->renderRoot($build);
$this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.');
@ -190,14 +190,14 @@ class BlockViewBuilderTest extends KernelTestBase {
public function testBlockViewBuilderAlter() {
// Establish baseline.
$build = $this->getBlockRenderArray();
$this->assertIdentical($this->renderer->renderRoot($build), 'Llamas &gt; unicorns!');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas &gt; unicorns!');
// Enable the block view alter hook that adds a suffix, for basic testing.
\Drupal::state()->set('block_test_view_alter_suffix', TRUE);
Cache::invalidateTags($this->block->getCacheTags());
Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
$build = $this->getBlockRenderArray();
$this->assertTrue(isset($build['#suffix']) && $build['#suffix'] === '<br>Goodbye!', 'A block with content is altered.');
$this->assertIdentical($this->renderer->renderRoot($build), 'Llamas &gt; unicorns!<br>Goodbye!');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Llamas &gt; unicorns!<br>Goodbye!');
\Drupal::state()->set('block_test_view_alter_suffix', FALSE);
// Force a request via GET so we can test the render cache.
@ -206,7 +206,7 @@ class BlockViewBuilderTest extends KernelTestBase {
$request->setMethod('GET');
\Drupal::state()->set('block_test.content', NULL);
Cache::invalidateTags($this->block->getCacheTags());
Cache::invalidateTags($this->block->getCacheTagsToInvalidate());
$default_keys = array('entity_view', 'block', 'test_block');
$default_tags = array('block_view', 'config:block.block.test_block');
@ -214,11 +214,11 @@ class BlockViewBuilderTest extends KernelTestBase {
// Advanced: cached block, but an alter hook adds an additional cache key.
$alter_add_key = $this->randomMachineName();
\Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key);
$cid = 'entity_view:block:test_block:' . $alter_add_key . ':en:core';
$cid = 'entity_view:block:test_block:' . $alter_add_key . ':' . implode(':', \Drupal::service('cache_contexts_manager')->convertTokensToKeys(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'])->getKeys());
$expected_keys = array_merge($default_keys, array($alter_add_key));
$build = $this->getBlockRenderArray();
$this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.');
$this->assertIdentical($this->renderer->renderRoot($build), '');
$this->assertIdentical((string) $this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, ['rendered']);
@ -233,7 +233,7 @@ class BlockViewBuilderTest extends KernelTestBase {
$build = $this->getBlockRenderArray();
sort($build['#cache']['tags']);
$this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.');
$this->assertIdentical($this->renderer->renderRoot($build), '');
$this->assertIdentical((string) $this->renderer->renderRoot($build), '');
$cache_entry = $this->container->get('cache.render')->get($cid);
$this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
$expected_tags = array_merge($default_tags, [$alter_add_tag, 'rendered']);
@ -246,7 +246,7 @@ class BlockViewBuilderTest extends KernelTestBase {
\Drupal::state()->set('block_test_view_alter_append_pre_render_prefix', TRUE);
$build = $this->getBlockRenderArray();
$this->assertFalse(isset($build['#prefix']), 'The appended #pre_render callback has not yet run before rendering.');
$this->assertIdentical($this->renderer->renderRoot($build), 'Hiya!<br>');
$this->assertIdentical((string) $this->renderer->renderRoot($build), 'Hiya!<br>');
$this->assertTrue(isset($build['#prefix']) && $build['#prefix'] === 'Hiya!<br>', 'A cached block without content is altered.');
// Restore the previous request method.

View file

@ -26,7 +26,34 @@ class BlockXssTest extends WebTestBase {
*
* @var array
*/
public static $modules = ['block', 'block_content', 'menu_ui', 'views'];
public static $modules = ['block', 'block_content', 'block_test', 'menu_ui', 'views'];
/**
* Test XSS in title.
*/
public function testXssInTitle() {
$this->drupalPlaceBlock('test_xss_title', ['label' => '<script>alert("XSS label");</script>']);
\Drupal::state()->set('block_test.content', $this->randomMachineName());
$this->drupalGet('');
$this->assertNoRaw('<script>alert("XSS label");</script>', 'The block title was properly sanitized when rendered.');
$this->drupalLogin($this->drupalCreateUser(['administer blocks', 'access administration pages']));
$default_theme = $this->config('system.theme')->get('default');
$this->drupalGet('admin/structure/block/list/' . $default_theme);
$this->assertNoRaw("<script>alert('XSS subject');</script>", 'The block title was properly sanitized in Block Plugin UI Admin page.');
}
/**
* Tests XSS in category.
*/
public function testXssInCategory() {
$this->drupalPlaceBlock('test_xss_title');
$this->drupalLogin($this->drupalCreateUser(['administer blocks', 'access administration pages']));
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLinkPartialName('Place block');
$this->assertNoRaw("<script>alert('XSS category');</script>");
}
/**
* Tests various modules that provide blocks for XSS.
@ -51,8 +78,9 @@ class BlockXssTest extends WebTestBase {
$view->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLink('<script>alert("view");</script>');
$this->assertRaw('&lt;script&gt;alert(&quot;view&quot;);&lt;/script&gt;');
$this->clickLinkPartialName('Place block');
// The block admin label is automatically XSS admin filtered.
$this->assertRaw('alert("view");');
$this->assertNoRaw('<script>alert("view");</script>');
}
@ -66,8 +94,9 @@ class BlockXssTest extends WebTestBase {
])->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLink('<script>alert("menu");</script>');
$this->assertRaw('&lt;script&gt;alert(&quot;menu&quot;);&lt;/script&gt;');
$this->clickLinkPartialName('Place block');
// The block admin label is automatically XSS admin filtered.
$this->assertRaw('alert("menu");');
$this->assertNoRaw('<script>alert("menu");</script>');
}
@ -86,8 +115,9 @@ class BlockXssTest extends WebTestBase {
])->save();
$this->drupalGet(Url::fromRoute('block.admin_display'));
$this->clickLink('<script>alert("block_content");</script>');
$this->assertRaw('&lt;script&gt;alert(&quot;block_content&quot;);&lt;/script&gt;');
$this->clickLinkPartialName('Place block');
// The block admin label is automatically XSS admin filtered.
$this->assertRaw('alert("block_content");');
$this->assertNoRaw('<script>alert("block_content");</script>');
}

View file

@ -0,0 +1,183 @@
<?php
/**
* @file
* Contains \Drupal\block\Tests\Migrate\d6\MigrateBlockTest.
*/
namespace Drupal\block\Tests\Migrate\d6;
use Drupal\block\Entity\Block;
use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
/**
* Upgrade block settings to block.block.*.yml.
*
* @group block
*/
class MigrateBlockTest extends MigrateDrupal6TestBase {
/**
* Modules to enable.
*
* @var array
*/
static $modules = array(
'block',
'views',
'comment',
'menu_ui',
'block_content',
'node',
);
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('block_content');
$entities = array(
entity_create('menu', array('id' => 'primary-links')),
entity_create('menu', array('id' => 'secondary-links')),
entity_create('block_content', array('id' => 1, 'type' => 'basic', 'info' => $this->randomMachineName(8))),
entity_create('block_content', array('id' => 2, 'type' => 'basic', 'info' => $this->randomMachineName(8))),
);
foreach ($entities as $entity) {
$entity->enforceIsNew(TRUE);
$entity->save();
}
$this->prepareMigrations(array(
'd6_custom_block' => array(
array(array(1), array(1)),
array(array(2), array(2)),
),
'd6_menu' => array(
array(array('menu1'), array('menu')),
),
'd6_user_role' => array(
array(array(2), array('authenticated')),
array(array(3), array('migrate_test_role_1')),
),
));
// Set Bartik and Seven as the default public and admin theme.
$config = $this->config('system.theme');
$config->set('default', 'bartik');
$config->set('admin', 'seven');
$config->save();
// Install one of D8's test themes.
\Drupal::service('theme_handler')->install(array('test_theme'));
$this->loadDumps(['Blocks.php', 'BlocksRoles.php', 'AggregatorFeed.php']);
$this->executeMigration('d6_block');
}
/**
* Test the block settings migration.
*/
public function testBlockMigration() {
$blocks = Block::loadMultiple();
$this->assertIdentical(count($blocks), 10);
// User blocks
$test_block_user = $blocks['user'];
$this->assertNotNull($test_block_user);
$this->assertIdentical('sidebar_first', $test_block_user->getRegion());
$this->assertIdentical('bartik', $test_block_user->getTheme());
$visibility = $test_block_user->getVisibility();
$this->assertTrue(empty($visibility));
$this->assertIdentical(0, $test_block_user->getWeight());
$test_block_user_1 = $blocks['user_1'];
$this->assertNotNull($test_block_user_1);
$this->assertIdentical('sidebar_first', $test_block_user_1->getRegion());
$this->assertIdentical('bartik', $test_block_user_1->getTheme());
$visibility = $test_block_user_1->getVisibility();
$this->assertTrue(empty($visibility));
$this->assertIdentical(0, $test_block_user_1->getWeight());
$test_block_user_2 = $blocks['user_2'];
$this->assertNotNull($test_block_user_2);
$this->assertIdentical('sidebar_second', $test_block_user_2->getRegion());
$this->assertIdentical('bartik', $test_block_user_2->getTheme());
$visibility = $test_block_user_2->getVisibility();
$this->assertIdentical($visibility['user_role']['id'], 'user_role');
$roles = array();
$roles['authenticated'] = 'authenticated';
$this->assertIdentical($visibility['user_role']['roles'], $roles);
$this->assertFalse($visibility['user_role']['negate']);
$this->assertIdentical(-9, $test_block_user_2->getWeight());
$test_block_user_3 = $blocks['user_3'];
$this->assertNotNull($test_block_user_3);
$this->assertIdentical('sidebar_second', $test_block_user_3->getRegion());
$this->assertIdentical('bartik', $test_block_user_3->getTheme());
$visibility = $test_block_user_3->getVisibility();
$this->assertIdentical($visibility['user_role']['id'], 'user_role');
$roles = array();
$roles['migrate_test_role_1'] = 'migrate_test_role_1';
$this->assertIdentical($visibility['user_role']['roles'], $roles);
$this->assertFalse($visibility['user_role']['negate']);
$this->assertIdentical(-6, $test_block_user_3->getWeight());
// Check system block
$test_block_system = $blocks['system'];
$this->assertNotNull($test_block_system);
$this->assertIdentical('footer', $test_block_system->getRegion());
$this->assertIdentical('bartik', $test_block_system->getTheme());
$visibility = $test_block_system->getVisibility();
$this->assertIdentical('request_path', $visibility['request_path']['id']);
$this->assertIdentical('node/1', $visibility['request_path']['pages']);
$this->assertTrue($visibility['request_path']['negate']);
$this->assertIdentical(-5, $test_block_system->getWeight());
// Check menu blocks
$test_block_menu = $blocks['menu'];
$this->assertNotNull($test_block_menu);
$this->assertIdentical('header', $test_block_menu->getRegion());
$this->assertIdentical('bartik', $test_block_menu->getTheme());
$visibility = $test_block_menu->getVisibility();
$this->assertTrue(empty($visibility));
$this->assertIdentical(-5, $test_block_menu->getWeight());
// Check custom blocks
$test_block_block = $blocks['block'];
$this->assertNotNull($test_block_block);
$this->assertIdentical('content', $test_block_block->getRegion());
$this->assertIdentical('bartik', $test_block_block->getTheme());
$visibility = $test_block_block->getVisibility();
$this->assertIdentical('request_path', $visibility['request_path']['id']);
$this->assertIdentical('<front>', $visibility['request_path']['pages']);
$this->assertFalse($visibility['request_path']['negate']);
$this->assertIdentical(0, $test_block_block->getWeight());
$test_block_block_1 = $blocks['block_1'];
$this->assertNotNull($test_block_block_1);
$this->assertIdentical('right', $test_block_block_1->getRegion());
$this->assertIdentical('bluemarine', $test_block_block_1->getTheme());
$visibility = $test_block_block_1->getVisibility();
$this->assertIdentical('request_path', $visibility['request_path']['id']);
$this->assertIdentical('node', $visibility['request_path']['pages']);
$this->assertFalse($visibility['request_path']['negate']);
$this->assertIdentical(-4, $test_block_block_1->getWeight());
$test_block_block_2 = $blocks['block_2'];
$this->assertNotNull($test_block_block_2);
$this->assertIdentical('right', $test_block_block_2->getRegion());
$this->assertIdentical('test_theme', $test_block_block_2->getTheme());
$visibility = $test_block_block_2->getVisibility();
$this->assertTrue(empty($visibility));
$this->assertIdentical(-7, $test_block_block_2->getWeight());
$test_block_block_3 = $blocks['block_3'];
$this->assertNotNull($test_block_block_3);
$this->assertIdentical('left', $test_block_block_3->getRegion());
$this->assertIdentical('test_theme', $test_block_block_3->getTheme());
$visibility = $test_block_block_3->getVisibility();
$this->assertTrue(empty($visibility));
$this->assertIdentical(-2, $test_block_block_3->getWeight());
}
}

View file

@ -0,0 +1,110 @@
<?php
/**
* @file
* Contains \Drupal\block\Tests\Update\BlockContextMappingUpdateTest.
*/
namespace Drupal\block\Tests\Update;
use Drupal\block\Entity\Block;
use Drupal\node\Entity\Node;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests the upgrade path for block context mapping renames.
*
* @see https://www.drupal.org/node/2354889
*
* @group Update
*/
class BlockContextMappingUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['block_test', 'language'];
/**
* {@inheritdoc}
*/
protected function setUp() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8.block-context-manager-2354889.php',
];
parent::setUp();
}
/**
* Tests that block context mapping is updated properly.
*/
public function testUpdateHookN() {
$this->runUpdates();
$this->assertRaw('Encountered an unknown context mapping key coming probably from a contributed or custom module: One or more mappings could not be updated. Please manually review your visibility settings for the following blocks, which are disabled now:<ul><li>User login (Visibility: Baloney spam)</li></ul>');
// Disable maintenance mode.
\Drupal::state()->set('system.maintenance_mode', FALSE);
// We finished updating so we can login the user now.
$this->drupalLogin($this->rootUser);
// The block that we are testing has the following visibility rules:
// - only visible on node pages
// - only visible to authenticated users.
$block_title = 'Test for 2354889';
// Create two nodes, a page and an article.
$page = Node::create([
'type' => 'page',
'title' => 'Page node',
]);
$page->save();
$article = Node::create([
'type' => 'article',
'title' => 'Article node',
]);
$article->save();
// Check that the block appears only on Page nodes for authenticated users.
$this->drupalGet('node/' . $page->id());
$this->assertRaw($block_title, 'Test block is visible on a Page node as an authenticated user.');
$this->drupalGet('node/' . $article->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an authenticated user.');
$this->drupalLogout();
// Check that the block does not appear on any page for anonymous users.
$this->drupalGet('node/' . $page->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Page node as an anonymous user.');
$this->drupalGet('node/' . $article->id());
$this->assertNoRaw($block_title, 'Test block is not visible on a Article node as an anonymous user.');
// Ensure that all the context mappings got updated properly.
$block = Block::load('testfor2354889');
$visibility = $block->get('visibility');
$this->assertEqual('@node.node_route_context:node', $visibility['node_type']['context_mapping']['node']);
$this->assertEqual('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']);
$this->assertEqual('@language.current_language_context:language_interface', $visibility['language']['context_mapping']['language']);
// Check that a block with invalid context is being disabled and that it can
// still be edited afterward.
$disabled_block = Block::load('thirdtestfor2354889');
$this->assertFalse($disabled_block->status(), 'Block with invalid context is disabled');
$this->assertEqual(['thirdtestfor2354889' => ['missing_context_ids' => ['baloney.spam' => ['node_type']], 'status' => TRUE]], \Drupal::keyValue('update_backup')->get('block_update_8001'));
$disabled_block_visibility = $disabled_block->get('visibility');
$this->assertTrue(!isset($disabled_block_visibility['node_type']), 'The problematic visibility condition has been removed.');
$admin_user = $this->drupalCreateUser(['administer blocks']);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/structure/block/manage/thirdtestfor2354889');
$this->assertResponse('200');
}
}

View file

@ -8,8 +8,6 @@
namespace Drupal\block\Tests\Views;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\views\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
@ -60,19 +58,20 @@ class DisplayBlockTest extends ViewTestBase {
$edit['block[style][row_plugin]'] = 'fields';
$this->drupalPostForm('admin/structure/views/add', $edit, t('Save and edit'));
$pattern = '//tr[.//td[text()=:category] and .//td//a[contains(@href, :href)]]';
// Test that the block was given a default category corresponding to its
// base table.
$arguments = array(
':id' => 'edit-category-lists-views',
':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-1',
':href' => \Drupal::Url('block.admin_add', array(
'plugin_id' => 'views_block:' . $edit['id'] . '-block_1',
'theme' => 'classy',
)),
':text' => $edit['label'],
':category' => t('Lists (Views)'),
);
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the category for its base table.');
// Duplicate the block before changing the category.
@ -81,10 +80,9 @@ class DisplayBlockTest extends ViewTestBase {
// Change the block category to a random string.
$this->drupalGet('admin/structure/views/view/' . $edit['id'] . '/edit/block_1');
$label = t('Lists (Views)');
$link = $this->xpath('//a[@id="views-block-1-block-category" and normalize-space(text())=:label]', array(':label' => $label));
$link = $this->xpath('//a[@id="views-block-1-block-category" and normalize-space(text())=:category]', $arguments);
$this->assertTrue(!empty($link));
$this->clickLink($label);
$this->clickLink(t('Lists (Views)'));
$category = $this->randomString();
$this->drupalPostForm(NULL, array('block_category' => $category), t('Apply'));
@ -95,34 +93,30 @@ class DisplayBlockTest extends ViewTestBase {
$this->drupalPostForm(NULL, array(), t('Save'));
// Test that the blocks are listed under the correct categories.
$category_id = Html::getUniqueId('edit-category-' . SafeMarkup::checkPlain($category));
$arguments[':id'] = $category_id;
$arguments[':category'] = $category;
$this->drupalGet('admin/structure/block');
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$this->clickLinkPartialName('Place block');
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The test block appears in the custom category.');
$arguments = array(
':id' => 'edit-category-lists-views',
':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-2',
':href' => \Drupal::Url('block.admin_add', array(
'plugin_id' => 'views_block:' . $edit['id'] . '-block_2',
'theme' => 'classy',
)),
':text' => $edit['label'],
':category' => t('Lists (Views)'),
);
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The first duplicated test block remains in the original category.');
$arguments = array(
':id' => $category_id,
':li_class' => 'views-block' . Html::getClass($edit['id']) . '-block-3',
':href' => \Drupal::Url('block.admin_add', array(
'plugin_id' => 'views_block:' . $edit['id'] . '-block_3',
'theme' => 'classy',
)),
':text' => $edit['label'],
':category' => $category,
);
$elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue(!empty($elements), 'The second duplicated test block appears in the custom category.');
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\block_test\Plugin\Condition\BaloneySpam.
*/
namespace Drupal\block_test\Plugin\Condition;
use Drupal\Core\Condition\ConditionPluginBase;
/**
* Provides a 'baloney.spam' condition.
*
* @Condition(
* id = "baloney.spam",
* label = @Translation("Baloney spam"),
* )
*
*/
class BaloneySpam extends ConditionPluginBase {
/**
* {@inheritdoc}
*/
public function evaluate() {
return TRUE;
}
/**
* {@inheritdoc}
*/
public function summary() {
return 'Summary';
}
}

View file

@ -30,13 +30,6 @@ class BlockFormTest extends UnitTestCase {
*/
protected $storage;
/**
* The event dispatcher service.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $dispatcher;
/**
* The language manager service.
*
@ -59,6 +52,13 @@ class BlockFormTest extends UnitTestCase {
*/
protected $entityManager;
/**
* The mocked context repository.
*
* @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $contextRepository;
/**
* {@inheritdoc}
*/
@ -67,7 +67,7 @@ class BlockFormTest extends UnitTestCase {
$this->conditionManager = $this->getMock('Drupal\Core\Executable\ExecutableManagerInterface');
$this->language = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->contextRepository = $this->getMock('Drupal\Core\Plugin\Context\ContextRepositoryInterface');
$this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
$this->storage = $this->getMock('Drupal\Core\Config\Entity\ConfigEntityStorageInterface');
@ -104,7 +104,7 @@ class BlockFormTest extends UnitTestCase {
->method('getQuery')
->will($this->returnValue($query));
$block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->dispatcher, $this->language, $this->themeHandler);
$block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->contextRepository, $this->language, $this->themeHandler);
// Ensure that the block with just one other instance gets the next available
// name suggestion.

View file

@ -8,6 +8,7 @@
namespace Drupal\Tests\block\Unit;
use Drupal\block\BlockRepository;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Tests\UnitTestCase;
@ -59,7 +60,7 @@ class BlockRepositoryTest extends UnitTestCase {
]);
$theme_manager = $this->getMock('Drupal\Core\Theme\ThemeManagerInterface');
$theme_manager->expects($this->once())
$theme_manager->expects($this->atLeastOnce())
->method('getActiveTheme')
->will($this->returnValue($active_theme));
@ -84,15 +85,18 @@ class BlockRepositoryTest extends UnitTestCase {
$blocks = [];
foreach ($blocks_config as $block_id => $block_config) {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->once())
->method('setContexts')
->willReturnSelf();
$block->expects($this->once())
->method('access')
->will($this->returnValue($block_config[0]));
$block->expects($block_config[0] ? $this->atLeastOnce() : $this->never())
->method('getRegion')
->willReturn($block_config[1]);
$block->expects($this->any())
->method('label')
->willReturn($block_id);
$block->expects($this->any())
->method('getWeight')
->willReturn($block_config[2]);
$blocks[$block_id] = $block;
}
@ -101,30 +105,34 @@ class BlockRepositoryTest extends UnitTestCase {
->with(['theme' => $this->theme])
->willReturn($blocks);
$result = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion([]) as $region => $resulting_blocks) {
$cacheable_metadata = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
$result[$region] = [];
foreach ($resulting_blocks as $plugin_id => $block) {
$result[$region][] = $plugin_id;
}
}
$this->assertSame($result, $expected_blocks);
$this->assertEquals($expected_blocks, $result);
}
public function providerBlocksConfig() {
$blocks_config = array(
'block1' => array(
TRUE, 'top', 0
AccessResult::allowed(), 'top', 0
),
// Test a block without access.
'block2' => array(
FALSE, 'bottom', 0
),
// Test two blocks in the same region with specific weight.
'block3' => array(
TRUE, 'bottom', 5
AccessResult::forbidden(), 'bottom', 0
),
// Test some blocks in the same region with specific weight.
'block4' => array(
TRUE, 'bottom', -5
AccessResult::allowed(), 'bottom', 5
),
'block3' => array(
AccessResult::allowed(), 'bottom', 5
),
'block5' => array(
AccessResult::allowed(), 'bottom', -5
),
);
@ -133,7 +141,7 @@ class BlockRepositoryTest extends UnitTestCase {
[
'top' => ['block1'],
'center' => [],
'bottom' => ['block4', 'block3'],
'bottom' => ['block5', 'block3', 'block4'],
]
];
return $test_cases;
@ -146,24 +154,21 @@ class BlockRepositoryTest extends UnitTestCase {
*/
public function testGetVisibleBlocksPerRegionWithContext() {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->once())
->method('setContexts')
->willReturnSelf();
$block->expects($this->once())
->method('access')
->willReturn(TRUE);
->willReturn(AccessResult::allowed()->addCacheTags(['config:block.block.block_id']));
$block->expects($this->once())
->method('getRegion')
->willReturn('top');
$blocks['block_id'] = $block;
$contexts = [];
$this->blockStorage->expects($this->once())
->method('loadByProperties')
->with(['theme' => $this->theme])
->willReturn($blocks);
$result = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($contexts) as $region => $resulting_blocks) {
$cacheable_metadata = [];
foreach ($this->blockRepository->getVisibleBlocksPerRegion($cacheable_metadata) as $region => $resulting_blocks) {
$result[$region] = [];
foreach ($resulting_blocks as $plugin_id => $block) {
$result[$region][] = $plugin_id;
@ -177,6 +182,10 @@ class BlockRepositoryTest extends UnitTestCase {
'bottom' => [],
];
$this->assertSame($expected, $result);
// Assert that the cacheable metadata from the block access results was
// collected.
$this->assertEquals(['config:block.block.block_id'], $cacheable_metadata['top']->getCacheTags());
}
}

View file

@ -7,6 +7,8 @@
namespace Drupal\Tests\block\Unit\Plugin\DisplayVariant;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\DependencyInjection\Container;
use Drupal\Tests\UnitTestCase;
/**
@ -29,13 +31,6 @@ class BlockPageVariantTest extends UnitTestCase {
*/
protected $blockViewBuilder;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $dispatcher;
/**
* The plugin context handler.
*
@ -55,14 +50,23 @@ class BlockPageVariantTest extends UnitTestCase {
* A mocked display variant plugin.
*/
public function setUpDisplayVariant($configuration = array(), $definition = array()) {
$container = new Container();
$cache_context_manager = $this->getMockBuilder('Drupal\Core\Cache\CacheContextsManager')
->disableOriginalConstructor()
->getMock();
$container->set('cache_contexts_manager', $cache_context_manager);
$cache_context_manager->expects($this->any())
->method('validateTokens')
->with([])
->willReturn([]);
\Drupal::setContainer($container);
$this->blockRepository = $this->getMock('Drupal\block\BlockRepositoryInterface');
$this->blockViewBuilder = $this->getMock('Drupal\Core\Entity\EntityViewBuilderInterface');
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->dispatcher->expects($this->any())
->method('dispatch')
->willReturnArgument(1);
return $this->getMockBuilder('Drupal\block\Plugin\DisplayVariant\BlockPageVariant')
->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, $this->dispatcher, ['config:block_list']))
->setConstructorArgs(array($configuration, 'test', $definition, $this->blockRepository, $this->blockViewBuilder, ['config:block_list']))
->setMethods(array('getRegionNames'))
->getMock();
}
@ -96,7 +100,10 @@ class BlockPageVariantTest extends UnitTestCase {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
@ -121,7 +128,10 @@ class BlockPageVariantTest extends UnitTestCase {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
@ -152,7 +162,10 @@ class BlockPageVariantTest extends UnitTestCase {
'#cache' => [
'tags' => [
'config:block_list',
'route',
],
'contexts' => [],
'max-age' => -1,
],
'top' => [
'block1' => [],
@ -194,20 +207,26 @@ class BlockPageVariantTest extends UnitTestCase {
$messages_block_plugin = $this->getMock('Drupal\Core\Block\MessagesBlockPluginInterface');
foreach ($blocks_config as $block_id => $block_config) {
$block = $this->getMock('Drupal\block\BlockInterface');
$block->expects($this->any())
->method('getContexts')
->willReturn([]);
$block->expects($this->atLeastOnce())
->method('getPlugin')
->willReturn($block_config[1] ? $main_content_block_plugin : ($block_config[2] ? $messages_block_plugin : $block_plugin));
$blocks[$block_config[0]][$block_id] = $block;
}
$this->blockViewBuilder->expects($this->exactly($visible_block_count))
->method('view')
->will($this->returnValue(array()));
$this->blockRepository->expects($this->once())
->method('getVisibleBlocksPerRegion')
->will($this->returnValue($blocks));
->willReturnCallback(function (&$cacheable_metadata) use ($blocks) {
$cacheable_metadata['top'] = (new CacheableMetadata())->addCacheTags(['route']);
return $blocks;
});
$this->assertSame($expected_render_array, $display_variant->build());
$value = $display_variant->build();
$this->assertSame($expected_render_array, $value);
}
/**
@ -226,6 +245,8 @@ class BlockPageVariantTest extends UnitTestCase {
'tags' => [
'config:block_list',
],
'contexts' => [],
'max-age' => -1,
],
'content' => [
'system_main' => [],

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\Tests\block\Unit\Plugin\migrate\source\d6\BlockTest.
*/
namespace Drupal\Tests\block\Unit\Plugin\migrate\source\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests D6 block source plugin.
*
* @coversDefaultClass \Drupal\block\Plugin\migrate\source\d6\Block
* @group block
*/
class BlockTest extends MigrateSqlSourceTestCase {
// The plugin system is not working during unit testing so the source plugin
// class needs to be manually specified.
const PLUGIN_CLASS = 'Drupal\block\Plugin\migrate\source\d6\Block';
// The fake Migration configuration entity.
protected $migrationConfiguration = array(
// The ID of the entity, can be any string.
'id' => 'test',
'idlist' => array(),
'source' => array(
'plugin' => 'd6_block',
),
);
/**
* Sample block instance query results from the source.
*/
protected $expectedResults = array(
array(
'bid' => 1,
'module' => 'block',
'delta' => '1',
'theme' => 'garland',
'status' => 1,
'weight' => 0,
'region' => 'left',
'visibility' => 0,
'pages' => '',
'title' => 'Test Title 01',
'cache' => -1,
),
array(
'bid' => 2,
'module' => 'block',
'delta' => '2',
'theme' => 'garland',
'status' => 1,
'weight' => 5,
'region' => 'right',
'visibility' => 0,
'pages' => '<front>',
'title' => 'Test Title 02',
'cache' => -1,
),
);
/**
* Sample block roles table.
*/
protected $expectedBlocksRoles = array(
array(
'module' => 'block',
'delta' => 1,
'rid' => 2,
),
);
/**
* Prepopulate database contents.
*/
protected function setUp() {
$this->databaseContents['blocks'] = $this->expectedResults;
$this->databaseContents['blocks_roles'] = $this->expectedBlocksRoles;
parent::setUp();
}
}