Issue #2512456 by tim.plunkett, legolasbo, MattA, dawehner, googletorp, darol100, Bojhan, webchick, andypost, ivanstegic, larowlan, lauriii, LewisNyman, tkoleary, rickvug, eliza411, lunk_rat, nickrosencrans, stpaultim, Mark LaCroix: Implement the new block layout design to emphasize the primary interaction of placing a block
							parent
							
								
									2378f939b2
								
							
						
					
					
						commit
						6b95f9a850
					
				| 
						 | 
				
			
			@ -58,9 +58,6 @@ function block_theme() {
 | 
			
		|||
    'block' => array(
 | 
			
		||||
      'render element' => 'elements',
 | 
			
		||||
    ),
 | 
			
		||||
    'block_list' => array(
 | 
			
		||||
      'render element' => 'form',
 | 
			
		||||
    ),
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -170,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">',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@
 | 
			
		|||
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;
 | 
			
		||||
| 
						 | 
				
			
			@ -44,13 +43,6 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
   */
 | 
			
		||||
  protected $request;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The block manager.
 | 
			
		||||
   *
 | 
			
		||||
   * @var \Drupal\Core\Block\BlockManagerInterface
 | 
			
		||||
   */
 | 
			
		||||
  protected $blockManager;
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The theme manager.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			@ -77,17 +69,14 @@ 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, ThemeManagerInterface $theme_manager, FormBuilderInterface $form_builder) {
 | 
			
		||||
  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;
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			@ -99,7 +88,6 @@ 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')
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			@ -135,7 +123,6 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
   * {@inheritdoc}
 | 
			
		||||
   */
 | 
			
		||||
  public function buildForm(array $form, FormStateInterface $form_state) {
 | 
			
		||||
    $form['#theme'] = array('block_list');
 | 
			
		||||
    $form['#attached']['library'][] = 'core/drupal.tableheader';
 | 
			
		||||
    $form['#attached']['library'][] = 'block/drupal.block';
 | 
			
		||||
    $form['#attached']['library'][] = 'block/drupal.block.admin';
 | 
			
		||||
| 
						 | 
				
			
			@ -143,7 +130,6 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
 | 
			
		||||
    // Build the form tree.
 | 
			
		||||
    $form['blocks'] = $this->buildBlocksForm();
 | 
			
		||||
    $form['place_blocks'] = $this->buildPlaceBlocksForm();
 | 
			
		||||
 | 
			
		||||
    $form['actions'] = array(
 | 
			
		||||
      '#tree' => FALSE,
 | 
			
		||||
| 
						 | 
				
			
			@ -206,7 +192,7 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
 | 
			
		||||
    // Loop over each region and build blocks.
 | 
			
		||||
    $regions = $this->systemRegionList($this->getThemeName(), REGIONS_VISIBLE);
 | 
			
		||||
    $block_regions_with_disabled = $regions + array(BlockInterface::BLOCK_REGION_NONE => BlockInterface::BLOCK_REGION_NONE);
 | 
			
		||||
    $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['#tabledrag'][] = array(
 | 
			
		||||
        'action' => 'match',
 | 
			
		||||
| 
						 | 
				
			
			@ -229,10 +215,20 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
        ),
 | 
			
		||||
      );
 | 
			
		||||
      $form['region-' . $region]['title'] = array(
 | 
			
		||||
        '#markup' => $region != BlockInterface::BLOCK_REGION_NONE ? $title : $this->t('Disabled', array(), array('context' => 'Plural')),
 | 
			
		||||
        '#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['region-' . $region . '-message'] = array(
 | 
			
		||||
| 
						 | 
				
			
			@ -312,78 +308,6 @@ class BlockListBuilder extends ConfigEntityListBuilder implements FormInterface
 | 
			
		|||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Builds the "Place Blocks" portion of the form.
 | 
			
		||||
   *
 | 
			
		||||
   * @return array
 | 
			
		||||
   */
 | 
			
		||||
  protected function buildPlaceBlocksForm() {
 | 
			
		||||
    $form['title'] = array(
 | 
			
		||||
      '#type' => 'container',
 | 
			
		||||
      '#markup' => '<h3>' . $this->t('Place blocks') . '</h3>',
 | 
			
		||||
      '#attributes' => array(
 | 
			
		||||
        'class' => array(
 | 
			
		||||
          'entity-meta__header',
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $form['filter'] = array(
 | 
			
		||||
      '#type' => 'search',
 | 
			
		||||
      '#title' => $this->t('Filter'),
 | 
			
		||||
      '#title_display' => 'invisible',
 | 
			
		||||
      '#size' => 30,
 | 
			
		||||
      '#placeholder' => $this->t('Filter by block name'),
 | 
			
		||||
      '#attributes' => array(
 | 
			
		||||
        'class' => array('block-filter-text'),
 | 
			
		||||
        'data-element' => '.entity-meta',
 | 
			
		||||
        'title' => $this->t('Enter a part of the block name to filter by.'),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    $form['list']['#type'] = 'container';
 | 
			
		||||
    $form['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['list'][$category_key])) {
 | 
			
		||||
        $form['list'][$category_key] = array(
 | 
			
		||||
          '#type' => 'details',
 | 
			
		||||
          '#title' => $category,
 | 
			
		||||
          '#open' => TRUE,
 | 
			
		||||
          'content' => array(
 | 
			
		||||
            '#theme' => 'links',
 | 
			
		||||
            '#links' => array(),
 | 
			
		||||
            '#attributes' => array(
 | 
			
		||||
              'class' => array(
 | 
			
		||||
                'block-list',
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      $form['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,
 | 
			
		||||
          )),
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    return $form;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Gets the name of the theme used for this block listing.
 | 
			
		||||
   *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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']);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -139,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().
 | 
			
		||||
| 
						 | 
				
			
			@ -154,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().');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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('<script>alert("view");</script>');
 | 
			
		||||
    $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('<script>alert("menu");</script>');
 | 
			
		||||
    $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('<script>alert("block_content");</script>');
 | 
			
		||||
    $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>');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,7 +8,6 @@ block_content_add_action:
 | 
			
		|||
  route_name: block_content.add_page
 | 
			
		||||
  title: 'Add custom block'
 | 
			
		||||
  appears_on:
 | 
			
		||||
    - block.admin_display
 | 
			
		||||
    - block.admin_display_theme
 | 
			
		||||
    - block.admin_library
 | 
			
		||||
    - entity.block_content.collection
 | 
			
		||||
  class: \Drupal\block_content\Plugin\Menu\LocalAction\BlockContentAddLocalAction
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -189,6 +189,7 @@ class BlockContentTypeTest extends BlockContentTestBase {
 | 
			
		|||
        // block configure form.
 | 
			
		||||
        $path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
 | 
			
		||||
        $this->drupalGet($path);
 | 
			
		||||
        $this->clickLinkPartialName('Place block');
 | 
			
		||||
        $this->clickLink(t('Add custom block'));
 | 
			
		||||
        // The seven theme has markup inside the link, we cannot use clickLink().
 | 
			
		||||
        if ($default_theme == 'seven') {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -219,6 +219,7 @@ class MenuTest extends MenuWebTestBase {
 | 
			
		|||
 | 
			
		||||
    // Confirm that the custom menu block is available.
 | 
			
		||||
    $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($label);
 | 
			
		||||
 | 
			
		||||
    // Enable the block.
 | 
			
		||||
| 
						 | 
				
			
			@ -532,6 +533,7 @@ class MenuTest extends MenuWebTestBase {
 | 
			
		|||
    // Make sure menu shows up with new name in block addition.
 | 
			
		||||
    $default_theme = $this->config('system.theme')->get('default');
 | 
			
		||||
    $this->drupalget('admin/structure/block/list/' . $default_theme);
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($edit['label']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,6 +36,7 @@ class SearchBlockTest extends SearchTestBase {
 | 
			
		|||
 | 
			
		||||
    // Test availability of the search block in the admin "Place blocks" list.
 | 
			
		||||
    $this->drupalGet('admin/structure/block');
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertLinkByHref('/admin/structure/block/add/search_form_block/classy', 0,
 | 
			
		||||
      'Did not find the search block in block candidate list.');
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2324,7 +2324,7 @@ abstract class WebTestBase extends TestBase {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Follows a link by name.
 | 
			
		||||
   * Follows a link by complete name.
 | 
			
		||||
   *
 | 
			
		||||
   * Will click the first link found with this link text by default, or a later
 | 
			
		||||
   * one if an index is given. Match is case sensitive with normalized space.
 | 
			
		||||
| 
						 | 
				
			
			@ -2332,17 +2332,54 @@ abstract class WebTestBase extends TestBase {
 | 
			
		|||
   *
 | 
			
		||||
   * If the link is discovered and clicked, the test passes. Fail otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @param $label
 | 
			
		||||
   * @param string $label
 | 
			
		||||
   *   Text between the anchor tags.
 | 
			
		||||
   * @param $index
 | 
			
		||||
   * @param int $index
 | 
			
		||||
   *   Link position counting from zero.
 | 
			
		||||
   *
 | 
			
		||||
   * @return
 | 
			
		||||
   * @return string|bool
 | 
			
		||||
   *   Page contents on success, or FALSE on failure.
 | 
			
		||||
   */
 | 
			
		||||
  protected function clickLink($label, $index = 0) {
 | 
			
		||||
    return $this->clickLinkHelper($label, $index, '//a[normalize-space()=:label]');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Follows a link by partial name.
 | 
			
		||||
   *
 | 
			
		||||
   *
 | 
			
		||||
   * If the link is discovered and clicked, the test passes. Fail otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $label
 | 
			
		||||
   *   Text between the anchor tags, uses starts-with().
 | 
			
		||||
   * @param int $index
 | 
			
		||||
   *   Link position counting from zero.
 | 
			
		||||
   *
 | 
			
		||||
   * @return string|bool
 | 
			
		||||
   *   Page contents on success, or FALSE on failure.
 | 
			
		||||
   *
 | 
			
		||||
   * @see ::clickLink()
 | 
			
		||||
   */
 | 
			
		||||
  protected function clickLinkPartialName($label, $index = 0) {
 | 
			
		||||
    return $this->clickLinkHelper($label, $index, '//a[starts-with(normalize-space(), :label)]');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Provides a helper for ::clickLink() and ::clickLinkPartialName().
 | 
			
		||||
   *
 | 
			
		||||
   * @param string $label
 | 
			
		||||
   *   Text between the anchor tags, uses starts-with().
 | 
			
		||||
   * @param int $index
 | 
			
		||||
   *   Link position counting from zero.
 | 
			
		||||
   * @param string $pattern
 | 
			
		||||
   *   A pattern to use for the XPath.
 | 
			
		||||
   *
 | 
			
		||||
   * @return bool|string
 | 
			
		||||
   *   Page contents on success, or FALSE on failure.
 | 
			
		||||
   */
 | 
			
		||||
  protected function clickLinkHelper($label, $index, $pattern) {
 | 
			
		||||
    $url_before = $this->getUrl();
 | 
			
		||||
    $urls = $this->xpath('//a[normalize-space()=:label]', array(':label' => $label));
 | 
			
		||||
    $urls = $this->xpath($pattern, array(':label' => $label));
 | 
			
		||||
    if (isset($urls[$index])) {
 | 
			
		||||
      $url_target = $this->getAbsoluteUrl($urls[$index]['href']);
 | 
			
		||||
      $this->pass(SafeMarkup::format('Clicked link %label (@url_target) from @url_before', array('%label' => $label, '@url_target' => $url_target, '@url_before' => $url_before)), 'Browser');
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,6 +133,7 @@ class BasicTest extends WizardTestBase {
 | 
			
		|||
 | 
			
		||||
    // Confirm that the block is available in the block administration UI.
 | 
			
		||||
    $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($view3['label']);
 | 
			
		||||
 | 
			
		||||
    // Place the block.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -71,6 +71,7 @@ class ItemsPerPageTest extends WizardTestBase {
 | 
			
		|||
 | 
			
		||||
    // Confirm that the block is listed in the block administration UI.
 | 
			
		||||
    $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($view['label']);
 | 
			
		||||
 | 
			
		||||
    // Place the block, visit a page that displays the block, and check that the
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -50,6 +50,7 @@ class OverrideDisplaysTest extends UITestBase {
 | 
			
		|||
 | 
			
		||||
    // Confirm that the view block is available in the block administration UI.
 | 
			
		||||
    $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($view['label']);
 | 
			
		||||
 | 
			
		||||
    // Place the block.
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +110,7 @@ class OverrideDisplaysTest extends UITestBase {
 | 
			
		|||
 | 
			
		||||
    // Confirm that the block is available in the block administration UI.
 | 
			
		||||
    $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
 | 
			
		||||
    $this->clickLinkPartialName('Place block');
 | 
			
		||||
    $this->assertText($view['label']);
 | 
			
		||||
 | 
			
		||||
    // Put the block into the first sidebar region, and make sure it will not
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue