Update to Drupal 8.2.0. For more information, see https://www.drupal.org/project/drupal/releases/8.2.0

This commit is contained in:
Pantheon Automation 2016-10-06 15:16:20 -07:00 committed by Greg Anderson
parent 2f563ab520
commit f1c8716f57
1732 changed files with 52334 additions and 11780 deletions

View file

@ -1,7 +1,8 @@
id: d6_action_settings
id: action_settings
label: Action configuration
migration_tags:
- Drupal 6
- Drupal 7
source:
plugin: variable
variables:

View file

@ -84,9 +84,9 @@ class EmailAction extends ConfigurableActionBase implements ContainerFactoryPlug
* The entity manager.
* @param \Psr\Log\LoggerInterface $logger
* A logger instance.
* @param \Drupal\Core\Mail\MailManagerInterface
* @param \Drupal\Core\Mail\MailManagerInterface $mail_manager
* The mail manager.
* @param \Drupal\Core\Language\LanguageManagerInterface
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Egulias\EmailValidator\EmailValidator $email_validator
* The email validator.

View file

@ -132,7 +132,7 @@ display:
entity_type: node
filters:
status:
value: true
value: '1'
table: node_field_data
field: status
id: status

View file

@ -25,7 +25,9 @@ class ActionUninstallTest extends BrowserTestBase {
public function testActionUninstall() {
\Drupal::service('module_installer')->uninstall(array('action'));
$this->assertTrue(entity_load('action', 'user_block_user_action', TRUE), 'Configuration entity \'user_block_user_action\' still exists after uninstalling action module.' );
$storage = $this->container->get('entity_type.manager')->getStorage('action');
$storage->resetCache(['user_block_user_action']);
$this->assertTrue($storage->load('user_block_user_action'), 'Configuration entity \'user_block_user_action\' still exists after uninstalling action module.' );
$admin_user = $this->drupalCreateUser(array('administer users'));
$this->drupalLogin($admin_user);

View file

@ -68,7 +68,7 @@ class BulkFormTest extends BrowserTestBase {
// Set all nodes to sticky and check that.
$edit += array('action' => 'node_make_sticky_action');
$this->drupalPostForm(NULL, $edit, t('Apply'));
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
foreach ($nodes as $node) {
$changed_node = $node_storage->load($node->id());
@ -82,7 +82,7 @@ class BulkFormTest extends BrowserTestBase {
$this->assertTrue($node->isPublished(), 'The node is published.');
$edit = array('node_bulk_form[0]' => TRUE, 'action' => 'node_unpublish_action');
$this->drupalPostForm(NULL, $edit, t('Apply'));
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
$this->assertText('Unpublish content was applied to 1 item.');
@ -123,7 +123,7 @@ class BulkFormTest extends BrowserTestBase {
// Check the default title.
$this->drupalGet('test_bulk_form');
$result = $this->xpath('//label[@for="edit-action"]');
$this->assertEqual('With selection', $result[0]->getText());
$this->assertEqual('Action', $result[0]->getText());
// Setup up a different bulk form title.
$view = Views::getView('test_bulk_form');
@ -142,7 +142,7 @@ class BulkFormTest extends BrowserTestBase {
$edit["node_bulk_form[$i]"] = TRUE;
}
$edit += array('action' => 'node_delete_action');
$this->drupalPostForm(NULL, $edit, t('Apply'));
$this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
// Make sure we don't show an action message while we are still on the
// confirmation page.
$errors = $this->xpath('//div[contains(@class, "messages--status")]');

View file

@ -3,6 +3,7 @@
namespace Drupal\Tests\action\Functional;
use Drupal\Component\Utility\Crypt;
use Drupal\system\Entity\Action;
use Drupal\Tests\BrowserTestBase;
/**
@ -81,7 +82,7 @@ class ConfigurationTest extends BrowserTestBase {
$this->assertResponse(200);
$this->assertNoText($new_action_label, "Make sure the action label does not appear on the overview page after we've deleted the action.");
$action = entity_load('action', $aid);
$action = Action::load($aid);
$this->assertFalse($action, 'Make sure the action is gone after being deleted.');
}

View file

@ -24,7 +24,7 @@ class MigrateActionConfigsTest extends MigrateDrupal6TestBase {
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('d6_action_settings');
$this->executeMigration('action_settings');
}
/**

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\Tests\action\Kernel\Migrate\d7;
use Drupal\config\Tests\SchemaCheckTestTrait;
use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
/**
* Upgrade variables to action.settings.yml.
*
* @group migrate_drupal_7
*/
class MigrateActionConfigsTest extends MigrateDrupal7TestBase {
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
public static $modules = ['action'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->executeMigration('action_settings');
}
/**
* Tests migration of action variables to action.settings.yml.
*/
public function testActionSettings() {
$config = $this->config('action.settings');
$this->assertSame(28, $config->get('recursion_limit'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'action.settings', $config->get());
}
}

View file

@ -1,11 +1,11 @@
aggregator.admin_overview:
title: 'Feed aggregator'
title: 'Aggregator'
description: 'Add feeds or import OPMLs to gather external content and configure how often they are updated.'
route_name: aggregator.admin_overview
parent: system.admin_config_services
weight: 10
aggregator.page_last:
title: 'Feed aggregator'
title: 'Aggregator'
weight: 5
route_name: aggregator.page_last
aggregator.feed_add:

View file

@ -31,11 +31,11 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) {
$output .= '<dd>' . t('Users view feed content in the <a href=":aggregator">main aggregator display</a>, or by <a href=":aggregator-sources">their source</a> (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the <a href=":admin-block">Blocks administration page</a>.', array(':aggregator' => \Drupal::url('aggregator.page_last'), ':aggregator-sources' => $url->toString(), ':admin-block' => (\Drupal::moduleHandler()->moduleExists('block')) ? \Drupal::url('block.admin_display') : '#')) . '</dd>';
}
$output .= '<dt>' . t('Adding, editing, and deleting feeds') . '</dt>';
$output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href=":feededit">Feed aggregator page</a>.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '</dd>';
$output .= '<dd>' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the <a href=":feededit">Aggregator administration page</a>.', array(':feededit' => \Drupal::url('aggregator.admin_overview'))) . '</dd>';
$output .= '<dt>' . t('Configuring the display of feed items') . '</dt>';
$output .= '<dd>' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the <a href=":settings">Feed aggregator settings page</a>.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '</dd>';
$output .= '<dd>' . t('Administrators can choose how many items are displayed in the listing pages, which HTML tags are allowed in the content of feed items, and whether they should be trimmed to a maximum number of characters on the <a href=":settings">Aggregator settings page</a>.', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '</dd>';
$output .= '<dt>' . t('Discarding old feed items') . '</dt>';
$output .= '<dd>' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the <a href=":settings">Feed aggregator settings page</a>. This requires a correctly configured cron maintenance task (see below).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '<dd>';
$output .= '<dd>' . t('Administrators can choose whether to discard feed items that are older than a specified period of time on the <a href=":settings">Aggregator settings page</a>. This requires a correctly configured cron maintenance task (see below).', array(':settings' => \Drupal::url('aggregator.admin_settings'))) . '<dd>';
$output .= '<dt>' . t('<abbr title="Outline Processor Markup Language">OPML</abbr> integration') . '</dt>';
// Check if the aggregator opml View is enabled.

View file

@ -2,7 +2,7 @@ aggregator.admin_overview:
path: '/admin/config/services/aggregator'
defaults:
_controller: '\Drupal\aggregator\Controller\AggregatorController::adminOverview'
_title: 'Feed aggregator'
_title: 'Aggregator'
requirements:
_permission: 'administer news feeds'
@ -10,7 +10,7 @@ aggregator.admin_settings:
path: '/admin/config/services/aggregator/settings'
defaults:
_form: '\Drupal\aggregator\Form\SettingsForm'
_title: 'Feed aggregator settings'
_title: 'Aggregator settings'
requirements:
_permission: 'administer news feeds'
@ -53,6 +53,6 @@ aggregator.page_last:
path: '/aggregator'
defaults:
_controller: '\Drupal\aggregator\Controller\AggregatorController::pageLast'
_title: 'Feed aggregator'
_title: 'Aggregator'
requirements:
_permission: 'access news feeds'

View file

@ -127,16 +127,16 @@ class Feed extends ContentEntityBase implements FeedInterface {
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['fid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Feed ID'))
->setDescription(t('The ID of the aggregator feed.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The aggregator feed UUID.'))
->setReadOnly(TRUE);
$fields['fid']->setLabel(t('Feed ID'))
->setDescription(t('The ID of the aggregator feed.'));
$fields['uuid']->setDescription(t('The aggregator feed UUID.'));
$fields['langcode']->setLabel(t('Language code'))
->setDescription(t('The feed language code.'));
$fields['title'] = BaseFieldDefinition::create('string')
->setLabel(t('Title'))
@ -150,17 +150,6 @@ class Feed extends ContentEntityBase implements FeedInterface {
->setDisplayConfigurable('form', TRUE)
->addConstraint('FeedTitle');
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))
->setDescription(t('The feed language code.'))
->setDisplayOptions('view', array(
'type' => 'hidden',
))
->setDisplayOptions('form', array(
'type' => 'language_select',
'weight' => 2,
));
$fields['url'] = BaseFieldDefinition::create('uri')
->setLabel(t('URL'))
->setDescription(t('The fully-qualified URL of the feed.'))

View file

@ -47,11 +47,14 @@ class Item extends ContentEntityBase implements ItemInterface {
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['iid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Aggregator item ID'))
->setDescription(t('The ID of the feed item.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['iid']->setLabel(t('Aggregator item ID'))
->setDescription(t('The ID of the feed item.'));
$fields['langcode']->setLabel(t('Language code'))
->setDescription(t('The feed item language code.'));
$fields['fid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Source feed'))
@ -69,10 +72,6 @@ class Item extends ContentEntityBase implements ItemInterface {
->setLabel(t('Title'))
->setDescription(t('The title of the feed item.'));
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language code'))
->setDescription(t('The feed item language code.'));
$fields['link'] = BaseFieldDefinition::create('uri')
->setLabel(t('Link'))
->setDescription(t('The link of the feed item.'))

View file

@ -21,11 +21,9 @@ class FeedAccessControlHandler extends EntityAccessControlHandler {
switch ($operation) {
case 'view':
return AccessResult::allowedIfHasPermission($account, 'access news feeds');
break;
default:
return AccessResult::allowedIfHasPermission($account, 'administer news feeds');
break;
}
}

View file

@ -16,13 +16,16 @@ class FeedForm extends ContentEntityForm {
*/
public function save(array $form, FormStateInterface $form_state) {
$feed = $this->entity;
if ($feed->save() == SAVED_UPDATED) {
drupal_set_message($this->t('The feed %feed has been updated.', array('%feed' => $feed->label())));
$status = $feed->save();
$label = $feed->label();
$view_link = $feed->link($label, 'canonical');
if ($status == SAVED_UPDATED) {
drupal_set_message($this->t('The feed %feed has been updated.', array('%feed' => $view_link)));
$form_state->setRedirectUrl($feed->urlInfo('canonical'));
}
else {
$this->logger('aggregator')->notice('Feed %feed added.', array('%feed' => $feed->label(), 'link' => $this->l($this->t('View'), new Url('aggregator.admin_overview'))));
drupal_set_message($this->t('The feed %feed has been added.', array('%feed' => $feed->label())));
drupal_set_message($this->t('The feed %feed has been added.', array('%feed' => $view_link)));
}
}

View file

@ -60,17 +60,17 @@ class AggregatorTitleFormatter extends FormatterBase {
}
foreach ($items as $delta => $item) {
if ($this->getSetting('display_as_link') && $url_string) {
$elements[$delta] = [
if ($this->getSetting('display_as_link') && $url_string) {
$elements[$delta] = [
'#type' => 'link',
'#title' => $item->value,
'#url' => Url::fromUri($url_string),
];
}
else {
$elements[$delta] = ['#markup' => $item->value];
}
];
}
else {
$elements[$delta] = ['#markup' => $item->value];
}
}
return $elements;
}

View file

@ -14,7 +14,7 @@ class AggregatorAdminTest extends AggregatorTestBase {
*/
public function testSettingsPage() {
$this->drupalGet('admin/config');
$this->clickLink('Feed aggregator');
$this->clickLink('Aggregator');
$this->clickLink('Settings');
// Make sure that test plugins are present.
$this->assertText('Test fetcher');

View file

@ -3,6 +3,7 @@
namespace Drupal\aggregator\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\views\Entity\View;
/**
* Tests display of aggregator items on the page.
@ -90,7 +91,7 @@ class AggregatorRenderingTest extends AggregatorTestBase {
public function testFeedPage() {
// Increase the number of items published in the rss.xml feed so we have
// enough articles to test paging.
$view = entity_load('view', 'frontpage');
$view = View::load('frontpage');
$display = &$view->getDisplay('feed_1');
$display['display_options']['pager']['options']['items_per_page'] = 30;
$view->save();

View file

@ -61,7 +61,11 @@ abstract class AggregatorTestBase extends WebTestBase {
public function createFeed($feed_url = NULL, array $edit = array()) {
$edit = $this->getFeedEditArray($feed_url, $edit);
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
// Verify that the creation message contains a link to a feed.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
$this->assert(isset($view_link), 'The message area contains a link to a feed');
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE title = :title AND url = :url", array(':title' => $edit['title[0][value]'], ':url' => $edit['url[0][value]']))->fetchField();
$this->assertTrue(!empty($fid), 'The feed found in database.');

View file

@ -3,7 +3,7 @@
namespace Drupal\aggregator\Tests;
/**
* Tests the display of a feed on the feed aggregator list page.
* Tests the display of a feed on the Aggregator list page.
*
* @group aggregator
*/

View file

@ -35,7 +35,11 @@ class UpdateFeedItemTest extends AggregatorTestBase {
$this->assertResponse(200);
$this->drupalPostForm('aggregator/sources/add', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been added.', array('%name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
$this->assertText(t('The feed @name has been added.', array('@name' => $edit['title[0][value]'])), format_string('The feed @name has been added.', array('@name' => $edit['title[0][value]'])));
// Verify that the creation message contains a link to a feed.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
$this->assert(isset($view_link), 'The message area contains a link to a feed');
$fid = db_query("SELECT fid FROM {aggregator_feed} WHERE url = :url", array(':url' => $edit['url[0][value]']))->fetchField();
$feed = Feed::load($fid);

View file

@ -24,7 +24,11 @@ class UpdateFeedTest extends AggregatorTestBase {
$edit[$same_field] = $feed->{$same_field}->value;
}
$this->drupalPostForm('aggregator/sources/' . $feed->id() . '/configure', $edit, t('Save'));
$this->assertRaw(t('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])), format_string('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])));
$this->assertText(t('The feed @name has been updated.', array('@name' => $edit['title[0][value]'])), format_string('The feed %name has been updated.', array('%name' => $edit['title[0][value]'])));
// Verify that the creation message contains a link to a feed.
$view_link = $this->xpath('//div[@class="messages"]//a[contains(@href, :href)]', array(':href' => 'aggregator/sources/'));
$this->assert(isset($view_link), 'The message area contains a link to a feed');
// Check feed data.
$this->assertUrl($feed->url('canonical', ['absolute' => TRUE]));

View file

@ -1,6 +1,6 @@
<?php
namespace Drupal\Tests\aggregator\Unit\Plugin {
namespace Drupal\Tests\aggregator\Unit\Plugin;
use Drupal\aggregator\Form\SettingsForm;
use Drupal\Core\Form\FormState;
@ -105,11 +105,9 @@ class AggregatorPluginSettingsBaseTest extends UnitTestCase {
}
}
// @todo Delete after https://www.drupal.org/node/2278383 is in.
namespace Drupal\Core\Form;
namespace {
// @todo Delete after https://www.drupal.org/node/1858196 is in.
if (!function_exists('drupal_set_message')) {
function drupal_set_message() {}
}
if (!function_exists('drupal_set_message')) {
function drupal_set_message() {}
}

View file

@ -4,3 +4,5 @@ description: 'Provides the HTTP Basic authentication provider'
package: Web services
version: VERSION
core: 8.x
dependencies:
- user

View file

@ -16,8 +16,7 @@ services:
public: false
class: \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
decorates: html_response.attachments_processor
decoration_inner_name: html_response.attachments_processor.original
arguments: ['@html_response.attachments_processor.original', '@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
arguments: ['@html_response.attachments_processor.big_pipe.inner', '@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
route_subscriber.no_big_pipe:
class: Drupal\big_pipe\EventSubscriber\NoBigPipeRouteAlterSubscriber

View file

@ -244,7 +244,7 @@ class BigPipePlaceholderTestCases {
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '[data-big-pipe-placeholder-id="timecurrent-timetime"]',
'data' => '<time datetime=1991-03-14"></time>',
'data' => '<time datetime="1991-03-14"></time>',
'settings' => NULL,
],
];
@ -258,7 +258,7 @@ class BigPipePlaceholderTestCases {
],
],
];
$current_time->embeddedHtmlResponse = '<time datetime=1991-03-14"></time>';
$current_time->embeddedHtmlResponse = '<time datetime="1991-03-14"></time>';
// 6. Edge case: #lazy_builder that throws an exception.

View file

@ -85,7 +85,7 @@ class BigPipeTestController {
*/
public static function currentTime() {
return [
'#markup' => '<time datetime=' . date('Y-m-d', 668948400) . '"></time>',
'#markup' => '<time datetime="' . date('Y-m-d', 668948400) . '"></time>',
'#cache' => ['max-age' => 0]
];
}

View file

@ -109,12 +109,12 @@ function block_themes_installed($theme_list) {
*/
function block_theme_initialize($theme) {
// Initialize theme's blocks if none already registered.
$has_blocks = entity_load_multiple_by_properties('block', array('theme' => $theme));
$has_blocks = \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(array('theme' => $theme));
if (!$has_blocks) {
$default_theme = \Drupal::config('system.theme')->get('default');
// Apply only to new theme's visible regions.
$regions = system_region_list($theme, REGIONS_VISIBLE);
$default_theme_blocks = entity_load_multiple_by_properties('block', array('theme' => $default_theme));
$default_theme_blocks = \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(array('theme' => $default_theme));
foreach ($default_theme_blocks as $default_theme_block_id => $default_theme_block) {
if (strpos($default_theme_block_id, $default_theme . '_') === 0) {
$id = str_replace($default_theme, $theme, $default_theme_block_id);
@ -141,7 +141,7 @@ function block_rebuild() {
if ($data->status) {
$regions = system_region_list($theme);
/** @var \Drupal\block\BlockInterface[] $blocks */
$blocks = entity_load_multiple_by_properties('block', ['theme' => $theme]);
$blocks = \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(['theme' => $theme]);
foreach ($blocks as $block_id => $block) {
// Disable blocks in invalid regions.
$region = $block->getRegion();

View file

@ -63,17 +63,19 @@ process:
region:
plugin: 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
- region
map:
garland:
bartik:
# Garland 6.x --> Bartik 8.x
header: header
footer: footer_fifth
left: sidebar_first
right: sidebar_second
# If mapping fails, put the block in the content region.
default_value: content
weight: weight
settings:
plugin: block_settings

View file

@ -67,17 +67,24 @@ process:
region:
plugin: 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
- region
map:
bartik:
bartik:
# Bartik 7.x --> Bartik 8.x
featured: featured_top
triptych_first: featured_bottom_first
triptych_middle: featured_bottom_second
triptych_last: featured_bottom_third
footer_firstcolumn: footer_first
footer_secondcolumn: footer_second
footer_thirdcolumn: footer_third
footer_fourthcolumn: footer_fourth
footer: footer_fifth
# If mapping fails, put the block in the content region.
default_value: content
weight: weight
settings:
plugin: block_settings

View file

@ -3,15 +3,18 @@
namespace Drupal\block;
use Drupal\Component\Utility\Html;
use Drupal\Core\Plugin\PluginFormFactoryInterface;
use Drupal\Core\Block\BlockPluginInterface;
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Executable\ExecutableManagerInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\SubformState;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
use Drupal\Core\Plugin\PluginWithFormsInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -68,6 +71,13 @@ class BlockForm extends EntityForm {
*/
protected $contextRepository;
/**
* The plugin form manager.
*
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface
*/
protected $pluginFormFactory;
/**
* Constructs a BlockForm object.
*
@ -81,13 +91,16 @@ class BlockForm extends EntityForm {
* The language manager.
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
* The theme handler.
* @param \Drupal\Core\Plugin\PluginFormFactoryInterface $plugin_form_manager
* The plugin form manager.
*/
public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler) {
public function __construct(EntityManagerInterface $entity_manager, ExecutableManagerInterface $manager, ContextRepositoryInterface $context_repository, LanguageManagerInterface $language, ThemeHandlerInterface $theme_handler, PluginFormFactoryInterface $plugin_form_manager) {
$this->storage = $entity_manager->getStorage('block');
$this->manager = $manager;
$this->contextRepository = $context_repository;
$this->language = $language;
$this->themeHandler = $theme_handler;
$this->pluginFormFactory = $plugin_form_manager;
}
/**
@ -99,7 +112,8 @@ class BlockForm extends EntityForm {
$container->get('plugin.manager.condition'),
$container->get('context.repository'),
$container->get('language_manager'),
$container->get('theme_handler')
$container->get('theme_handler'),
$container->get('plugin_form.factory')
);
}
@ -120,7 +134,9 @@ class BlockForm extends EntityForm {
$form_state->setTemporaryValue('gathered_contexts', $this->contextRepository->getAvailableContexts());
$form['#tree'] = TRUE;
$form['settings'] = $entity->getPlugin()->buildConfigurationForm(array(), $form_state);
$form['settings'] = [];
$subform_state = SubformState::createForSubform($form['settings'], $form, $form_state);
$form['settings'] = $this->getPluginForm($entity->getPlugin())->buildConfigurationForm($form['settings'], $subform_state);
$form['visibility'] = $this->buildVisibilityInterface([], $form_state);
// If creating a new block, calculate a safe default machine name.
@ -164,6 +180,13 @@ class BlockForm extends EntityForm {
);
}
// Hidden weight setting.
$weight = $entity->isNew() ? $this->getRequest()->query->get('weight', 0) : $entity->getWeight();
$form['weight'] = array(
'#type' => 'hidden',
'#default_value' => $weight,
);
// Region settings.
$entity_region = $entity->getRegion();
$region = $entity->isNew() ? $this->getRequest()->query->get('region', $entity_region) : $entity_region;
@ -278,13 +301,10 @@ class BlockForm extends EntityForm {
public function validateForm(array &$form, FormStateInterface $form_state) {
parent::validateForm($form, $form_state);
$form_state->setValue('weight', (int) $form_state->getValue('weight'));
// The Block Entity form puts all block plugin form elements in the
// settings form element, so just pass that to the block for validation.
$settings = (new FormState())->setValues($form_state->getValue('settings'));
// Call the plugin validate handler.
$this->entity->getPlugin()->validateConfigurationForm($form, $settings);
// Update the original form values.
$form_state->setValue('settings', $settings->getValues());
$this->getPluginForm($this->entity->getPlugin())->validateConfigurationForm($form['settings'], SubformState::createForSubform($form['settings'], $form, $form_state));
$this->validateVisibility($form, $form_state);
}
@ -308,11 +328,7 @@ class BlockForm extends EntityForm {
// Allow the condition to validate the form.
$condition = $form_state->get(['conditions', $condition_id]);
$condition_values = (new FormState())
->setValues($values);
$condition->validateConfigurationForm($form, $condition_values);
// Update the original form values.
$form_state->setValue(['visibility', $condition_id], $condition_values->getValues());
$condition->validateConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
}
}
@ -325,37 +341,17 @@ class BlockForm extends EntityForm {
$entity = $this->entity;
// The Block Entity form puts all block plugin form elements in the
// settings form element, so just pass that to the block for submission.
// @todo Find a way to avoid this manipulation.
$settings = (new FormState())->setValues($form_state->getValue('settings'));
$sub_form_state = SubformState::createForSubform($form['settings'], $form, $form_state);
// Call the plugin submit handler.
$entity->getPlugin()->submitConfigurationForm($form, $settings);
$block = $entity->getPlugin();
$this->getPluginForm($block)->submitConfigurationForm($form, $sub_form_state);
// If this block is context-aware, set the context mapping.
if ($block instanceof ContextAwarePluginInterface && $block->getContextDefinitions()) {
$context_mapping = $settings->getValue('context_mapping', []);
$context_mapping = $sub_form_state->getValue('context_mapping', []);
$block->setContextMapping($context_mapping);
}
// Update the original form values.
$form_state->setValue('settings', $settings->getValues());
// Submit visibility condition settings.
foreach ($form_state->getValue('visibility') as $condition_id => $values) {
// Allow the condition to submit the form.
$condition = $form_state->get(['conditions', $condition_id]);
$condition_values = (new FormState())
->setValues($values);
$condition->submitConfigurationForm($form, $condition_values);
if ($condition instanceof ContextAwarePluginInterface) {
$context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : [];
$condition->setContextMapping($context_mapping);
}
// Update the original form values.
$condition_configuration = $condition->getConfiguration();
$form_state->setValue(['visibility', $condition_id], $condition_configuration);
// Update the visibility conditions on the block.
$entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration);
}
$this->submitVisibility($form, $form_state);
// Save the settings of the plugin.
$entity->save();
@ -370,6 +366,36 @@ class BlockForm extends EntityForm {
);
}
/**
* Helper function to independently submit the visibility UI.
*
* @param array $form
* A nested array form elements comprising the form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*/
protected function submitVisibility(array $form, FormStateInterface $form_state) {
foreach ($form_state->getValue('visibility') as $condition_id => $values) {
// Allow the condition to submit the form.
$condition = $form_state->get(['conditions', $condition_id]);
$condition->submitConfigurationForm($form['visibility'][$condition_id], SubformState::createForSubform($form['visibility'][$condition_id], $form, $form_state));
// Setting conditions' context mappings is the plugins' responsibility.
// This code exists for backwards compatibility, because
// \Drupal\Core\Condition\ConditionPluginBase::submitConfigurationForm()
// did not set its own mappings until Drupal 8.2
// @todo Remove the code that sets context mappings in Drupal 9.0.0.
if ($condition instanceof ContextAwarePluginInterface) {
$context_mapping = isset($values['context_mapping']) ? $values['context_mapping'] : [];
$condition->setContextMapping($context_mapping);
}
$condition_configuration = $condition->getConfiguration();
// Update the visibility conditions on the block.
$this->entity->getVisibilityConditions()->addInstanceId($condition_id, $condition_configuration);
}
}
/**
* Generates a unique machine name for a block.
*
@ -402,4 +428,20 @@ class BlockForm extends EntityForm {
return $machine_default;
}
/**
* Retrieves the plugin form for a given block and operation.
*
* @param \Drupal\Core\Block\BlockPluginInterface $block
* The block plugin.
*
* @return \Drupal\Core\Plugin\PluginFormInterface
* The plugin form for the block.
*/
protected function getPluginForm(BlockPluginInterface $block) {
if ($block instanceof PluginWithFormsInterface) {
return $this->pluginFormFactory->createInstance($block, 'configure');
}
return $block;
}
}

View file

@ -14,6 +14,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContextAwarePluginInterface;
use Drupal\Core\Render\Element;
use Drupal\block\Entity\Block;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@ -186,7 +187,7 @@ class BlockViewBuilder extends EntityViewBuilder {
* A render array with a #pre_render callback to render the block.
*/
public static function lazyBuilder($entity_id, $view_mode) {
return static::buildPreRenderableBlock(entity_load('block', $entity_id), \Drupal::service('module_handler'));
return static::buildPreRenderableBlock(Block::load($entity_id), \Drupal::service('module_handler'));
}
/**

View file

@ -8,6 +8,7 @@ use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Menu\LocalActionManagerInterface;
use Drupal\Core\Plugin\Context\LazyContextRepository;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -46,6 +47,13 @@ class BlockLibraryController extends ControllerBase {
*/
protected $localActionManager;
/**
* The redirect destination.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a BlockLibraryController object.
*
@ -57,12 +65,15 @@ class BlockLibraryController extends ControllerBase {
* The current route match.
* @param \Drupal\Core\Menu\LocalActionManagerInterface $local_action_manager
* The local action manager.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination.
*/
public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager) {
public function __construct(BlockManagerInterface $block_manager, LazyContextRepository $context_repository, RouteMatchInterface $route_match, LocalActionManagerInterface $local_action_manager, RedirectDestinationInterface $redirect_destination) {
$this->blockManager = $block_manager;
$this->routeMatch = $route_match;
$this->localActionManager = $local_action_manager;
$this->contextRepository = $context_repository;
$this->redirectDestination = $redirect_destination;
}
/**
@ -73,7 +84,8 @@ class BlockLibraryController extends ControllerBase {
$container->get('plugin.manager.block'),
$container->get('context.repository'),
$container->get('current_route_match'),
$container->get('plugin.manager.menu.local_action')
$container->get('plugin.manager.menu.local_action'),
$container->get('redirect.destination')
);
}
@ -107,6 +119,7 @@ class BlockLibraryController extends ControllerBase {
$definitions = $this->blockManager->getSortedDefinitions($definitions);
$region = $request->query->get('region');
$weight = $request->query->get('weight');
$rows = [];
foreach ($definitions as $plugin_id => $plugin_definition) {
$row = [];
@ -132,6 +145,13 @@ class BlockLibraryController extends ControllerBase {
if ($region) {
$links['add']['query']['region'] = $region;
}
if (isset($weight)) {
$links['add']['query']['weight'] = $weight;
}
$destination = $this->redirectDestination->get();
if ($destination) {
$links['add']['query']['destination'] = $destination;
}
$row['operations']['data'] = [
'#type' => 'operations',
'#links' => $links,

View file

@ -4,7 +4,7 @@ namespace Drupal\block\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -13,7 +13,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* id = "block_region"
* )
*/
class BlockRegion extends ProcessPluginBase implements ContainerFactoryPluginInterface {
class BlockRegion extends StaticMap implements ContainerFactoryPluginInterface {
/**
* List of regions, keyed by theme.
@ -56,7 +56,7 @@ class BlockRegion extends ProcessPluginBase implements ContainerFactoryPluginInt
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// Set the destination region, based on the source region and theme as well
// as the current destination default theme.
list($region, $source_theme, $destination_theme) = $value;
list($source_theme, $destination_theme, $region) = $value;
// Theme is the same on both source and destination, so ensure that the
// region exists in the destination theme.
@ -66,15 +66,8 @@ class BlockRegion extends ProcessPluginBase implements ContainerFactoryPluginInt
}
}
// 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';
// Fall back to static mapping.
return parent::transform($value, $migrate_executable, $row, $destination_property);
}
}

View file

@ -24,11 +24,11 @@ class BlockTheme extends ProcessPluginBase implements ContainerFactoryPluginInte
*/
protected $configFactory;
/**
* Contains the system.theme configuration object.
*
* @var \Drupal\Core\Config\Config
*/
/**
* Contains the system.theme configuration object.
*
* @var \Drupal\Core\Config\Config
*/
protected $themeConfig;
/**

View file

@ -63,7 +63,7 @@ class BlockCacheTest extends WebTestBase {
$this->normalUserAlt->save();
// Enable our test block.
$this->block = $this->drupalPlaceBlock('test_cache');
$this->block = $this->drupalPlaceBlock('test_cache');
}
/**

View file

@ -25,7 +25,7 @@ class BlockFormInBlockTest extends WebTestBase {
parent::setUp();
// Enable our test block.
$this->drupalPlaceBlock('test_form_in_block');
$this->drupalPlaceBlock('test_form_in_block');
}
/**

View file

@ -4,6 +4,7 @@ namespace Drupal\block\Tests;
use Drupal\Component\Utility\Html;
use Drupal\block\Entity\Block;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
@ -127,6 +128,94 @@ class BlockTest extends BlockTestBase {
$this->assertNoText($title, 'Block was not displayed to anonymous users on the front page.');
}
/**
* Tests adding a block from the library page with a destination query string.
*/
public function testAddBlockFromLibrary() {
$default_theme = $this->config('system.theme')->get('default');
$help_url = Url::fromRoute('help.page', ['name' => 'block']);
// Set up the request so we land on the block help page after creation.
$options = [
'query' => [
'region' => 'sidebar_first',
'destination' => $help_url->toString(),
],
];
$this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options));
$block_name = 'system_powered_by_block';
$add_url = Url::fromRoute('block.admin_add', ['plugin_id' => $block_name, 'theme' => $default_theme]);
$links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]);
$this->assertEqual(1, count($links), 'Found one matching link');
list($path, $query_string) = explode('?', $links[0]['href'], 2);
parse_str($query_string, $query_parts);
$this->assertEqual(t('Place block'), (string) $links[0]);
$this->assertEqual($help_url->toString(), $query_parts['destination'], 'Expected destination query string is in href');
// Create a random title for the block.
$title = $this->randomMachineName(8);
$block_id = strtolower($this->randomMachineName(8));
$edit = [
'id' => $block_id,
'settings[label]' => $title,
];
// Create the block using the link parsed from the library page.
$this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block'));
// Verify that we are redirected according to the original request.
$this->assertUrl($help_url);
// Ensure that the block was created.
/** @var \Drupal\block\BlockInterface $block */
$block = Block::load($block_id);
$this->assertEqual($title, $block->label(), 'Found the block with expected title.');
}
/**
* Tests adding a block from the library page with a weight query string.
*/
public function testAddBlockFromLibraryWithWeight() {
$default_theme = $this->config('system.theme')->get('default');
// Test one positive, zero, and one negative weight.
foreach (['7', '0', '-9'] as $weight) {
$options = [
'query' => [
'region' => 'sidebar_first',
'weight' => $weight,
],
];
$this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => $default_theme], $options));
$block_name = 'system_powered_by_block';
$add_url = Url::fromRoute('block.admin_add', [
'plugin_id' => $block_name,
'theme' => $default_theme
]);
$links = $this->xpath('//a[contains(@href, :href)]', [':href' => $add_url->toString()]);
$this->assertEqual(1, count($links), 'Found one matching link.');
$this->assertEqual(t('Place block'), (string) $links[0], 'Found the expected link text.');
list($path, $query_string) = explode('?', $links[0]['href'], 2);
parse_str($query_string, $query_parts);
$this->assertEqual($weight, $query_parts['weight'], 'Found the expected weight query string.');
// Create a random title for the block.
$title = $this->randomMachineName(8);
$block_id = strtolower($this->randomMachineName(8));
$edit = [
'id' => $block_id,
'settings[label]' => $title,
];
// Create the block using the link parsed from the library page.
$this->drupalPostForm($this->getAbsoluteUrl($links[0]['href']), $edit, t('Save block'));
// Ensure that the block was created with the expected weight.
/** @var \Drupal\block\BlockInterface $block */
$block = Block::load($block_id);
$this->assertEqual($weight, $block->getWeight(), 'Found the block with expected weight.');
}
}
/**
* Test configuring and moving a module-define block to specific regions.
*/

View file

@ -288,4 +288,20 @@ class BlockUiTest extends WebTestBase {
$this->assertUrl('admin/structure/block/list/classy');
}
/**
* Tests if validation errors are passed plugin form to the parent form.
*/
public function testBlockValidateErrors() {
$this->drupalPostForm('admin/structure/block/add/test_settings_validation/classy', ['settings[digits]' => 'abc'], t('Save block'));
$arguments = [':message' => 'Only digits are allowed'];
$pattern = '//div[contains(@class,"messages messages--error")]/div[contains(text()[2],:message)]';
$elements = $this->xpath($pattern, $arguments);
$this->assertTrue($elements, 'Plugin error message found in parent form.');
$error_class_pattern = '//div[contains(@class,"form-item-settings-digits")]/input[contains(@class,"error")]';
$error_class = $this->xpath($error_class_pattern);
$this->assertTrue($error_class, 'Plugin error class found in parent form.');
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\block_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a block with multiple forms.
*
* @Block(
* id = "test_multiple_forms_block",
* forms = {
* "secondary" = "\Drupal\block_test\PluginForm\EmptyBlockForm"
* },
* admin_label = @Translation("Multiple forms test block")
* )
*/
class TestMultipleFormsBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function build() {
return [];
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Drupal\block_test\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a test settings validation block.
*
* @Block(
* id = "test_settings_validation",
* admin_label = @Translation("Test settings validation block"),
* )
*/
class TestSettingsValidationBlock extends BlockBase {
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
return ['digits' => ['#type' => 'textfield']] + $form;
}
/**
* {@inheritdoc}
*/
public function blockValidate($form, FormStateInterface $form_state) {
if (!ctype_digit($form_state->getValue('digits'))) {
$form_state->setErrorByName('digits', $this->t('Only digits are allowed'));
}
}
/**
* {@inheritdoc}
*/
public function build() {
return ['#markup' => 'foo'];
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Drupal\block_test\PluginForm;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\PluginFormBase;
/**
* Provides a form for a block that is empty.
*/
class EmptyBlockForm extends PluginFormBase {
/**
* {@inheritdoc}
*/
public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
return $form;
}
/**
* {@inheritdoc}
*/
public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
// Intentionally empty.
}
}

View file

@ -49,6 +49,7 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
'd6_user_role',
'd6_block',
]);
block_rebuild();
}
/**
@ -68,18 +69,21 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
* The block label.
* @param string $label_display
* The block label display setting.
* @param bool $status
* (optional) Whether the block is expected to be enabled.
*/
public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display) {
public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display, $status = TRUE) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
$this->assertIdentical($visibility, $block->getVisibility());
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
$this->assertSame($visibility, $block->getVisibility());
$this->assertSame($region, $block->getRegion());
$this->assertSame($theme, $block->getTheme());
$this->assertSame($weight, $block->getWeight());
$this->assertSame($status, $block->status());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
$this->assertIdentical($label_display, $config->get('settings.label_display'));
$this->assertSame($label, $config->get('settings.label'));
$this->assertSame($label_display, $config->get('settings.label_display'));
}
/**
@ -122,7 +126,7 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = '/node/1';
$this->assertEntity('system', $visibility, 'footer', 'bartik', -5, '', '0');
$this->assertEntity('system', $visibility, 'footer_fifth', 'bartik', -5, '', '0');
// Check menu blocks
$visibility = [];
@ -137,7 +141,10 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '/node';
$this->assertEntity('block_1', $visibility, 'sidebar_second', 'bluemarine', -4, 'Another Static Block', 'visible');
// bluemarine does not exist in Drupal 8 and the d6_block migration defines
// no mapping for its regions, so this block should have been defaulted
// to the 'content' region.
$this->assertEntity('block_1', $visibility, 'content', 'bluemarine', -4, 'Another Static Block', 'visible');
$visibility = [];
$this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7, '', '0');

View file

@ -13,7 +13,7 @@ use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
*/
class MigrateBlockTest extends MigrateDrupal7TestBase {
/**
/**
* {@inheritdoc}
*/
public static $modules = [
@ -53,6 +53,7 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
'd7_custom_block',
'd7_block',
]);
block_rebuild();
}
/**
@ -76,29 +77,32 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
* The block label.
* @param string $label_display
* The block label display setting.
* @param bool $status
* (optional) Whether the block is expected to be enabled.
*/
public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight, $label, $label_display) {
public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight, $label, $label_display, $status = TRUE) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
/** @var \Drupal\block\BlockInterface $block */
$this->assertIdentical($plugin_id, $block->getPluginId());
$this->assertSame($plugin_id, $block->getPluginId());
$visibility = $block->getVisibility();
if ($roles) {
$this->assertIdentical($roles, array_values($visibility['user_role']['roles']));
$this->assertIdentical('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']);
$this->assertSame($roles, array_values($visibility['user_role']['roles']));
$this->assertSame('@user.current_user_context:current_user', $visibility['user_role']['context_mapping']['user']);
}
if ($pages) {
$this->assertIdentical($pages, $visibility['request_path']['pages']);
$this->assertSame($pages, $visibility['request_path']['pages']);
}
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
$this->assertSame($region, $block->getRegion());
$this->assertSame($theme, $block->getTheme());
$this->assertSame($weight, $block->getWeight());
$this->assertSame($status, $block->status());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
$this->assertIdentical($label_display, $config->get('settings.label_display'));
$this->assertSame($label, $config->get('settings.label'));
$this->assertSame($label_display, $config->get('settings.label_display'));
}
/**
@ -108,7 +112,7 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
$this->assertEntity('bartik_system_main', 'system_main_block', [], '', 'content', 'bartik', 0, '', '0');
$this->assertEntity('bartik_search_form', 'search_form_block', [], '', 'sidebar_first', 'bartik', -1, '', '0');
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0, '', '0');
$this->assertEntity('bartik_system_powered_by', 'system_powered_by_block', [], '', 'footer', 'bartik', 10, '', '0');
$this->assertEntity('bartik_system_powered_by', 'system_powered_by_block', [], '', 'footer_fifth', 'bartik', 10, '', '0');
$this->assertEntity('seven_system_main', 'system_main_block', [], '', 'content', 'seven', 0, '', '0');
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10, '', '0');

View file

@ -3,6 +3,7 @@
namespace Drupal\Tests\block\Unit;
use Drupal\block\BlockForm;
use Drupal\Core\Plugin\PluginFormFactoryInterface;
use Drupal\Tests\UnitTestCase;
/**
@ -54,6 +55,13 @@ class BlockFormTest extends UnitTestCase {
*/
protected $contextRepository;
/**
* The plugin form manager.
*
* @var \Drupal\Core\Plugin\PluginFormFactoryInterface|\Prophecy\Prophecy\ProphecyInterface
*/
protected $pluginFormFactory;
/**
* {@inheritdoc}
*/
@ -71,6 +79,7 @@ class BlockFormTest extends UnitTestCase {
->method('getStorage')
->will($this->returnValue($this->storage));
$this->pluginFormFactory = $this->prophesize(PluginFormFactoryInterface::class);
}
/**
@ -99,7 +108,7 @@ class BlockFormTest extends UnitTestCase {
->method('getQuery')
->will($this->returnValue($query));
$block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->contextRepository, $this->language, $this->themeHandler);
$block_form_controller = new BlockForm($this->entityManager, $this->conditionManager, $this->contextRepository, $this->language, $this->themeHandler, $this->pluginFormFactory->reveal());
// Ensure that the block with just one other instance gets the next available
// name suggestion.

View file

@ -18,7 +18,7 @@ class BlockRegionTest extends UnitTestCase {
*
* @param array $value
* The value to transform.
* @param \Drupal\migrate\Row|NULL $row
* @param \Drupal\migrate\Row|null $row
* (optional) The mocked row.
*
* @return array|string
@ -30,14 +30,20 @@ class BlockRegionTest extends UnitTestCase {
$row = $this->prophesize(Row::class)->reveal();
}
$regions = array(
'bartik' => array(
'triptych_first' => 'Triptych first',
'triptych_second' => 'Triptych second',
'triptych_third' => 'Triptych third',
),
);
$plugin = new BlockRegion(['region_map' => []], 'block_region', [], $regions);
$configuration = [
'map' => [
'bartik' => [
'bartik' => [
'triptych_first' => 'triptych_first',
'triptych_middle' => 'triptych_second',
'triptych_last' => 'triptych_third',
],
],
],
'default_value' => 'content',
];
$plugin = new BlockRegion($configuration, 'block_region', [], $configuration['map']['bartik']['bartik']);
return $plugin->transform($value, $executable, $row, 'foo');
}
@ -48,7 +54,7 @@ class BlockRegionTest extends UnitTestCase {
* @covers ::transform
*/
public function testTransformSameThemeRegionExists() {
$this->assertSame('triptych_second', $this->transform(['triptych_second', 'bartik', 'bartik']));
$this->assertSame('triptych_second', $this->transform(['bartik', 'bartik', 'triptych_middle']));
}
/**
@ -58,7 +64,7 @@ class BlockRegionTest extends UnitTestCase {
* @covers ::transform
*/
public function testTransformSameThemeRegionNotExists() {
$this->assertSame('content', $this->transform(['footer', 'bartik', 'bartik']));
$this->assertSame('content', $this->transform(['bartik', 'bartik', 'footer']));
}
}

View file

@ -7,4 +7,5 @@ core: 8.x
dependencies:
- block
- text
- user
configure: entity.block_content.collection

View file

@ -39,3 +39,25 @@ function block_content_update_8002() {
// are stable.
// @see https://www.drupal.org/node/2569469
}
/**
* Add 'revision_created' and 'revision_user' fields to 'block_content' entities.
*/
function block_content_update_8003() {
$revision_created = BaseFieldDefinition::create('created')
->setLabel(t('Revision create time'))
->setDescription(t('The time that the current revision was created.'))
->setRevisionable(TRUE);
\Drupal::entityDefinitionUpdateManager()
->installFieldStorageDefinition('revision_created', 'block_content', 'block_content', $revision_created);
$revision_user = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setRevisionable(TRUE);
\Drupal::entityDefinitionUpdateManager()
->installFieldStorageDefinition('revision_user', 'block_content', 'block_content', $revision_user);
}

View file

@ -30,3 +30,6 @@ destination:
migration_dependencies:
required:
- block_content_type
provider:
- block_content
- migrate_drupal

View file

@ -17,3 +17,6 @@ process:
label: label
destination:
plugin: entity:block_content_type
provider:
- block_content
- migrate_drupal

View file

@ -88,7 +88,7 @@ class BlockContentForm extends ContentEntityForm {
// Set up default values, if required.
$block_type = $this->blockContentTypeStorage->load($block->bundle());
if (!$block->isNew()) {
$block->setRevisionLog(NULL);
$block->setRevisionLogMessage(NULL);
}
// Always use the default revision setting.
$block->setNewRevision($block_type->shouldCreateNewRevision());
@ -170,6 +170,9 @@ class BlockContentForm extends ContentEntityForm {
// Save as a new revision if requested to do so.
if (!$form_state->isValueEmpty('revision')) {
$block->setNewRevision();
// If a new revision is created, save the current user as revision author.
$block->setRevisionCreationTime(REQUEST_TIME);
$block->setRevisionUserId(\Drupal::currentUser()->id());
}
$insert = $block->isNew();

View file

@ -4,17 +4,21 @@ namespace Drupal\block_content;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityChangedInterface;
use Drupal\Core\Entity\RevisionLogInterface;
/**
* Provides an interface defining a custom block entity.
*/
interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface {
interface BlockContentInterface extends ContentEntityInterface, EntityChangedInterface, RevisionLogInterface {
/**
* Returns the block revision log message.
*
* @return string
* The revision log message.
*
* @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
* \Drupal\Core\Entity\RevisionLogInterface::getRevisionLogMessage() instead.
*/
public function getRevisionLog();
@ -37,6 +41,9 @@ interface BlockContentInterface extends ContentEntityInterface, EntityChangedInt
*
* @return \Drupal\block_content\BlockContentInterface
* The class instance that this method is called on.
*
* @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
* \Drupal\Core\Entity\RevisionLogInterface::setRevisionLogMessage() instead.
*/
public function setRevisionLog($revision_log);

View file

@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\block_content\BlockContentInterface;
use Drupal\user\UserInterface;
/**
* Defines the custom block entity class.
@ -119,7 +120,7 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
* {@inheritdoc}
*/
public function getInstances() {
return entity_load_multiple_by_properties('block', array('plugin' => 'block_content:' . $this->uuid()));
return \Drupal::entityTypeManager()->getStorage('block')->loadByProperties(array('plugin' => 'block_content:' . $this->uuid()));
}
/**
@ -132,7 +133,7 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
// If we are updating an existing block_content without adding a new
// revision and the user did not supply a revision log, keep the existing
// one.
$record->revision_log = $this->original->getRevisionLog();
$record->revision_log = $this->original->getRevisionLogMessage();
}
}
@ -150,35 +151,20 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Custom block ID'))
->setDescription(t('The custom block ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The custom block UUID.'))
->setReadOnly(TRUE);
$fields['id']->setLabel(t('Custom block ID'))
->setDescription(t('The custom block ID.'));
$fields['revision_id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Revision ID'))
->setDescription(t('The revision ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['uuid']->setDescription(t('The custom block UUID.'));
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The custom block language code.'))
->setTranslatable(TRUE)
->setRevisionable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
->setDisplayOptions('form', array(
'type' => 'language_select',
'weight' => 2,
));
$fields['revision_id']->setDescription(t('The revision ID.'));
$fields['langcode']->setDescription(t('The custom block language code.'));
$fields['type']->setLabel(t('Block type'))
->setDescription(t('The block type.'));
$fields['info'] = BaseFieldDefinition::create('string')
->setLabel(t('Block description'))
@ -193,12 +179,6 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
->setDisplayConfigurable('form', TRUE)
->addConstraint('UniqueField', []);
$fields['type'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Block type'))
->setDescription(t('The block type.'))
->setSetting('target_type', 'block_content_type');
$fields['revision_log'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Revision log message'))
->setDescription(t('The log entry explaining the changes in this revision.'))
@ -210,6 +190,17 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
->setTranslatable(TRUE)
->setRevisionable(TRUE);
$fields['revision_created'] = BaseFieldDefinition::create('created')
->setLabel(t('Revision create time'))
->setDescription(t('The time that the current revision was created.'))
->setRevisionable(TRUE);
$fields['revision_user'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Revision user'))
->setDescription(t('The user ID of the author of the current revision.'))
->setSetting('target_type', 'user')
->setRevisionable(TRUE);
$fields['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
->setLabel(t('Revision translation affected'))
->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
@ -224,7 +215,7 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
* {@inheritdoc}
*/
public function getRevisionLog() {
return $this->get('revision_log')->value;
return $this->getRevisionLogMessage();
}
/**
@ -239,7 +230,63 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
* {@inheritdoc}
*/
public function setRevisionLog($revision_log) {
$this->set('revision_log', $revision_log);
return $this->setRevisionLogMessage($revision_log);
}
/**
* {@inheritdoc}
*/
public function getRevisionCreationTime() {
return $this->get('revision_created')->value;
}
/**
* {@inheritdoc}
*/
public function setRevisionCreationTime($timestamp) {
$this->set('revision_created', $timestamp);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionUser() {
return $this->get('revision_user')->entity;
}
public function setRevisionUser(UserInterface $account) {
$this->set('revision_user', $account);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionUserId() {
return $this->get('revision_user')->entity->id();
}
/**
* {@inheritdoc}
*/
public function setRevisionUserId($user_id) {
$this->set('revision_user', $user_id);
return $this;
}
/**
* {@inheritdoc}
*/
public function getRevisionLogMessage() {
return $this->get('revision_log')->value;
}
/**
* {@inheritdoc}
*/
public function setRevisionLogMessage($revision_log_message) {
$this->set('revision_log', $revision_log_message);
return $this;
}

View file

@ -3,6 +3,8 @@
namespace Drupal\block_content\Tests;
use Drupal\block_content\Entity\BlockContent;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* Create a block with revisions.
@ -29,6 +31,9 @@ class BlockContentRevisionsTest extends BlockContentTestBase {
protected function setUp() {
parent::setUp();
/** @var UserInterface $user */
$user = User::load(1);
// Create initial block.
$block = $this->createBlockContent('initial');
@ -43,8 +48,10 @@ class BlockContentRevisionsTest extends BlockContentTestBase {
$revision_count = 3;
for ($i = 0; $i < $revision_count; $i++) {
$block->setNewRevision(TRUE);
$block->setRevisionLog($this->randomMachineName(32));
$logs[] = $block->getRevisionLog();
$block->setRevisionLogMessage($this->randomMachineName(32));
$block->setRevisionUser($this->adminUser);
$block->setRevisionCreationTime(REQUEST_TIME);
$logs[] = $block->getRevisionLogMessage();
$block->save();
$blocks[] = $block->getRevisionId();
}
@ -62,11 +69,17 @@ class BlockContentRevisionsTest extends BlockContentTestBase {
foreach ($blocks as $delta => $revision_id) {
// Confirm the correct revision text appears.
/** @var \Drupal\block_content\BlockContentInterface $loaded */
$loaded = entity_revision_load('block_content', $revision_id);
// Verify revision log is the same.
$this->assertEqual($loaded->getRevisionLog(), $logs[$delta], format_string('Correct log message found for revision @revision', array(
$this->assertEqual($loaded->getRevisionLogMessage(), $logs[$delta], format_string('Correct log message found for revision @revision', array(
'@revision' => $loaded->getRevisionId(),
)));
if ($delta > 0) {
$this->assertTrue($loaded->getRevisionUser() instanceof UserInterface, 'Revision User found.');
$this->assertTrue(is_numeric($loaded->getRevisionUserId()), 'Revision User ID found.');
$this->assertTrue(is_numeric($loaded->getRevisionCreationTime()), 'Revision time found.');
}
}
// Confirm that this is the default revision.

View file

@ -19,7 +19,7 @@ abstract class BlockContentTestBase extends WebTestBase {
/**
* Admin user
*
* @var object
* @var \Drupal\user\UserInterface
*/
protected $adminUser;

View file

@ -179,7 +179,10 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
* {@inheritdoc}
*/
protected function doTestTranslationEdit() {
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
$storage = $this->container->get('entity_type.manager')
->getStorage($this->entityTypeId);
$storage->resetCache([$this->entityId]);
$entity = $storage->load($this->entityId);
$languages = $this->container->get('language_manager')->getLanguages();
foreach ($this->langcodes as $langcode) {

View file

@ -0,0 +1,39 @@
<?php
namespace Drupal\block_content\Tests;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests adding revision_user and revision_created fields.
*
* @group Update
*/
class BlockContentUpdateEntityFields extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
];
}
/**
* Tests that email token in status_blocked of user.mail is updated.
*/
public function testAddingFields() {
$this->runUpdates();
$post_revision_created = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('revision_created', 'block_content');
$post_revision_user = \Drupal::entityDefinitionUpdateManager()->getFieldStorageDefinition('revision_user', 'block_content');
$this->assertTrue($post_revision_created instanceof BaseFieldDefinition, "Revision created field found");
$this->assertTrue($post_revision_user instanceof BaseFieldDefinition, "Revision user field found");
$this->assertEqual('created', $post_revision_created->getType(), "Field is type created");
$this->assertEqual('entity_reference', $post_revision_user->getType(), "Field is type entity_reference");
}
}

View file

@ -0,0 +1,8 @@
name: Place Blocks
type: module
description: 'Allow administrators to place blocks from any Drupal page'
package: Core (Experimental)
version: VERSION
core: 8.x
dependencies:
- block

View file

@ -0,0 +1,11 @@
drupal.block_place:
version: VERSION
css:
theme:
css/block-place.css: {}
drupal.block_place.icons:
version: VERSION
css:
theme:
css/block-place.icons.theme.css: {}

View file

@ -0,0 +1,84 @@
<?php
/**
* @file
* Controls the placement of blocks from all pages.
*/
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
/**
* Implements hook_help().
*/
function block_place_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.block_place':
$output = '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Place Blocks module allows you to place blocks from every page. For more information, see the <a href=":blocks-documentation">online documentation for the Place Blocks module</a>.', [':blocks-documentation' => 'https://www.drupal.org/documentation/modules/block_place/']) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<p>' . t('Block placement is specific to each theme on your site. This module allows you to place blocks in the context of your content pages') . '</p>';
return $output;
}
}
/**
* Implements hook_toolbar().
*/
function block_place_toolbar() {
// Link to the current page with a query parameter.
$query = \Drupal::request()->query->all();
$wrapper_class = '';
$status_class = '';
$description = '';
if (isset($query['block-place'])) {
$status_class = 'active';
$wrapper_class = 'is-active';
$description = t('Exit Place block mode.');
unset($query['block-place']);
unset($query['destination']);
}
else {
$status_class = 'inactive';
$description = t('Show regions to Place blocks.');
$query['block-place'] = '1';
// Setting destination is both a work-around for the toolbar "Back to site"
// link in escapeAdmin.js and used for the destination after picking a
// block.
$query['destination'] = Url::fromRoute('<current>')->toString();
}
// Remove on Admin routes.
$admin_route = \Drupal::service('router.admin_context')->isAdminRoute();
// Remove on Block Demo page.
$admin_demo = \Drupal::routeMatch()->getRouteName() === 'block.admin_demo';
$access = (\Drupal::currentUser()->hasPermission('administer blocks') && !$admin_route && !$admin_demo);
// The 'Place Block' tab is a simple link, with no corresponding tray.
$items['block_place'] = [
'#cache' => [
'contexts' => ['user.permissions', 'url.query_args'],
],
'#type' => 'toolbar_item',
'tab' => [
'#access' => $access,
'#type' => 'link',
'#title' => t('Place block'),
'#url' => Url::fromRoute('<current>', [], ['query' => $query]),
'#attributes' => [
'title' => $description,
'class' => ['toolbar-icon', 'toolbar-icon-place-block-' . $status_class],
],
],
'#wrapper_attributes' => [
'class' => ['toolbar-tab', 'block-place-toolbar-tab', $wrapper_class],
],
'#weight' => 100,
'#attached' => [
'library' => [
'block_place/drupal.block_place.icons',
],
],
];
return $items;
}

View file

@ -0,0 +1,6 @@
services:
block_place.page_display_variant_subscriber.block:
class: Drupal\block_place\EventSubscriber\BlockPlaceEventSubscriber
arguments: ['@request_stack', '@current_user']
tags:
- { name: event_subscriber }

View file

@ -0,0 +1,29 @@
/**
* @file
* Styling for block_place module regions and buttons during block placement.
*/
.block-place-region {
outline: 1px dashed rgba(0,0,0,0.5);
box-shadow: 0 0 0 1px rgba(255,255,255,0.7);
margin: 1em 0;
padding: 5px;
text-align: center;
text-shadow: none;
}
.block-place-region a.button {
background: url(../../../misc/icons/bebebe/plus.svg) #ffffff center center / 16px 16px no-repeat;
border: 1px solid #cccccc;
box-sizing: border-box;
font-size: 1rem;
padding: 0;
height: 26px;
width: 26px;
white-space: nowrap;
}
.block-place-region:hover a.button, .block-place-region:focus a.button {
background-image: url(../../../misc/icons/787878/plus.svg);
transition: all 0.25s ease;
}

View file

@ -0,0 +1,24 @@
/**
* @file
* Styling for block_place module toolbar icons.
*/
.toolbar .block-place-toolbar-tab.is-active {
background-image: -webkit-linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
background-image: linear-gradient(rgba(255, 255, 255, 0.25) 20%, transparent 200%);
}
.toolbar .toolbar-bar .block-place-toolbar-tab {
float: right;
}
[dir="rtl"] .toolbar .toolbar-bar .block-place-toolbar-tab {
float: left;
}
.toolbar-bar .toolbar-icon-place-block-active:before {
background-image: url(../icons/ffffff/place-block.svg);
}
.toolbar-bar .toolbar-icon-place-block-inactive:before {
background-image: url(../icons/bebebe/place-block.svg);
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" width="16" height="16" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"><rect x="1" y="6" fill="#bebebe" width="14" height="9"/><ellipse fill="#bebebe" cx="11.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="9.25" y="3.68" fill="#bebebe" width="4.45" height="3.35"/><ellipse fill="#bebebe" cx="4.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="2.25" y="3.68" fill="#bebebe" width="4.45" height="3.35"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0" y="0" width="16" height="16" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve"><rect x="1" y="6" fill="#ffffff" width="14" height="9"/><ellipse fill="#ffffff" cx="11.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="9.25" y="3.68" fill="#ffffff" width="4.45" height="3.35"/><ellipse fill="#ffffff" cx="4.48" cy="3.68" rx="2.23" ry="0.61"/><rect x="2.25" y="3.68" fill="#ffffff" width="4.45" height="3.35"/></svg>

After

Width:  |  Height:  |  Size: 548 B

View file

@ -0,0 +1,67 @@
<?php
namespace Drupal\block_place\EventSubscriber;
use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
use Drupal\Core\Render\RenderEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Drupal\Core\Session\AccountInterface;
/**
* @see \Drupal\block_place\Plugin\DisplayVariant\PlaceBlockPageVariant
*/
class BlockPlaceEventSubscriber implements EventSubscriberInterface {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $account;
/**
* Constructs a \Drupal\block_place\EventSubscriber\BlockPlaceEventSubscriber object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack used to retrieve the current request.
* @param \Drupal\Core\Session\AccountInterface $account
* The current user.
*/
public function __construct(RequestStack $request_stack, AccountInterface $account) {
$this->requestStack = $request_stack;
$this->account = $account;
}
/**
* Selects the block place override of the block page display variant.
*
* @param \Drupal\Core\Render\PageDisplayVariantSelectionEvent $event
* The event to process.
*/
public function onBlockPageDisplayVariantSelected(PageDisplayVariantSelectionEvent $event) {
if ($event->getPluginId() === 'block_page') {
if ($this->requestStack->getCurrentRequest()->query->has('block-place') && $this->account->hasPermission('administer blocks')) {
$event->setPluginId('block_place_page');
}
$event->addCacheContexts(['user.permissions', 'url.query_args']);
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
// Set a very low priority, so that it runs last.
$events[RenderEvents::SELECT_PAGE_DISPLAY_VARIANT][] = ['onBlockPageDisplayVariantSelected', -1000];
return $events;
}
}

View file

@ -0,0 +1,139 @@
<?php
namespace Drupal\block_place\Plugin\DisplayVariant;
use Drupal\block\BlockRepositoryInterface;
use Drupal\block\Plugin\DisplayVariant\BlockPageVariant;
use Drupal\Component\Serialization\Json;
use Drupal\Core\Entity\EntityViewBuilderInterface;
use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\Core\Link;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Allows blocks to be placed directly within a region.
*
* @PageDisplayVariant(
* id = "block_place_page",
* admin_label = @Translation("Page with blocks and place block buttons")
* )
*/
class PlaceBlockPageVariant extends BlockPageVariant {
/**
* The theme manager.
*
* @var \Drupal\Core\Theme\ThemeManagerInterface
*/
protected $themeManager;
/**
* The redirect destination.
*
* @var \Drupal\Core\Routing\RedirectDestinationInterface
*/
protected $redirectDestination;
/**
* Constructs a new PlaceBlockPageVariant.
*
* @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\block\BlockRepositoryInterface $block_repository
* The block repository.
* @param \Drupal\Core\Entity\EntityViewBuilderInterface $block_view_builder
* The block view builder.
* @param string[] $block_list_cache_tags
* The Block entity type list cache tags.
* @param \Drupal\Core\Theme\ThemeManagerInterface $theme_manager
* The theme manager.
* @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
* The redirect destination.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockRepositoryInterface $block_repository, EntityViewBuilderInterface $block_view_builder, array $block_list_cache_tags, ThemeManagerInterface $theme_manager, RedirectDestinationInterface $redirect_destination) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $block_repository, $block_view_builder, $block_list_cache_tags);
$this->themeManager = $theme_manager;
$this->redirectDestination = $redirect_destination;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('block.repository'),
$container->get('entity_type.manager')->getViewBuilder('block'),
$container->get('entity_type.manager')->getDefinition('block')->getListCacheTags(),
$container->get('theme.manager'),
$container->get('redirect.destination')
);
}
/**
* {@inheritdoc}
*/
public function build() {
$build = parent::build();
$active_theme = $this->themeManager->getActiveTheme();
$theme_name = $active_theme->getName();
$destination = $this->redirectDestination->get();
$visible_regions = $this->getVisibleRegionNames($theme_name);
// Build an array of the region names in the right order.
$build += array_fill_keys(array_keys($visible_regions), []);
foreach ($visible_regions as $region => $region_name) {
$query = [
'region' => $region,
];
if ($destination) {
$query['destination'] = $destination;
}
$title = $this->t('<span class="visually-hidden">Place block in the %region region</span>', ['%region' => $region_name]);
$operations['block_description'] = [
'#type' => 'inline_template',
'#template' => '<div class="block-place-region">{{ link }}</div>',
'#context' => [
'link' => Link::createFromRoute($title, 'block.admin_library', ['theme' => $theme_name], [
'query' => $query,
'attributes' => [
'title' => $title,
'class' => ['use-ajax', 'button', 'button--small'],
'data-dialog-type' => 'modal',
'data-dialog-options' => Json::encode([
'width' => 700,
]),
],
]),
],
];
$build[$region] = ['block_place_operations' => $operations] + $build[$region];
}
$build['#attached']['library'][] = 'block_place/drupal.block_place';
return $build;
}
/**
* Returns the human-readable list of regions keyed by machine name.
*
* @param string $theme
* The name of the theme.
*
* @return array
* An array of human-readable region names keyed by machine name.
*/
protected function getVisibleRegionNames($theme) {
return system_region_list($theme, REGIONS_VISIBLE);
}
}

View file

@ -0,0 +1,76 @@
<?php
namespace Drupal\Tests\block_place\Functional;
use Drupal\Core\Url;
use Drupal\Tests\BrowserTestBase;
/**
* Tests the placing a block.
*
* @group block_place
*/
class BlockPlaceTest extends BrowserTestBase {
/**
* Modules to install.
*
* @var array
*/
public static $modules = ['block', 'block_place', 'toolbar'];
/**
* Tests placing blocks as an admin.
*/
public function testPlacingBlocksAdmin() {
// Create administrative user.
$this->drupalLogin($this->drupalCreateUser([
'access administration pages',
'access toolbar',
'administer blocks',
'view the administration theme',
]));
$this->drupalGet(Url::fromRoute('<front>'));
$this->clickLink('Place block');
// Each region should have one link to place a block.
$theme_name = $this->container->get('theme.manager')->getActiveTheme()->getName();
$visible_regions = system_region_list($theme_name, REGIONS_VISIBLE);
$this->assertGreaterThan(0, count($visible_regions));
$default_theme = $this->config('system.theme')->get('default');
$block_library_url = Url::fromRoute('block.admin_library', ['theme' => $default_theme]);
foreach ($visible_regions as $region => $name) {
$block_library_url->setOption('query', ['region' => $region]);
$links = $this->xpath('//a[contains(@href, :href)]', [':href' => $block_library_url->toString()]);
$this->assertEquals(1, count($links));
list(, $query_string) = explode('?', $links[0]->getAttribute('href'), 2);
parse_str($query_string, $query_parts);
$this->assertNotEmpty($query_parts['destination']);
// Get the text inside the div->a->span->em.
$demo_block = $this->xpath('//div[@class="block-place-region"]/a/span[text()="Place block in the "]/em[text()="' . $name . '"]');
$this->assertEquals(1, count($demo_block));
}
}
/**
* Tests placing blocks as an unprivileged user.
*/
public function testPlacingBlocksUnprivileged() {
// Create a user who cannot administer blocks.
$this->drupalLogin($this->drupalCreateUser([
'access administration pages',
'access toolbar',
'view the administration theme',
]));
$this->drupalGet(Url::fromRoute('<front>'));
$links = $this->xpath('//a[text()=:label]', [':label' => 'Place block']);
$this->assertEmpty($links);
$this->drupalGet(Url::fromRoute('block.admin_library', ['theme' => 'classy']));
$this->assertSession()->statusCodeEquals(403);
}
}

View file

@ -3,7 +3,7 @@ administer book outlines:
create new books:
title: 'Create new books'
add content to books:
title: 'Add content and child pages to books'
title: 'Add content and child pages to books and manage their hierarchies.'
access printer-friendly version:
title: 'View printer-friendly books'
description: 'View a book page and all of its sub-pages as a single document for ease of printing. Can be performance heavy.'

View file

@ -0,0 +1,125 @@
<?php
/**
* @file
* Provide views data for book.module.
*
* @ingroup views_module_handlers
*/
/**
* Implements hook_views_data().
*/
function book_views_data() {
$data = [];
$data['book'] = [];
$data['book']['table'] = [];
$data['book']['table']['group'] = t('Book');
$data['book']['table']['join'] = [
'node_field_data' => [
'left_field' => 'nid',
'field' => 'nid',
],
];
$data['book']['nid'] = [
'title' => t('Page'),
'help' => t('The book page node.'),
'relationship' => [
'base' => 'node_field_data',
'id' => 'standard',
'label' => t('Book Page'),
],
];
$data['book']['bid'] = [
'title' => t('Top level book'),
'help' => t('The book the node is in.'),
'relationship' => [
'base' => 'node_field_data',
'id' => 'standard',
'label' => t('Book'),
],
];
$data['book']['pid'] = [
'title' => t('Parent'),
'help' => t('The parent book node.'),
'relationship' => [
'base' => 'node_field_data',
'id' => 'standard',
'label' => t('Book Parent'),
],
];
$data['book']['has_children'] = [
'title' => t('Page has Children'),
'help' => t('Flag indicating whether this book page has children'),
'field' => [
'id' => 'boolean',
],
'sort' => [
'id' => 'standard',
],
'filter' => [
'id' => 'boolean',
'label' => t('Has Children'),
],
'argument' => [
'id' => 'numeric',
],
];
$data['book']['weight'] = [
'title' => t('Weight'),
'help' => t('The weight of the book page.'),
'field' => [
'id' => 'numeric',
],
'sort' => [
'id' => 'standard',
],
];
$data['book']['depth'] = [
'title' => t('Depth'),
'help' => t('The depth of the book page in the hierarchy; top level books have a depth of 1.'),
'field' => [
'id' => 'numeric',
],
'sort' => [
'id' => 'standard',
],
'filter' => [
'id' => 'numeric',
],
'argument' => [
'id' => 'standard',
],
];
$parents = [
1 => t('1st parent'),
2 => t('2nd parent'),
3 => t('3rd parent'),
4 => t('4th parent'),
5 => t('5th parent'),
6 => t('6th parent'),
7 => t('7th parent'),
8 => t('8th parent'),
9 => t('9th parent'),
];
foreach ($parents as $i => $parent_label) {
$data['book']["p$i"] = [
'title' => $parent_label,
'help' => t('The @parent of book node.', ['@parent' => $parent_label]),
'relationship' => [
'base' => 'node_field_data',
'id' => 'standard',
'label' => t('Book @parent', ['@parent' => $parent_label]),
],
];
}
return $data;
}

View file

@ -8,6 +8,6 @@ name: 'Book page'
type: book
description: '<em>Books</em> have a built-in hierarchical navigation. Use for handbooks or tutorials.'
help: ''
new_revision: false
new_revision: true
preview_mode: 1
display_submitted: true

View file

@ -0,0 +1,8 @@
# Schema for the views plugins of the Book module.
views.argument_default.top_level_book:
type: sequence
label: 'Top Level Book from current node'
sequence:
type: string
label: 'Nid'

View file

@ -138,7 +138,7 @@ class BookManager implements BookManagerInterface {
/**
* Determine the relative depth of the children of a given book link.
*
* @param array
* @param array $book_link
* The book link.
*
* @return int

View file

@ -4,7 +4,8 @@ namespace Drupal\book\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\Context\CacheContextInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\RequestStack;
/**
@ -19,7 +20,9 @@ use Symfony\Component\HttpFoundation\RequestStack;
* This class is container-aware to avoid initializing the 'book.manager'
* service when it is not necessary.
*/
class BookNavigationCacheContext extends ContainerAware implements CacheContextInterface {
class BookNavigationCacheContext implements CacheContextInterface, ContainerAwareInterface {
use ContainerAwareTrait;
/**
* The request stack.

View file

@ -0,0 +1,73 @@
<?php
namespace Drupal\book\Plugin\views\argument_default;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\node\NodeStorageInterface;
use Drupal\node\Plugin\views\argument_default\Node;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Default argument plugin to get the current node's top level book.
*
* @ViewsArgumentDefault(
* id = "top_level_book",
* title = @Translation("Top Level Book from current node")
* )
*/
class TopLevelBook extends Node {
/**
* The node storage controller.
*
* @var \Drupal\node\NodeStorageInterface
*/
protected $nodeStorage;
/**
* Constructs a Drupal\book\Plugin\views\argument_default\TopLevelBook 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 array $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Routing\RouteMatchInterface $route_match
* The route match.
* @param \Drupal\node\NodeStorageInterface $node_storage
* The node storage controller.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, RouteMatchInterface $route_match, NodeStorageInterface $node_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_match);
$this->nodeStorage = $node_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('current_route_match'),
$container->get('entity.manager')->getStorage('node')
);
}
/**
* {@inheritdoc}
*/
public function getArgument() {
// Use the argument_default_node plugin to get the nid argument.
$nid = parent::getArgument();
if (!empty($nid)) {
$node = $this->nodeStorage->load($nid);
if (isset($node->book['bid'])) {
return $node->book['bid'];
}
}
}
}

View file

@ -526,49 +526,49 @@ class BookTest extends WebTestBase {
/**
* Tests the access for deleting top-level book nodes.
*/
function testBookDelete() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$edit = array();
function testBookDelete() {
$node_storage = $this->container->get('entity.manager')->getStorage('node');
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$edit = array();
// Test access to delete top-level and child book nodes.
$this->drupalGet('node/' . $this->book->id() . '/outline/remove');
$this->assertResponse('403', 'Deleting top-level book node properly forbidden.');
$this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove'));
$node_storage->resetCache(array($nodes[4]->id()));
$node4 = $node_storage->load($nodes[4]->id());
$this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.');
// Test access to delete top-level and child book nodes.
$this->drupalGet('node/' . $this->book->id() . '/outline/remove');
$this->assertResponse('403', 'Deleting top-level book node properly forbidden.');
$this->drupalPostForm('node/' . $nodes[4]->id() . '/outline/remove', $edit, t('Remove'));
$node_storage->resetCache(array($nodes[4]->id()));
$node4 = $node_storage->load($nodes[4]->id());
$this->assertTrue(empty($node4->book), 'Deleting child book node properly allowed.');
// Delete all child book nodes and retest top-level node deletion.
foreach ($nodes as $node) {
$nids[] = $node->id();
}
entity_delete_multiple('node', $nids);
$this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove'));
$node_storage->resetCache(array($this->book->id()));
$node = $node_storage->load($this->book->id());
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
// Delete all child book nodes and retest top-level node deletion.
foreach ($nodes as $node) {
$nids[] = $node->id();
}
entity_delete_multiple('node', $nids);
$this->drupalPostForm('node/' . $this->book->id() . '/outline/remove', $edit, t('Remove'));
$node_storage->resetCache(array($this->book->id()));
$node = $node_storage->load($this->book->id());
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
// Tests directly deleting a book parent.
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$this->drupalGet($this->book->urlInfo('delete-form'));
$this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()]));
// Delete parent, and visit a child page.
$this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete'));
$this->drupalGet($nodes[0]->urlInfo());
$this->assertResponse(200);
$this->assertText($nodes[0]->label());
// The book parents should be updated.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node_storage->resetCache();
$child = $node_storage->load($nodes[0]->id());
$this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
// 3rd-level children should now be 2nd-level.
$second = $node_storage->load($nodes[1]->id());
$this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
// Tests directly deleting a book parent.
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$this->drupalGet($this->book->urlInfo('delete-form'));
$this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()]));
// Delete parent, and visit a child page.
$this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete'));
$this->drupalGet($nodes[0]->urlInfo());
$this->assertResponse(200);
$this->assertText($nodes[0]->label());
// The book parents should be updated.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node_storage->resetCache();
$child = $node_storage->load($nodes[0]->id());
$this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
// 3rd-level children should now be 2nd-level.
$second = $node_storage->load($nodes[1]->id());
$this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
/**
* Tests re-ordering of books.

View file

@ -0,0 +1,156 @@
<?php
namespace Drupal\book\Tests\Views;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests entity reference relationship data.
*
* @group book
*
* @see book_views_data()
*/
class BookRelationshipTest extends ViewTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_book_view');
/**
* Modules to install.
*
* @var array
*/
public static $modules = array('book_test_views', 'book', 'views');
/**
* A book node.
*
* @var object
*/
protected $book;
/**
* A user with permission to create and edit books.
*
* @var object
*/
protected $bookAuthor;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create users.
$this->bookAuthor = $this->drupalCreateUser(
array(
'create new books',
'create book content',
'edit own book content',
'add content to books',
)
);
ViewTestData::createTestViews(get_class($this), array('book_test_views'));
}
/**
* Creates a new book with a page hierarchy.
*/
protected function createBook() {
// Create new book.
$this->drupalLogin($this->bookAuthor);
$this->book = $this->createBookNode('new');
$book = $this->book;
$nodes = array();
// Node 0.
$nodes[] = $this->createBookNode($book->id());
// Node 1.
$nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']);
// Node 2.
$nodes[] = $this->createBookNode($book->id(), $nodes[1]->book['nid']);
// Node 3.
$nodes[] = $this->createBookNode($book->id(), $nodes[2]->book['nid']);
// Node 4.
$nodes[] = $this->createBookNode($book->id(), $nodes[3]->book['nid']);
// Node 5.
$nodes[] = $this->createBookNode($book->id(), $nodes[4]->book['nid']);
// Node 6.
$nodes[] = $this->createBookNode($book->id(), $nodes[5]->book['nid']);
// Node 7.
$nodes[] = $this->createBookNode($book->id(), $nodes[6]->book['nid']);
$this->drupalLogout();
return $nodes;
}
/**
* Creates a book node.
*
* @param int|string $book_nid
* A book node ID or set to 'new' to create a new book.
* @param int|null $parent
* (optional) Parent book reference ID. Defaults to NULL.
*
* @return \Drupal\node\NodeInterface
* The book node.
*/
protected function createBookNode($book_nid, $parent = NULL) {
// $number does not use drupal_static as it should not be reset
// since it uniquely identifies each call to createBookNode().
// Used to ensure that when sorted nodes stay in same order.
static $number = 0;
$edit = array();
$edit['title[0][value]'] = $number . ' - SimpleTest test node ' . $this->randomMachineName(10);
$edit['body[0][value]'] = 'SimpleTest test body ' . $this->randomMachineName(32) . ' ' . $this->randomMachineName(32);
$edit['book[bid]'] = $book_nid;
if ($parent !== NULL) {
$this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
$edit['book[pid]'] = $parent;
$this->drupalPostForm(NULL, $edit, t('Save'));
// Make sure the parent was flagged as having children.
$parent_node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($parent);
$this->assertFalse(empty($parent_node->book['has_children']), 'Parent node is marked as having children');
}
else {
$this->drupalPostForm('node/add/book', $edit, t('Save'));
}
// Check to make sure the book node was created.
$node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
$this->assertNotNull(($node === FALSE ? NULL : $node), 'Book node found in database.');
$number++;
return $node;
}
/**
* Tests using the views relationship.
*/
public function testRelationship() {
// Create new book.
// @var \Drupal\node\NodeInterface[] $nodes
$nodes = $this->createBook();
for ($i = 0; $i < 8; $i++) {
$this->drupalGet('test-book/' . $nodes[$i]->id());
for ($j = 0; $j < $i; $j++) {
$this->assertLink($nodes[$j]->label());
}
}
}
}

View file

@ -0,0 +1,9 @@
name: 'Book test views'
type: module
description: 'Provides default views for views book tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- book
- views

View file

@ -493,7 +493,7 @@
* A HTML string for the button to create a name for a new button group.
*/
Drupal.theme.ckeditorNewButtonGroup = function () {
return '<li class="ckeditor-add-new-group"><button role="button" aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
return '<li class="ckeditor-add-new-group"><button aria-label="' + Drupal.t('Add a CKEditor button group to the end of this row.') + '">' + Drupal.t('Add group') + '</button></li>';
};
})(jQuery, Drupal, drupalSettings, _);

View file

@ -71,7 +71,7 @@ interface CKEditorPluginInterface extends PluginInspectionInterface {
* Note: this does not use a Drupal library because this uses CKEditor's API,
* see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.resourceManager.html#addExternal.
*
* @return string|FALSE
* @return string|false
* The Drupal root-relative path to the file, FALSE if an internal plugin.
*/
public function getFile();

View file

@ -115,7 +115,7 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
*
* @param string $styles
* The "styles" setting.
* @return array|FALSE
* @return array|false
* An array containing the "stylesSet" configuration, or FALSE when the
* syntax is invalid.
*/

View file

@ -55,7 +55,7 @@ class CKEditorAdminTest extends WebTestBase {
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
// Ensure no Editor config entity exists yet.
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertFalse($editor, 'No Editor config entity exists yet.');
// Verify the "Text Editor" <select> when a text editor is available.
@ -111,7 +111,7 @@ class CKEditorAdminTest extends WebTestBase {
// Keep the "CKEditor" editor selected and click the "Configure" button.
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertFalse($editor, 'No Editor config entity exists yet.');
// Ensure that drupalSettings is correct.
@ -142,7 +142,7 @@ class CKEditorAdminTest extends WebTestBase {
// Ensure an Editor object exists now, with the proper settings.
$expected_settings = $expected_default_settings;
$expected_settings['plugins']['stylescombo']['styles'] = '';
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
@ -153,7 +153,7 @@ class CKEditorAdminTest extends WebTestBase {
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$expected_settings['plugins']['stylescombo']['styles'] = "h1.title|Title\np.callout|Callout\n\n";
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
@ -169,7 +169,7 @@ class CKEditorAdminTest extends WebTestBase {
'editor[settings][toolbar][button_groups]' => json_encode($expected_settings['toolbar']['rows']),
);
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
@ -197,7 +197,7 @@ class CKEditorAdminTest extends WebTestBase {
$this->drupalGet('admin/config/content/formats/manage/filtered_html');
$ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and not(@checked)]');
$this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is not checked.');
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
$this->assertEqual($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
@ -211,7 +211,7 @@ class CKEditorAdminTest extends WebTestBase {
$ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and @checked="checked"]');
$this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is checked.');
$expected_settings['plugins']['llama_contextual_and_button']['ultra_llama_mode'] = TRUE;
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
$this->assertEqual($expected_settings, $editor->getSettings());
}
@ -245,9 +245,9 @@ class CKEditorAdminTest extends WebTestBase {
'editor[editor]' => 'ckeditor',
);
$this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
$filter_format = entity_load('filter_format', 'amazing_format');
$filter_format = FilterFormat::load('amazing_format');
$this->assertFalse($filter_format, 'No FilterFormat config entity exists yet.');
$editor = entity_load('editor', 'amazing_format');
$editor = Editor::load('amazing_format');
$this->assertFalse($editor, 'No Editor config entity exists yet.');
// Ensure the toolbar buttons configuration value is initialized to the
@ -270,13 +270,13 @@ class CKEditorAdminTest extends WebTestBase {
$this->drupalPostForm(NULL, $edit, t('Save configuration'));
// Ensure a FilterFormat object exists now.
$filter_format = entity_load('filter_format', 'amazing_format');
$filter_format = FilterFormat::load('amazing_format');
$this->assertTrue($filter_format instanceof FilterFormatInterface, 'A FilterFormat config entity exists now.');
// Ensure an Editor object exists now, with the proper settings.
$expected_settings = $default_settings;
$expected_settings['plugins']['stylescombo']['styles'] = '';
$editor = entity_load('editor', 'amazing_format');
$editor = Editor::load('amazing_format');
$this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
$this->assertEqual($this->castSafeStrings($expected_settings), $this->castSafeStrings($editor->getSettings()), 'The Editor config entity has the correct settings.');
}

View file

@ -100,7 +100,7 @@ class CKEditorLoadingTest extends WebTestBase {
$this->drupalGet('node/add/article');
list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
$ckeditor_plugin = $this->container->get('plugin.manager.editor')->createInstance('ckeditor');
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$expected = array('formats' => array('filtered_html' => array(
'format' => 'filtered_html',
'editor' => 'ckeditor',

View file

@ -69,7 +69,7 @@ class CKEditorTest extends KernelTestBase {
* Tests CKEditor::getJSSettings().
*/
function testGetJSSettings() {
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
// Default toolbar.
$expected_config = $this->getDefaultInternalConfig() + array(
@ -217,7 +217,7 @@ class CKEditorTest extends KernelTestBase {
* Tests CKEditor::buildToolbarJSSetting().
*/
function testBuildToolbarJSSetting() {
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
// Default toolbar.
$expected = $this->getDefaultToolbarConfig();
@ -248,7 +248,7 @@ class CKEditorTest extends KernelTestBase {
* Tests CKEditor::buildContentsCssJSSetting().
*/
function testBuildContentsCssJSSetting() {
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
// Default toolbar.
$expected = $this->getDefaultContentsCssConfig();
@ -285,7 +285,7 @@ class CKEditorTest extends KernelTestBase {
* Tests Internal::getConfig().
*/
function testInternalGetConfig() {
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$internal_plugin = $this->container->get('plugin.manager.ckeditor.plugin')->createInstance('internal');
// Default toolbar.
@ -306,7 +306,7 @@ class CKEditorTest extends KernelTestBase {
* Tests StylesCombo::getConfig().
*/
function testStylesComboGetConfig() {
$editor = entity_load('editor', 'filtered_html');
$editor = Editor::load('filtered_html');
$stylescombo_plugin = $this->container->get('plugin.manager.ckeditor.plugin')->createInstance('stylescombo');
// Styles dropdown/button enabled: new setting should be present.

View file

@ -1,4 +1,5 @@
<?php
/**
* @file
* Allows users to change the color scheme of themes.

View file

@ -1,15 +1,15 @@
<?php
namespace Drupal\color\Tests;
namespace Drupal\Tests\color\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Ensures the color config schema is correct.
*
* @group color
*/
class ColorConfigSchemaTest extends WebTestBase {
class ColorConfigSchemaTest extends BrowserTestBase {
/**
* Modules to install.

View file

@ -1,16 +1,16 @@
<?php
namespace Drupal\color\Tests;
namespace Drupal\Tests\color\Functional;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Tests sanitizing color preview loaded from theme.
*
* @group Theme
* @group color
*/
class ColorSafePreviewTest extends WebTestBase {
class ColorSafePreviewTest extends BrowserTestBase {
/**
* Modules to enable.

View file

@ -1,8 +1,8 @@
<?php
namespace Drupal\color\Tests;
namespace Drupal\Tests\color\Functional;
use Drupal\simpletest\WebTestBase;
use Drupal\Tests\BrowserTestBase;
/**
* Modify the Bartik theme colors and make sure the changes are reflected on the
@ -10,7 +10,7 @@ use Drupal\simpletest\WebTestBase;
*
* @group color
*/
class ColorTest extends WebTestBase {
class ColorTest extends BrowserTestBase {
/**
* Modules to install.

View file

@ -6,6 +6,8 @@
*/
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\StringTranslation\PluralTranslatableMarkup;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\field\Entity\FieldStorageConfig;
/**
@ -134,3 +136,46 @@ function comment_update_8001() {
function comment_update_8002() {
// Empty update to cause a cache flush.
}
/**
* @addtogroup updates-8.2.x
* @{
*/
/**
* Add the 'view_mode' setting to displays having 'comment_default' formatter.
*/
function comment_update_8200() {
$config_factory = \Drupal::configFactory();
$displays = [];
// Iterate on all entity view displays.
foreach ($config_factory->listAll('core.entity_view_display.') as $name) {
$changed = FALSE;
$display = $config_factory->getEditable($name);
$components = $display->get('content') ?: [];
foreach ($components as $field_name => $component) {
if (isset($component['type']) && ($component['type'] === 'comment_default')) {
if (empty($display->get("content.{$field_name}.settings.view_mode"))) {
$display->set("content.{$field_name}.settings.view_mode", 'default');
$displays[] = $display->get('id');
$changed = TRUE;
}
}
}
if ($changed) {
$display->save(TRUE);
}
}
if ($displays) {
return new PluralTranslatableMarkup(count($displays), '1 entity display updated: @displays.', '@count entity displays updated: @displays.', ['@displays' => implode(', ', $displays)]);
}
else {
return new TranslatableMarkup('No entity view display updated.');
}
}
/**
* @} End of "addtogroup updates-8.2.x".
*/

View file

@ -14,6 +14,7 @@ use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\CommentType;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
@ -656,7 +657,7 @@ function template_preprocess_comment(&$variables) {
$variables['permalink'] = \Drupal::l(t('Permalink'), new Url('<front>'));
}
else {
$uri = $comment->urlInfo();
$uri = $comment->permalink();
$attributes = $uri->getOption('attributes') ?: array();
$attributes += array('class' => array('permalink'), 'rel' => 'bookmark');
$uri->setOption('attributes', $attributes);
@ -756,3 +757,43 @@ function comment_preprocess_field(&$variables) {
function comment_ranking() {
return \Drupal::service('comment.statistics')->getRankingInfo();
}
/**
* Implements hook_ENTITY_TYPE_presave() for entity_view_display entities.
*/
function comment_entity_view_display_presave(EntityViewDisplayInterface $display) {
// Act only on comment view displays being disabled.
if ($display->isNew() || $display->getTargetEntityTypeId() !== 'comment' || $display->status()) {
return;
}
$storage = \Drupal::entityTypeManager()->getStorage('entity_view_display');
if (!$storage->loadUnchanged($display->getOriginalId())->status()) {
return;
}
// Disable the comment field formatter when the used view display is disabled.
foreach ($storage->loadMultiple() as $id => $view_display) {
$changed = FALSE;
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
foreach ($view_display->getComponents() as $field => $component) {
if (isset($component['type']) && ($component['type'] === 'comment_default')) {
if ($component['settings']['view_mode'] === $display->getMode()) {
$view_display->removeComponent($field);
/** @var \Drupal\Core\Entity\EntityViewModeInterface $mode */
$mode = EntityViewMode::load($display->getTargetEntityTypeId() . '.' . $display->getMode());
$arguments = [
'@id' => $view_display->id(),
'@name' => $field,
'@display' => $mode->label(),
'@mode' => $display->getMode(),
];
\Drupal::logger('system')->warning("View display '@id': Comment field formatter '@name' was disabled because it is using the comment view display '@display' (@mode) that was just disabled.", $arguments);
$changed = TRUE;
}
}
}
if ($changed) {
$view_display->save();
}
}
}

View file

@ -7,6 +7,8 @@
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Datetime\Entity\DateFormat;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Render\BubbleableMetadata;
/**
@ -19,15 +21,28 @@ function comment_token_info() {
'needs-data' => 'comment',
);
// @todo Make this work per field. See https://www.drupal.org/node/2031903.
$entity['comment-count'] = array(
'name' => t("Comment count"),
'description' => t("The number of comments posted on an entity."),
);
$entity['comment-count-new'] = array(
'name' => t("New comment count"),
'description' => t("The number of comments posted on an entity since the reader last viewed it."),
);
$tokens = [];
// Provide a integration for each entity type except comment.
foreach (\Drupal::entityTypeManager()->getDefinitions() as $entity_type_id => $entity_type) {
if ($entity_type_id == 'comment' || !$entity_type->isSubclassOf(ContentEntityInterface::class)) {
continue;
}
if (\Drupal::service('comment.manager')->getFields($entity_type_id)) {
// Get the correct token type.
$token_type = ($entity_type_id == 'taxonomy_term') ? 'term' : $entity_type_id;
// @todo Make this work per field. See https://www.drupal.org/node/2031903.
$tokens[$token_type]['comment-count'] = [
'name' => t("Comment count"),
'description' => t("The number of comments posted on an entity."),
];
$tokens[$token_type]['comment-count-new'] = [
'name' => t("New comment count"),
'description' => t("The number of comments posted on an entity since the reader last viewed it."),
];
}
}
// Core comment tokens
$comment['cid'] = array(
@ -97,9 +112,8 @@ function comment_token_info() {
return array(
'types' => array('comment' => $type),
'tokens' => array(
'entity' => $entity,
'comment' => $comment,
),
) + $tokens,
);
}
@ -235,9 +249,10 @@ function comment_tokens($type, $tokens, array $data, array $options, BubbleableM
$replacements += $token_service->generate('user', $author_tokens, array('user' => $account), $options, $bubbleable_metadata);
}
}
elseif ($type == 'entity' & !empty($data['entity'])) {
// Replacement tokens for any content entities that have comment field.
elseif (!empty($data[$type]) && $data[$type] instanceof FieldableEntityInterface) {
/** @var $entity \Drupal\Core\Entity\FieldableEntityInterface */
$entity = $data['entity'];
$entity = $data[$type];
foreach ($tokens as $name => $original) {
switch ($name) {

View file

@ -173,7 +173,7 @@ display:
entity_field: changed
filters:
status:
value: true
value: '1'
table: comment_field_data
field: status
id: status
@ -184,7 +184,7 @@ display:
entity_type: comment
entity_field: status
status_node:
value: true
value: '1'
table: node_field_data
field: status
relationship: node

View file

@ -4,6 +4,9 @@ field.formatter.settings.comment_default:
type: mapping
label: 'Comment display format settings'
mapping:
view_mode:
type: string
label: 'The comment entity view mode to be used in this formatter'
pager_id:
type: integer
label: 'Pager ID'

View file

@ -32,7 +32,7 @@ interface CommentInterface extends ContentEntityInterface, EntityChangedInterfac
/**
* Returns the parent comment entity if this is a reply to a comment.
*
* @return \Drupal\comment\CommentInterface|NULL
* @return \Drupal\comment\CommentInterface|null
* A comment entity of the parent comment or NULL if there is no parent.
*/
public function getParentComment();

View file

@ -79,8 +79,7 @@ class CommentController extends ControllerBase {
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse.
* Redirects to the permalink URL for this comment.
* @return \Symfony\Component\HttpFoundation\RedirectResponse
*/
public function commentApprove(CommentInterface $comment) {
$comment->setPublished(TRUE);
@ -125,9 +124,8 @@ class CommentController extends ControllerBase {
// Find the current display page for this comment.
$page = $this->entityManager()->getStorage('comment')->getDisplayOrdinal($comment, $field_definition->getSetting('default_mode'), $field_definition->getSetting('per_page'));
// @todo: Cleaner sub request handling.
$subrequest_url = $entity->urlInfo()->toString(TRUE);
$subrequest_url = $entity->urlInfo()->setOption('query', ['page' => $page])->toString(TRUE);
$redirect_request = Request::create($subrequest_url->getGeneratedUrl(), 'GET', $request->query->all(), $request->cookies->all(), array(), $request->server->all());
$redirect_request->query->set('page', $page);
// Carry over the session to the subrequest.
if ($session = $request->getSession()) {
$redirect_request->setSession($session);

View file

@ -208,16 +208,18 @@ class Comment extends ContentEntityBase implements CommentInterface {
* {@inheritdoc}
*/
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields['cid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Comment ID'))
->setDescription(t('The comment ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
/** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
$fields = parent::baseFieldDefinitions($entity_type);
$fields['uuid'] = BaseFieldDefinition::create('uuid')
->setLabel(t('UUID'))
->setDescription(t('The comment UUID.'))
->setReadOnly(TRUE);
$fields['cid']->setLabel(t('Comment ID'))
->setDescription(t('The comment ID.'));
$fields['uuid']->setDescription(t('The comment UUID.'));
$fields['comment_type']->setLabel(t('Comment Type'))
->setDescription(t('The comment type.'));
$fields['langcode']->setDescription(t('The comment language code.'));
$fields['pid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Parent ID'))
@ -229,18 +231,6 @@ class Comment extends ContentEntityBase implements CommentInterface {
->setDescription(t('The ID of the entity of which this comment is a reply.'))
->setRequired(TRUE);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The comment language code.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', array(
'type' => 'hidden',
))
->setDisplayOptions('form', array(
'type' => 'language_select',
'weight' => 2,
));
$fields['subject'] = BaseFieldDefinition::create('string')
->setLabel(t('Subject'))
->setTranslatable(TRUE)
@ -312,11 +302,6 @@ class Comment extends ContentEntityBase implements CommentInterface {
->setSetting('is_ascii', TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH);
$fields['comment_type'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Comment Type'))
->setDescription(t('The comment type.'))
->setSetting('target_type', 'comment_type');
$fields['field_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Comment field name'))
->setDescription(t('The field name through which this comment was added.'))

View file

@ -151,10 +151,10 @@ class CommentAdminOverview extends FormBase {
'operations' => $this->t('Operations'),
);
$cids = $this->commentStorage->getQuery()
->condition('status', $status)
->tableSort($header)
->pager(50)
->execute();
->condition('status', $status)
->tableSort($header)
->pager(50)
->execute();
/** @var $comments \Drupal\comment\CommentInterface[] */
$comments = $this->commentStorage->loadMultiple($cids);

View file

@ -3,6 +3,7 @@
namespace Drupal\comment\Plugin\Field\FieldFormatter;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityFormBuilderInterface;
use Drupal\Core\Field\FieldItemListInterface;
@ -36,6 +37,7 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
*/
public static function defaultSettings() {
return array(
'view_mode' => 'default',
'pager_id' => 0,
) + parent::defaultSettings();
}
@ -167,7 +169,7 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
$comments_per_page = $comment_settings['per_page'];
$comments = $this->storage->loadThread($entity, $field_name, $mode, $comments_per_page, $this->getSetting('pager_id'));
if ($comments) {
$build = $this->viewBuilder->viewMultiple($comments);
$build = $this->viewBuilder->viewMultiple($comments, $this->getSetting('view_mode'));
$build['pager']['#type'] = 'pager';
// CommentController::commentPermalink() calculates the page number
// where a specific comment appears and does a subrequest pointing to
@ -217,6 +219,16 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$element = array();
$view_modes = $this->getViewModes();
$element['view_mode'] = [
'#type' => 'select',
'#title' => $this->t('Comments view mode'),
'#description' => $this->t('Select the view mode used to show the list of comments.'),
'#default_value' => $this->getSetting('view_mode'),
'#options' => $view_modes,
// Only show the select element when there are more than one options.
'#access' => count($view_modes) > 1,
];
$element['pager_id'] = array(
'#type' => 'select',
'#title' => $this->t('Pager ID'),
@ -231,13 +243,41 @@ class CommentDefaultFormatter extends FormatterBase implements ContainerFactoryP
* {@inheritdoc}
*/
public function settingsSummary() {
// Only show a summary if we're using a non-standard pager id.
if ($this->getSetting('pager_id')) {
return array($this->t('Pager ID: @id', array(
'@id' => $this->getSetting('pager_id'),
)));
$view_mode = $this->getSetting('view_mode');
$view_modes = $this->getViewModes();
$view_mode_label = isset($view_modes[$view_mode]) ? $view_modes[$view_mode] : 'default';
$summary = [$this->t('Comment view mode: @mode', ['@mode' => $view_mode_label])];
if ($pager_id = $this->getSetting('pager_id')) {
$summary[] = $this->t('Pager ID: @id', ['@id' => $pager_id]);
}
return array();
return $summary;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
if ($mode = $this->getSetting('view_mode')) {
if ($bundle = $this->getFieldSetting('comment_type')) {
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
if ($display = EntityViewDisplay::load("comment.$bundle.$mode")) {
$dependencies[$display->getConfigDependencyKey()][] = $display->getConfigDependencyName();
}
}
}
return $dependencies;
}
/**
* Provides a list of comment view modes for the configured comment type.
*
* @return array
* Associative array keyed by view mode key and having the view mode label
* as value.
*/
protected function getViewModes() {
return $this->entityManager->getViewModeOptionsByBundle('comment', $this->getFieldSetting('comment_type'));
}
}

View file

@ -29,12 +29,12 @@ class CommentActionsTest extends CommentTestBase {
$comment = $this->postComment($this->node, $comment_text, $subject);
// Unpublish a comment.
$action = entity_load('action', 'comment_unpublish_action');
$action = Action::load('comment_unpublish_action');
$action->execute(array($comment));
$this->assertTrue($comment->isPublished() === FALSE, 'Comment was unpublished');
// Publish a comment.
$action = entity_load('action', 'comment_publish_action');
$action = Action::load('comment_publish_action');
$action->execute(array($comment));
$this->assertTrue($comment->isPublished() === TRUE, 'Comment was published');
}

Some files were not shown because too many files have changed in this diff Show more