Update to Drupal 8.0.0-rc3. For more information, see https://www.drupal.org/node/2608078

This commit is contained in:
Pantheon Automation 2015-11-04 11:11:27 -08:00 committed by Greg Anderson
parent 6419a031d7
commit 4afb23bbd3
762 changed files with 20080 additions and 6368 deletions

View file

@ -21,3 +21,19 @@ function aggregator_requirements($phase) {
}
return $requirements;
}
/**
* @addtogroup updates-8.0.0-rc
* @{
*/
/**
* The simple presence of this update function clears cached field definitions.
*/
function aggregator_update_8001() {
// Feed ID base field is now required.
}
/**
* @} End of "addtogroup updates-8.0.0-rc".
*/

View file

@ -53,7 +53,7 @@ use Drupal\aggregator\FeedInterface;
class Feed extends ContentEntityBase implements FeedInterface {
/**
* Implements Drupal\Core\Entity\EntityInterface::label().
* {@inheritdoc}
*/
public function label() {
return $this->get('title')->value;

View file

@ -42,7 +42,7 @@ use Drupal\Core\Url;
class Item extends ContentEntityBase implements ItemInterface {
/**
* Implements Drupal\Core\Entity\EntityInterface::label().
* {@inheritdoc}
*/
public function label() {
return $this->get('title')->value;
@ -60,6 +60,7 @@ class Item extends ContentEntityBase implements ItemInterface {
$fields['fid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Source feed'))
->setRequired(TRUE)
->setDescription(t('The aggregator feed entity associated with this item.'))
->setSetting('target_type', 'aggregator_feed')
->setDisplayOptions('view', array(

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\aggregator\FeedHtmlRouteProvider.
* Contains \Drupal\aggregator\FeedHtmlRouteProvider.
*/
namespace Drupal\aggregator;

View file

@ -50,7 +50,6 @@ class Rss extends RssPluginBase {
$item = new \stdClass();
foreach ($entity as $name => $field) {
// views_view_row_rss takes care about the escaping.
$item->{$name} = $field->value;
}

View file

@ -166,7 +166,7 @@ abstract class AggregatorTestBase extends WebTestBase {
public function updateFeedItems(FeedInterface $feed, $expected_count = NULL) {
// First, let's ensure we can get to the rss xml.
$this->drupalGet($feed->getUrl());
$this->assertResponse(200, format_string('!url is reachable.', array('!url' => $feed->getUrl())));
$this->assertResponse(200, format_string(':url is reachable.', array(':url' => $feed->getUrl())));
// Attempt to access the update link directly without an access token.
$this->drupalGet('admin/config/services/aggregator/update/' . $feed->id());

View file

@ -0,0 +1,46 @@
<?php
/**
* @file
* Contains \Drupal\aggregator\Tests\ItemWithoutFeedTest.
*/
namespace Drupal\aggregator\Tests;
use Drupal\aggregator\Entity\Item;
use Drupal\KernelTests\KernelTestBase;
/**
* Tests clean handling of an item with a missing feed ID.
*
* @group aggregator
*/
class ItemWithoutFeedTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['aggregator', 'options'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installEntitySchema('aggregator_feed');
$this->installEntitySchema('aggregator_item');
}
/**
* Tests attempting to create a feed item without a feed.
*/
public function testEntityCreation() {
$entity = Item::create([
'title' => t('Llama 2'),
'path' => 'https://groups.drupal.org/',
]);
$violations = $entity->validate();
$this->assertCount(1, $violations);
}
}

View file

@ -25,7 +25,7 @@ use Drupal\aggregator\FeedInterface;
class TestFetcher extends DefaultFetcher implements FetcherInterface {
/**
* Implements \Drupal\aggregator\Plugin\FetcherInterface::fetch().
* {@inheritdoc}
*/
public function fetch(FeedInterface $feed) {
if ($feed->label() == 'Do not fetch') {

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\Tests\ban\Unit\Plugin\migrate\source\d7\BlockedIps.
* Contains \Drupal\Tests\ban\Unit\Plugin\migrate\source\d7\BlockedIpsTest.
*/
namespace Drupal\Tests\ban\Unit\Plugin\migrate\source\d7;

View file

@ -186,8 +186,6 @@ class BlockViewBuilder extends EntityViewBuilder {
* A block config entity ID.
* @param $view_mode
* The view mode the block is being viewed in.
* @param $langcode
* The langcode the block is being viewed in.
*
* @return array
* A render array with a #pre_render callback to render the block.

View file

@ -207,7 +207,6 @@ class BlockUiTest extends WebTestBase {
$this->assertTrue(!empty($elements), 'The context-aware test block appears.');
$definition = \Drupal::service('plugin.manager.block')->getDefinition('test_context_aware');
$this->assertTrue(!empty($definition), 'The context-aware test block exists.');
$edit = [
'region' => 'content',
'settings[context_mapping][user]' => '@block_test.multiple_static_context:user2',
@ -217,6 +216,15 @@ class BlockUiTest extends WebTestBase {
$this->drupalGet('');
$this->assertText('Test context-aware block');
$this->assertRaw($expected_text);
// Test context mapping allows empty selection for optional contexts.
$this->drupalGet('admin/structure/block/manage/testcontextawareblock');
$edit = [
'settings[context_mapping][user]' => '',
];
$this->drupalPostForm(NULL, $edit, 'Save block');
$this->drupalGet('');
$this->assertText('No context mapping selected.');
}
/**

View file

@ -61,8 +61,6 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
*
* @param string $id
* The block ID.
* @param string $module
* The module.
* @param array $visibility
* The block visibility settings.
* @param string $region

View file

@ -9,8 +9,7 @@
* - configuration: A list of the block's configuration values.
* - label: The configured label for the block.
* - label_display: The display settings for the label.
* - module: The module that provided this block plugin.
* - cache: The cache settings.
* - provider: The module or other provider that provided this block plugin.
* - Block plugin specific settings will also be stored here.
* - content: The content of this block.
* - attributes: array of HTML attributes populated by modules, intended to

View file

@ -16,7 +16,7 @@ use Drupal\Core\Block\BlockBase;
* id = "test_context_aware",
* admin_label = @Translation("Test context-aware block"),
* context = {
* "user" = @ContextDefinition("entity:user")
* "user" = @ContextDefinition("entity:user", required = FALSE)
* }
* )
*/
@ -31,7 +31,7 @@ class TestContextAwareBlock extends BlockBase {
return array(
'#prefix' => '<div id="' . $this->getPluginId() . '--username">',
'#suffix' => '</div>',
'#markup' => $user->getUsername(),
'#markup' => $user ? $user->getUsername() : 'No context mapping selected.' ,
);
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\block_test\Plugin\Block\TestContextAwareBlock.
* Contains \Drupal\block_test\Plugin\Block\TestContextAwareUnsatisfiedBlock.
*/
namespace Drupal\block_test\Plugin\Block;

View file

@ -57,6 +57,13 @@ class BlockContentBlock extends BlockBase implements ContainerFactoryPluginInter
*/
protected $blockContent;
/**
* The URL generator.
*
* @var \Drupal\Core\Routing\UrlGeneratorInterface
*/
protected $urlGenerator;
/**
* Constructs a new BlockContentBlock.
*
@ -66,12 +73,14 @@ class BlockContentBlock extends BlockBase implements ContainerFactoryPluginInter
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Block\BlockManagerInterface
* @param \Drupal\Core\Block\BlockManagerInterface $block_manager
* The Plugin Block Manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Session\AccountInterface $account
* The account for which view access should be checked.
* @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
* The URL generator.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, BlockManagerInterface $block_manager, EntityManagerInterface $entity_manager, AccountInterface $account, UrlGeneratorInterface $url_generator) {
parent::__construct($configuration, $plugin_id, $plugin_definition);

View file

@ -65,7 +65,7 @@ abstract class BlockContentTestBase extends WebTestBase {
/**
* Creates a custom block.
*
* @param string $title
* @param bool|string $title
* (optional) Title of block. When no value is given uses a random name.
* Defaults to FALSE.
* @param string $bundle

View file

@ -44,7 +44,7 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
];
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
* {@inheritdoc}
*/
protected function setUp() {
$this->entityTypeId = 'block_content';
@ -69,7 +69,7 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
* {@inheritdoc}
*/
public function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array(
@ -83,10 +83,10 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
/**
* Creates a custom block.
*
* @param string $title
* @param bool|string $title
* (optional) Title of block. When no value is given uses a random name.
* Defaults to FALSE.
* @param string $bundle
* @param bool|string $bundle
* (optional) Bundle name. When no value is given, defaults to
* $this->bundle. Defaults to FALSE.
*
@ -106,7 +106,7 @@ class BlockContentTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getNewEntityValues().
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
return array('info' => Unicode::strtolower($this->randomMachineName())) + parent::getNewEntityValues($langcode);

View file

@ -65,12 +65,12 @@ class BookExport {
* @param \Drupal\node\NodeInterface $node
* The node to export.
*
* @throws \Exception
* Thrown when the node was not attached to a book.
*
* @return array
* A render array representing the HTML for a node and its children in the
* book hierarchy.
*
* @throws \Exception
* Thrown when the node was not attached to a book.
*/
public function bookExportHtml(NodeInterface $node) {
if (!isset($node->book)) {

View file

@ -488,7 +488,6 @@ class BookManager implements BookManagerInterface {
* {@inheritdoc}
*/
public function getActiveTrailIds($bid, $link) {
$nid = isset($link['nid']) ? $link['nid'] : 0;
// The tree is for a single item, so we need to match the values in its
// p columns and 0 (the top level) with the plid values of other links.
$active_trail = array(0);

View file

@ -9,6 +9,7 @@ namespace Drupal\book\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\book\BookManagerInterface;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -182,12 +183,7 @@ class BookNavigationBlock extends BlockBase implements ContainerFactoryPluginInt
* {@inheritdoc}
*/
public function getCacheContexts() {
// The "Book navigation" block must be cached per role and book navigation
// context.
return [
'user.roles',
'route.book_navigation',
];
return Cache::mergeContexts(parent::getCacheContexts(), ['route.book_navigation']);
}
/**

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\book\ProxyClass\BookUninstallValidator.
* Contains \Drupal\book\ProxyClass\BookUninstallValidator.
*/
/**

View file

@ -123,7 +123,7 @@ function template_preprocess_ckeditor_settings_toolbar(&$variables) {
'buttons' => array(),
);
$buttons = array_filter($button_row, function ($button) use ($group_name) {
return $button['group'] === $group_name;
return (string) $button['group'] === $group_name;
});
foreach ($buttons as $button) {
$variables['active_buttons'][$row_number][$group_name]['buttons'][] = $build_button_item($button, $rtl);

View file

@ -34,6 +34,12 @@
margin: 0;
padding: 0;
}
/* This is required to win over specificity of [dir="rtl"] ul */
[dir="rtl"] .ckeditor-toolbar ul,
[dir="rtl"] .ckeditor-toolbar-disabled ul {
margin-right: 0;
}
.ckeditor-row {
padding: 2px 0 3px;
border-radius: 3px;
@ -121,13 +127,19 @@
box-sizing: border-box;
}
.ckeditor-toolbar-disabled .ckeditor-toolbar-available {
float: left;
float: left; /* LTR */
width: 80%;
}
.ckeditor-toolbar-disabled .ckeditor-toolbar-dividers {
[dir="rtl"] .ckeditor-toolbar-disabled .ckeditor-toolbar-available {
float: right;
}
.ckeditor-toolbar-disabled .ckeditor-toolbar-dividers {
float: right; /* LTR */
width: 20%;
}
[dir="rtl"] .ckeditor-toolbar-disabled .ckeditor-toolbar-dividers {
float: left;
}
.ckeditor-toolbar-disabled .ckeditor-buttons li a,
.ckeditor-toolbar .ckeditor-buttons,
.ckeditor-add-new-group button {

View file

@ -29,50 +29,55 @@
return;
}
// Override requiredContent & allowedContent.
widgetDefinition.requiredContent = new CKEDITOR.style({
element: 'img',
styles: {},
attributes: {
'alt': '',
'src': '',
'width': '',
'height': '',
'data-entity-type': '',
'data-entity-uuid': ''
}
});
var allowedContentDefinition = {
element: 'img',
styles: {},
attributes: {
'!data-entity-type': '',
'!data-entity-uuid': ''
// First, convert requiredContent & allowedContent from the string
// format that image2 uses for both to formats that are better suited
// for extending, so that both this basic drupalimage plugin and Drupal
// modules can easily extend it.
// @see http://docs.ckeditor.com/#!/api/CKEDITOR.filter.allowedContentRules
// Mapped from image2's allowedContent. Unlike image2, we don't allow
// <figure>, <figcaption>, <div> or <p> in our downcast, so we omit
// those. For the <img> tag, we list all attributes it lists, but omit
// the classes, because the listed classes are for alignment, and for
// alignment we use the data-align attribute.
widgetDefinition.allowedContent = {
img: {
attributes: {
'!src': true,
'!alt': true,
'width': true,
'height': true
},
classes: {}
}
};
var imgAttributes = widgetDefinition.allowedContent.img.attributes.split(/\s*,\s*/);
for (var i = 0; i < imgAttributes.length; i++) {
allowedContentDefinition.attributes[imgAttributes[i]] = '';
}
if (widgetDefinition.allowedContent.img.classes) {
allowedContentDefinition.attributes['class'] = widgetDefinition.allowedContent.img.classes.split(/\s*,\s*/).join(' ');
}
if (widgetDefinition.allowedContent.img.styles) {
var imgStyles = widgetDefinition.allowedContent.img.styles.split(/\s*,\s*/);
for (var j = 0; j < imgStyles.length; j++) {
allowedContentDefinition.styles[imgStyles[j]] = '';
// Mapped from image2's requiredContent: "img[src,alt]". This does not
// use the object format unlike above, but a CKEDITOR.style instance,
// because requiredContent does not support the object format.
// @see https://www.drupal.org/node/2585173#comment-10456981
widgetDefinition.requiredContent = new CKEDITOR.style({
element: 'img',
attributes: {
src: '',
alt: ''
}
}
widgetDefinition.allowedContent = new CKEDITOR.style(allowedContentDefinition);
});
// Override the 'link' part, to completely disable image2's link
// support: http://dev.ckeditor.com/ticket/11341.
widgetDefinition.parts.link = 'This is a nonsensical selector to disable this functionality completely';
// Extend requiredContent & allowedContent.
// CKEDITOR.style is an immutable object: we cannot modify its
// definition to extend requiredContent. Hence we get the definition,
// modify it, and pass it to a new CKEDITOR.style instance.
var requiredContent = widgetDefinition.requiredContent.getDefinition();
requiredContent.attributes['data-entity-type'] = '';
requiredContent.attributes['data-entity-uuid'] = '';
widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
widgetDefinition.allowedContent.img.attributes['!data-entity-type'] = true;
widgetDefinition.allowedContent.img.attributes['!data-entity-uuid'] = true;
// Override downcast(): since we only accept <img> in our upcast method,
// the element is already correct. We only need to update the element's
// data-entity-uuid attribute.
widgetDefinition.downcast = function (element) {
element.attributes['data-entity-type'] = this.data['data-entity-type'];
element.attributes['data-entity-uuid'] = this.data['data-entity-uuid'];
};
@ -176,6 +181,18 @@
return widget;
};
};
var originalInit = widgetDefinition.init;
widgetDefinition.init = function () {
originalInit.call(this);
// Update data.link object with attributes if the link has been
// discovered.
// @see plugins/image2/plugin.js/init() in CKEditor; this is similar.
if (this.parts.link) {
this.setData('link', CKEDITOR.plugins.link.parseLinkAttributes(editor, this.parts.link));
}
};
});
// Add a widget#edit listener to every instance of image2 widget in order
@ -233,25 +250,86 @@
}
},
// Disable image2's integration with the link/drupallink plugins: don't
// allow the widget itself to become a link. Support for that may be added
// by an text filter that adds a data- attribute specifically for that.
afterInit: function (editor) {
if (editor.plugins.drupallink) {
var cmd = editor.getCommand('drupallink');
// Needs to be refreshed on selection changes.
cmd.contextSensitive = 1;
// Disable command and cancel event when the image widget is selected.
cmd.on('refresh', function (evt) {
var widget = editor.widgets.focused;
if (widget && widget.name === 'image') {
this.setState(CKEDITOR.TRISTATE_DISABLED);
evt.cancel();
}
});
}
linkCommandIntegrator(editor);
}
});
/**
* Integrates the drupalimage widget with the drupallink plugin.
*
* Makes images linkable.
*
* @param {CKEDITOR.editor} editor
* A CKEditor instance.
*/
function linkCommandIntegrator(editor) {
// Nothing to integrate with if the drupallink plugin is not loaded.
if (!editor.plugins.drupallink) {
return;
}
// Override default behaviour of 'drupalunlink' command.
editor.getCommand('drupalunlink').on('exec', function (evt) {
var widget = getFocusedWidget(editor);
// Override 'drupalunlink' only when link truly belongs to the widget. If
// wrapped inline widget in a link, let default unlink work.
// @see https://dev.ckeditor.com/ticket/11814
if (!widget || !widget.parts.link) {
return;
}
widget.setData('link', null);
// Selection (which is fake) may not change if unlinked image in focused
// widget, i.e. if captioned image. Let's refresh command state manually
// here.
this.refresh(editor, editor.elementPath());
evt.cancel();
});
// Override default refresh of 'drupalunlink' command.
editor.getCommand('drupalunlink').on('refresh', function (evt) {
var widget = getFocusedWidget(editor);
if (!widget) {
return;
}
// Note that widget may be wrapped in a link, which
// does not belong to that widget (#11814).
this.setState(widget.data.link || widget.wrapper.getAscendant('a') ?
CKEDITOR.TRISTATE_OFF : CKEDITOR.TRISTATE_DISABLED);
evt.cancel();
});
}
/**
* Gets the focused widget, if of the type specific for this plugin.
*
* @param {CKEDITOR.editor} editor
* A CKEditor instance.
*
* @return {?CKEDITOR.plugins.widget}
* The focused image2 widget instance, or null.
*/
function getFocusedWidget(editor) {
var widget = editor.widgets.focused;
if (widget && widget.name === 'image') {
return widget;
}
return null;
}
// Expose an API for other plugins to interact with drupalimage widgets.
CKEDITOR.plugins.drupalimage = {
getFocusedWidget: getFocusedWidget
};
})(jQuery, Drupal, CKEDITOR);

View file

@ -50,15 +50,16 @@
}
}, true);
// Override requiredContent & allowedContent.
// Extend requiredContent & allowedContent.
// CKEDITOR.style is an immutable object: we cannot modify its
// definition to extend requiredContent. Hence we get the definition,
// modify it, and pass it to a new CKEDITOR.style instance.
var requiredContent = widgetDefinition.requiredContent.getDefinition();
requiredContent.attributes['data-align'] = '';
requiredContent.attributes['data-caption'] = '';
widgetDefinition.requiredContent = new CKEDITOR.style(requiredContent);
var allowedContent = widgetDefinition.allowedContent.getDefinition();
allowedContent.attributes['!data-align'] = '';
allowedContent.attributes['!data-caption'] = '';
widgetDefinition.allowedContent = new CKEDITOR.style(allowedContent);
widgetDefinition.allowedContent.img.attributes['!data-align'] = true;
widgetDefinition.allowedContent.img.attributes['!data-caption'] = true;
// Override allowedContent setting for the 'caption' nested editable.
// This must match what caption_filter enforces.
@ -71,10 +72,9 @@
// data-caption attributes.
var originalDowncast = widgetDefinition.downcast;
widgetDefinition.downcast = function (element) {
var img = originalDowncast.call(this, element);
if (!img) {
img = findElementByName(element, 'img');
}
var img = findElementByName(element, 'img');
originalDowncast.call(this, img);
var caption = this.editables.caption;
var captionHtml = caption && caption.getData();
var attrs = img.attributes;
@ -91,10 +91,14 @@
attrs['data-align'] = this.data.align;
}
}
attrs['data-entity-type'] = this.data['data-entity-type'];
attrs['data-entity-uuid'] = this.data['data-entity-uuid'];
return img;
// If img is wrapped with a link, we want to return that link.
if (img.parent.name === 'a') {
return img.parent;
}
else {
return img;
}
};
// We want to upcast <img> elements to a DOM structure required by the
@ -115,6 +119,11 @@
element = originalUpcast.call(this, element, data);
var attrs = element.attributes;
if (element.parent.name === 'a') {
element = element.parent;
}
var retElement = element;
var caption;

View file

@ -13,17 +13,17 @@
init: function (editor) {
// Add the commands for link and unlink.
editor.addCommand('drupallink', {
allowedContent: new CKEDITOR.style({
element: 'a',
styles: {},
attributes: {
'!href': '',
'target': ''
allowedContent: {
a: {
attributes: {
'!href': true,
'target': true
},
classes: {}
}
}),
},
requiredContent: new CKEDITOR.style({
element: 'a',
styles: {},
attributes: {
href: ''
}
@ -31,6 +31,8 @@
modes: {wysiwyg: 1},
canUndo: true,
exec: function (editor) {
var drupalImageUtils = CKEDITOR.plugins.drupalimage;
var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
var linkElement = getSelectedLink(editor);
var linkDOMElement = null;
@ -56,9 +58,30 @@
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
}
}
// Or, if an image widget is focused, we're editing a link wrapping
// an image widget.
else if (focusedImageWidget && focusedImageWidget.data.link) {
var url = focusedImageWidget.data.link.url;
existingValues.href = url.protocol + url.url;
}
// Prepare a save callback to be used upon saving the dialog.
var saveCallback = function (returnValues) {
// If an image widget is focused, we're not editing an independent
// link, but we're wrapping an image widget in a link.
if (focusedImageWidget) {
var urlMatch = returnValues.attributes.href.match(urlRegex);
focusedImageWidget.setData('link', {
type: 'url',
url: {
protocol: urlMatch[1],
url: urlMatch[2]
}
});
editor.fire('saveSnapshot');
return;
}
editor.fire('saveSnapshot');
// Create a new link element if needed.
@ -124,13 +147,14 @@
editor.addCommand('drupalunlink', {
contextSensitive: 1,
startDisabled: 1,
allowedContent: new CKEDITOR.style({
element: 'a',
attributes: {
'!href': '',
'target': ''
allowedContent: {
a: {
attributes: {
'!href': true,
'target': true
}
}
}),
},
requiredContent: new CKEDITOR.style({
element: 'a',
attributes: {
@ -256,4 +280,57 @@
return null;
}
var urlRegex = /^((?:http|https):\/\/)?(.*)$/;
/**
* The image2 plugin is currently tightly coupled to the link plugin: it
* calls CKEDITOR.plugins.link.parseLinkAttributes().
*
* Drupal 8's CKEditor build doesn't include the 'link' plugin. Because it
* includes its own link plugin that integrates with Drupal's dialog system.
* So, to allow images to be linked, we need to duplicate the necessary subset
* of the logic.
*
* @todo Remove once we update to CKEditor 4.5.5.
* @see https://dev.ckeditor.com/ticket/13885
*/
CKEDITOR.plugins.link = CKEDITOR.plugins.link || {
parseLinkAttributes: function (editor, element) {
var href = (element && (element.data('cke-saved-href') || element.getAttribute('href'))) || '';
var urlMatch = href.match(urlRegex);
return {
type: 'url',
url: {
protocol: urlMatch[1],
url: urlMatch[2]
}
};
},
getLinkAttributes: function (editor, data) {
var set = {};
var protocol = (data.url && typeof data.url.protocol !== 'undefined') ? data.url.protocol : 'http://';
var url = (data.url && CKEDITOR.tools.trim(data.url.url)) || '';
set['data-cke-saved-href'] = (url.indexOf('/') === 0) ? url : protocol + url;
// Browser need the "href" fro copy/paste link to work. (#6641)
if (set['data-cke-saved-href']) {
set.href = set['data-cke-saved-href'];
}
// Remove all attributes which are not currently set.
var removed = {};
for (var s in set) {
if (set.hasOwnProperty(s)) {
delete removed[s];
}
}
return {
set: set,
removed: CKEDITOR.tools.objectKeys(removed)
};
}
};
})(jQuery, Drupal, drupalSettings, CKEDITOR);

View file

@ -75,14 +75,14 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
* {@inheritdoc}
*/
public function isInternal() {
return TRUE;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
public function getFile() {
// This plugin is already part of Drupal core's CKEditor build.
@ -90,7 +90,7 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
* {@inheritdoc}
*/
public function getConfig(Editor $editor) {
// Reasonable defaults that provide expected basic behavior.
@ -123,7 +123,7 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
* {@inheritdoc}
*/
public function getButtons() {
$button = function($name, $direction = 'ltr') {
@ -538,16 +538,26 @@ class Internal extends CKEditorPluginBase implements ContainerFactoryPluginInter
if (count($allowed_attributes)) {
$allowed[$tag]['attributes'] = implode(',', array_keys($allowed_attributes));
}
if (isset($allowed_attributes['style']) && is_array($allowed_attributes['style'])) {
$allowed_styles = $get_attribute_values($allowed_attributes['style'], TRUE);
if (isset($allowed_styles)) {
$allowed[$tag]['styles'] = $allowed_styles;
if (isset($allowed_attributes['style'])) {
if (is_bool($allowed_attributes['style'])) {
$allowed[$tag]['styles'] = $allowed_attributes['style'];
}
elseif (is_array($allowed_attributes['style'])) {
$allowed_classes = $get_attribute_values($allowed_attributes['style'], TRUE);
if (isset($allowed_classes)) {
$allowed[$tag]['styles'] = $allowed_classes;
}
}
}
if (isset($allowed_attributes['class']) && is_array($allowed_attributes['class'])) {
$allowed_classes = $get_attribute_values($allowed_attributes['class'], TRUE);
if (isset($allowed_classes)) {
$allowed[$tag]['classes'] = $allowed_classes;
if (isset($allowed_attributes['class'])) {
if (is_bool($allowed_attributes['class'])) {
$allowed[$tag]['classes'] = $allowed_attributes['class'];
}
elseif (is_array($allowed_attributes['class'])) {
$allowed_classes = $get_attribute_values($allowed_attributes['class'], TRUE);
if (isset($allowed_classes)) {
$allowed[$tag]['classes'] = $allowed_classes;
}
}
}

View file

@ -23,14 +23,14 @@ use Drupal\editor\Entity\Editor;
class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
* {@inheritdoc}
*/
public function isInternal() {
return TRUE;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
public function getFile() {
// This plugin is already part of Drupal core's CKEditor build.
@ -38,7 +38,7 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
* {@inheritdoc}
*/
public function getConfig(Editor $editor) {
$config = array();
@ -52,7 +52,7 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
* {@inheritdoc}
*/
public function getButtons() {
return array(
@ -70,7 +70,7 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
// Defaults.
@ -85,7 +85,7 @@ class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurab
'#title_display' => 'invisible',
'#type' => 'textarea',
'#default_value' => $config['styles'],
'#description' => t('A list of classes that will be provided in the "Styles" dropdown. Enter one class on each line in the format: element.class|Label. Example: h1.title|Title.<br />These styles should be available in your theme\'s CSS file.'),
'#description' => t('A list of classes that will be provided in the "Styles" dropdown. Enter one or more classes on each line in the format: element.classA.classB|Label. Example: h1.title|Title. Advanced example: h1.fancy.title|Fancy title.<br />These styles should be available in your theme\'s CSS file.'),
'#attached' => array(
'library' => array('ckeditor/drupal.ckeditor.stylescombo.admin'),
),

View file

@ -261,6 +261,9 @@ class CKEditorAdminTest extends WebTestBase {
$expected_buttons_value = json_encode($default_settings['toolbar']['rows']);
$this->assertFieldByName('editor[settings][toolbar][button_groups]', $expected_buttons_value);
// Regression test for https://www.drupal.org/node/2606460.
$this->assertTrue(strpos($this->drupalSettings['ckeditor']['toolbarAdmin'], '<li data-drupal-ckeditor-button-name="Bold" class="ckeditor-button"><a href="#" class="cke-icon-only cke_ltr" role="button" title="bold" aria-label="bold"><span class="cke_button_icon cke_button__bold_icon">bold</span></a></li>') !== FALSE);
// Ensure the styles textarea exists and is initialized empty.
$styles_textarea = $this->xpath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]');
$this->assertFieldByXPath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]', '', 'The styles textarea exists and is empty.');

View file

@ -123,11 +123,13 @@ class CKEditorTest extends KernelTestBase {
// Change the allowed HTML tags; the "allowedContent" and "format_tags"
// settings for CKEditor should automatically be updated as well.
$format = $editor->getFilterFormat();
$format->filters('filter_html')->settings['allowed_html'] .= '<pre> <h1>';
$format->filters('filter_html')->settings['allowed_html'] .= '<pre class> <h1> <blockquote class="*"> <address class="foo bar-* *">';
$format->save();
$expected_config['allowedContent']['pre'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$expected_config['allowedContent']['pre'] = array('attributes' => 'class', 'styles' => FALSE, 'classes' => TRUE);
$expected_config['allowedContent']['h1'] = array('attributes' => FALSE, 'styles' => FALSE, 'classes' => FALSE);
$expected_config['allowedContent']['blockquote'] = array('attributes' => 'class', 'styles' => FALSE, 'classes' => TRUE);
$expected_config['allowedContent']['address'] = array('attributes' => 'class', 'styles' => FALSE, 'classes' => 'foo,bar-*');
$expected_config['format_tags'] = 'p;h1;h2;h3;h4;h5;h6;pre';
ksort($expected_config['allowedContent']);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.');

View file

@ -31,35 +31,35 @@ use Drupal\editor\Entity\Editor;
class Llama extends PluginBase implements CKEditorPluginInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getDependencies().
* {@inheritdoc}
*/
function getDependencies(Editor $editor) {
return array();
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getLibraries().
* {@inheritdoc}
*/
function getLibraries(Editor $editor) {
return array();
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
* {@inheritdoc}
*/
function isInternal() {
return FALSE;
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
function getFile() {
return drupal_get_path('module', 'ckeditor_test') . '/js/llama.js';
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
* {@inheritdoc}
*/
public function getConfig(Editor $editor) {
return array();

View file

@ -20,7 +20,7 @@ use Drupal\ckeditor\CKEditorPluginButtonsInterface;
class LlamaButton extends Llama implements CKEditorPluginButtonsInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
* {@inheritdoc}
*/
function getButtons() {
return array(
@ -31,7 +31,7 @@ class LlamaButton extends Llama implements CKEditorPluginButtonsInterface {
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
function getFile() {
return drupal_get_path('module', 'ckeditor_test') . '/js/llama_button.js';

View file

@ -21,7 +21,7 @@ use Drupal\editor\Entity\Editor;
class LlamaContextual extends Llama implements CKEditorPluginContextualInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginContextualInterface::isEnabled().
* {@inheritdoc}
*/
function isEnabled(Editor $editor) {
// Automatically enable this plugin if the Underline button is enabled.
@ -37,7 +37,7 @@ class LlamaContextual extends Llama implements CKEditorPluginContextualInterface
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
function getFile() {
return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual.js';

View file

@ -25,7 +25,7 @@ use Drupal\editor\Entity\Editor;
class LlamaContextualAndButton extends Llama implements CKEditorPluginContextualInterface, CKEditorPluginButtonsInterface, CKEditorPluginConfigurableInterface {
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginContextualInterface::isEnabled().
* {@inheritdoc}
*/
function isEnabled(Editor $editor) {
// Automatically enable this plugin if the Strike button is enabled.
@ -41,7 +41,7 @@ class LlamaContextualAndButton extends Llama implements CKEditorPluginContextual
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
* {@inheritdoc}
*/
function getButtons() {
return array(
@ -52,14 +52,14 @@ class LlamaContextualAndButton extends Llama implements CKEditorPluginContextual
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
* {@inheritdoc}
*/
function getFile() {
return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual_and_button.js';
}
/**
* Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
* {@inheritdoc}
*/
function settingsForm(array $form, FormStateInterface $form_state, Editor $editor) {
// Defaults.

View file

@ -110,3 +110,20 @@ function comment_schema() {
return $schema;
}
/**
* @addtogroup updates-8.0.0-rc
* @{
*/
/**
* Clear caches to fix Comment entity list builder and operations Views field.
*/
function comment_update_8001() {
// Empty update to cause a cache flush to rebuild comment entity handler
// information, so that comment operation links work.
}
/**
* @} End of "addtogroup updates-8.0.0-rc".
*/

View file

@ -66,7 +66,7 @@ class CommentForm extends ContentEntityForm {
}
/**
* Overrides Drupal\Core\Entity\EntityForm::form().
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
/** @var \Drupal\comment\CommentInterface $comment */
@ -227,7 +227,7 @@ class CommentForm extends ContentEntityForm {
}
/**
* Overrides Drupal\Core\Entity\EntityForm::actions().
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$element = parent::actions($form, $form_state);
@ -348,7 +348,7 @@ class CommentForm extends ContentEntityForm {
}
/**
* Overrides Drupal\Core\Entity\EntityForm::save().
* {@inheritdoc}
*/
public function save(array $form, FormStateInterface $form_state) {
$comment = $this->entity;

View file

@ -42,10 +42,10 @@ class CommentStorage extends SqlContentEntityStorage implements CommentStorageIn
* The database connection to be used.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache
* Cache backend instance to use.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
*/

View file

@ -113,11 +113,11 @@ class CommentController extends ControllerBase {
* @param \Drupal\comment\CommentInterface $comment
* A comment entity.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*
* @return \Symfony\Component\HttpFoundation\Response
* The comment listing set to the page on which the comment appears.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
*/
public function commentPermalink(Request $request, CommentInterface $comment) {
if ($entity = $comment->getCommentedEntity()) {
@ -172,10 +172,10 @@ class CommentController extends ControllerBase {
* @param \Drupal\Core\Entity\EntityInterface $node
* The node object identified by the legacy URL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse
* Redirects user to new url.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function redirectNode(EntityInterface $node) {
$fields = $this->commentManager->getFields('node');
@ -208,17 +208,17 @@ class CommentController extends ControllerBase {
* (optional) Some comments are replies to other comments. In those cases,
* $pid is the parent comment's comment ID. Defaults to NULL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return array|\Symfony\Component\HttpFoundation\RedirectResponse
* An associative array containing:
* - An array for rendering the entity or parent comment.
* - comment_entity: If the comment is a reply to the entity.
* - comment_parent: If the comment is a reply to another comment.
* - comment_form: The comment form as a renderable array.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function getReplyForm(Request $request, EntityInterface $entity, $field_name, $pid = NULL) {
$account = $this->currentUser();
$uri = $entity->urlInfo()->setAbsolute();
$build = array();
// The user is not just previewing a comment.
@ -270,9 +270,10 @@ class CommentController extends ControllerBase {
* (optional) Some comments are replies to other comments. In those cases,
* $pid is the parent comment's comment ID. Defaults to NULL.
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return \Drupal\Core\Access\AccessResultInterface
* An access result
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function replyFormAccess(EntityInterface $entity, $field_name, $pid = NULL) {
// Check if entity and field exists.
@ -312,10 +313,11 @@ class CommentController extends ControllerBase {
* @param \Symfony\Component\HttpFoundation\Request $request
* The request of the page.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
* @return \Symfony\Component\HttpFoundation\JsonResponse
* The JSON response.
*
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function renderNewCommentsNodeLinks(Request $request) {
if ($this->currentUser()->isAnonymous()) {

View file

@ -30,6 +30,7 @@ use Drupal\user\UserInterface;
* "storage" = "Drupal\comment\CommentStorage",
* "storage_schema" = "Drupal\comment\CommentStorageSchema",
* "access" = "Drupal\comment\CommentAccessControlHandler",
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
* "view_builder" = "Drupal\comment\CommentViewBuilder",
* "views_data" = "Drupal\comment\CommentViewsData",
* "form" = {

View file

@ -22,7 +22,7 @@ use Drupal\views\ViewExecutable;
class LastTimestamp extends Date {
/**
* Overrides Drupal\views\Plugin\views\field\FieldPluginBase::init().
* {@inheritdoc}
*/
public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
parent::init($view, $display, $options);

View file

@ -84,8 +84,6 @@ class Rss extends RssPluginBase {
return;
}
$description_build = [];
$comment->link = $comment->url('canonical', array('absolute' => TRUE));
$comment->rss_namespaces = array();
$comment->rss_elements = array(
@ -113,13 +111,11 @@ class Rss extends RssPluginBase {
$this->view->style_plugin->namespaces = array_merge($this->view->style_plugin->namespaces, $comment->rss_namespaces);
}
$item = new \stdClass();
if ($view_mode != 'title') {
// We render comment contents.
$description_build = $build;
$item->description = $build;
}
$item = new \stdClass();
$item->description = $description_build;
$item->title = $comment->label();
$item->link = $comment->link;
// Provide a reference so that the render call in

View file

@ -53,7 +53,7 @@ class Comment extends WizardPluginBase {
);
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::rowStyleOptions().
* {@inheritdoc}
*/
protected function rowStyleOptions() {
$options = array();
@ -63,7 +63,7 @@ class Comment extends WizardPluginBase {
}
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayOptions().
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();

View file

@ -63,7 +63,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::setupBundle().
* {@inheritdoc}
*/
function setupBundle() {
parent::setupBundle();
@ -80,14 +80,14 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array('post comments', 'administer comments', 'access comments'));
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::createEntity().
* {@inheritdoc}
*/
protected function createEntity($values, $langcode, $comment_type = 'comment_article') {
if ($comment_type == 'comment_article') {
@ -114,7 +114,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getNewEntityValues().
* {@inheritdoc}
*/
protected function getNewEntityValues($langcode) {
// Comment subject is not translatable hence we use a fixed value.

View file

@ -0,0 +1,38 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\Views\CommentOperationsTest.
*/
namespace Drupal\comment\Tests\Views;
/**
* Tests comment operations.
*
* @group comment
*/
class CommentOperationsTest extends CommentTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_comment_operations'];
/**
* Test the operations field plugin.
*/
public function testCommentOperations() {
$admin_account = $this->drupalCreateUser(['administer comments']);
$this->drupalLogin($admin_account);
$this->drupalGet('test-comment-operations');
$this->assertResponse(200);
$operation = $this->cssSelect('.views-field-operations li.edit a');
$this->assertEqual(count($operation), 1, 'Found edit operation for comment.');
$operation = $this->cssSelect('.views-field-operations li.delete a');
$this->assertEqual(count($operation), 1, 'Found delete operation for comment.');
}
}

View file

@ -0,0 +1,254 @@
langcode: en
status: true
dependencies:
module:
- comment
- user
id: test_comment_operations
label: test_comment_operations
module: views
description: ''
tag: ''
base_table: comment_field_data
base_field: cid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'administer comments'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' Previous'
next: 'Next '
first: '« First'
last: 'Last »'
quantity: 9
style:
type: table
options:
grouping: { }
row_class: ''
default_row_class: true
override: true
sticky: false
caption: ''
summary: ''
description: ''
columns:
subject: subject
operations: operations
info:
subject:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
operations:
sortable: false
default_sort_order: asc
align: ''
separator: ''
empty_column: false
responsive: ''
default: '-1'
empty_table: false
row:
type: fields
options:
inline: { }
separator: ''
hide_empty: false
default_field_elements: true
relationships: { }
fields:
subject:
id: subject
table: comment_field_data
field: subject
relationship: none
group_type: group
admin_label: ''
label: Subject
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: false
ellipsis: false
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: true
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: comment
entity_field: subject
plugin_id: field
operations:
id: operations
table: comment
field: operations
relationship: none
group_type: group
admin_label: ''
label: Operations
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
destination: true
entity_type: comment
plugin_id: entity_operations
filters: { }
sorts: { }
header: { }
footer: { }
empty: { }
arguments: { }
display_extenders: { }
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- user.permissions
tags: { }
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test-comment-operations
cache_metadata:
max-age: 0
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- user.permissions
tags: { }

View file

@ -150,6 +150,12 @@ class ConfigCRUDTest extends KernelTestBase {
$new_config->save();
$this->assertIdentical($new_config->get('value'), $expected_values['value']);
$this->assertIdentical($new_config->get('404'), $expected_values['404']);
// Test that getMultiple() does not return new config objects that were
// previously accessed with get()
$new_config = $config_factory->get('non_existing_key');
$this->assertTrue($new_config->isNew());
$this->assertEqual(0, count($config_factory->loadMultiple(['non_existing_key'])), 'loadMultiple() does not return new objects');
}
/**

View file

@ -2,7 +2,7 @@
/**
* @file
* Definition of Drupal\config\Tests\ConfigEntityListMultilingualTest.
* Contains \Drupal\config\Tests\ConfigEntityListMultilingualTest.
*/
namespace Drupal\config\Tests;

View file

@ -132,6 +132,8 @@ class ConfigOverrideTest extends KernelTestBase {
->save();
// Ensure override is preserved but all other data has been updated
// accordingly.
$config = \Drupal::config('config_test.new');
$this->assertFalse($config->isNew(), 'The configuration object config_test.new is not new');
$this->assertIdentical($config->get('key'), 'override');
$this->assertIdentical($config->get('new_key'), 'new_value');
$raw_data = $config->getRawData();

View file

@ -16,7 +16,7 @@ use Drupal\Core\Config\Config;
class ConfigTestStorage extends ConfigEntityStorage {
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityStorage::importCreate().
* {@inheritdoc}
*/
public function importCreate($name, Config $new_config, Config $old_config) {
// Set a global value we can check in test code.
@ -26,7 +26,7 @@ class ConfigTestStorage extends ConfigEntityStorage {
}
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityStorage::importUpdate().
* {@inheritdoc}
*/
public function importUpdate($name, Config $new_config, Config $old_config) {
// Set a global value we can check in test code.
@ -36,7 +36,7 @@ class ConfigTestStorage extends ConfigEntityStorage {
}
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityStorage::importDelete().
* {@inheritdoc}
*/
public function importDelete($name, Config $new_config, Config $old_config) {
// Set a global value we can check in test code.

View file

@ -80,7 +80,7 @@ class ConfigTest extends ConfigEntityBase implements ConfigTestInterface {
protected $protected_property;
/**
* Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::sort().
* {@inheritdoc}
*/
public static function sort(ConfigEntityInterface $a, ConfigEntityInterface $b) {
\Drupal::state()->set('config_entity_sort', TRUE);

View file

@ -101,7 +101,6 @@ function config_translation_entity_type_alter(array &$entity_types) {
*/
function config_translation_config_translation_info(&$info) {
$entity_manager = \Drupal::entityManager();
$route_provider = \Drupal::service('router.route_provider');
// If field UI is not enabled, the base routes of the type
// "entity.field_config.{$entity_type}_field_edit_form" are not defined.

View file

@ -45,6 +45,8 @@ class ConfigTranslationOverviewAccess implements AccessInterface {
*
* @param \Drupal\config_translation\ConfigMapperManagerInterface $config_mapper_manager
* The mapper plugin discovery service.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
*/
public function __construct(ConfigMapperManagerInterface $config_mapper_manager, LanguageManagerInterface $language_manager) {
$this->configMapperManager = $config_mapper_manager;

View file

@ -141,7 +141,7 @@ class ConfigTranslationDeleteForm extends ConfirmFormBase {
// Flush all persistent caches.
$this->moduleHandler->invokeAll('cache_flush');
foreach (Cache::getBins() as $service_id => $cache_backend) {
foreach (Cache::getBins() as $cache_backend) {
$cache_backend->deleteAll();
}

View file

@ -534,7 +534,7 @@ function content_translation_form_language_content_settings_form_alter(array &$f
}
/**
* Implements hook_preprocess_HOOK() for theme_language_content_settings_table().
* Implements hook_preprocess_HOOK() for language-content-settings-table.html.twig.
*/
function content_translation_preprocess_language_content_settings_table(&$variables) {
module_load_include('inc', 'content_translation', 'content_translation.admin');

View file

@ -27,7 +27,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase {
public static $modules = array('language', 'content_translation', 'entity_test');
/**
* Overrides \Drupal\simpletest\WebTestBase::setUp().
* {@inheritdoc}
*/
protected function setUp() {
// Use the entity_test_mul as this has multilingual property support.
@ -36,7 +36,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
* {@inheritdoc}
*/
protected function getTranslatorPermissions() {
return array_merge(parent::getTranslatorPermissions(), array('administer entity_test content', 'view test entity'));

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Contains \Drupal\content_translation\Tests\ContentTranslationEnableTest.
*/
namespace Drupal\content_translation\Tests;
use Drupal\simpletest\WebTestBase;
/**
* Test enabling content translation after other modules.
*
* @group content_translation
*/
class ContentTranslationEnableTest extends WebTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test', 'menu_link_content'];
/**
* Tests that entity schemas are up-to-date after enabling translation.
*/
public function testEnable() {
$this->drupalLogin($this->rootUser);
// Enable modules and make sure the related config entity type definitions
// are installed.
$edit = [
'modules[Multilingual][content_translation][enable]' => TRUE,
'modules[Multilingual][language][enable]' => TRUE,
];
$this->drupalPostForm('admin/modules', $edit, t('Install'));
// No pending updates should be available.
$this->drupalGet('admin/reports/status');
$requirement_value = $this->cssSelect("tr.system-status-report__entry th:contains('Entity/field definitions') + td");
$this->assertEqual(t('Up to date'), trim((string) $requirement_value[0]));
// Enable content translation on entity types that have will have a
// content_translation_uid.
$edit = [
'entity_types[menu_link_content]' => TRUE,
'settings[menu_link_content][menu_link_content][translatable]' => TRUE,
'entity_types[entity_test_mul]' => TRUE,
'settings[entity_test_mul][entity_test_mul][translatable]' => TRUE,
];
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
// No pending updates should be available.
$this->drupalGet('admin/reports/status');
$requirement_value = $this->cssSelect("tr.system-status-report__entry th:contains('Entity/field definitions') + td");
$this->assertEqual(t('Up to date'), trim((string) $requirement_value[0]));
}
}

View file

@ -49,7 +49,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
}
/**
* Overrides \Drupal\content_translation\Tests\ContentTranslationTestBase::getEditorPermissions().
* {@inheritdoc}
*/
protected function getEditorPermissions() {
return array('administer entity_test content');

View file

@ -37,7 +37,7 @@ class MigrateDblogConfigsTest extends MigrateDrupal6TestBase {
*/
public function testBookSettings() {
$config = $this->config('dblog.settings');
$this->assertIdentical(1000, $config->get('row_limit'));
$this->assertIdentical(10000, $config->get('row_limit'));
$this->assertConfigSchema(\Drupal::service('config.typed'), 'dblog.settings', $config->get());
}

View file

@ -12,14 +12,12 @@ use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
/**
* Upgrade variables to dblog.settings.yml.
*
* @group dblog
* @group migrate_drupal_7
*/
class MigrateDblogConfigsTest extends MigrateDrupal7TestBase {
/**
* Modules to enable.
*
* @var array
* {@inheritdoc}
*/
public static $modules = ['dblog'];
@ -37,7 +35,7 @@ class MigrateDblogConfigsTest extends MigrateDrupal7TestBase {
*/
public function testDblogSettings() {
$config = $this->config('dblog.settings');
$this->assertIdentical(1000, $config->get('row_limit'));
$this->assertIdentical(10000, $config->get('row_limit'));
}
}

View file

@ -71,13 +71,13 @@ class ViewsIntegrationTest extends ViewKernelTestBase {
);
// Setup a watchdog entry with two tokens.
$entries[] = array(
'message' => '@token1 !token2',
'message' => '@token1 @token2',
// Setup a link with a tag which is filtered by
// \Drupal\Component\Utility\Xss::filterAdmin() in order to make sure
// that strings which are not marked as safe get filtered.
'variables' => array(
'@token1' => $this->randomMachineName(),
'!token2' => $this->randomMachineName(),
'@token2' => $this->randomMachineName(),
'link' => '<a href="' . \Drupal::url('<front>') . '"><object>Link</object></a>',
),
);

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageacheSubscriber.
* Contains \Drupal\dynamic_page_cache\EventSubscriber\DynamicPageCacheSubscriber.
*/
namespace Drupal\dynamic_page_cache\EventSubscriber;

View file

@ -11,9 +11,6 @@ drupal.editor:
version: VERSION
js:
js/editor.js: {}
css:
component:
css/editor.css: {}
dependencies:
- core/jquery
- core/drupal

View file

@ -53,13 +53,6 @@ class EditorLinkDialog extends FormBase {
'#maxlength' => 2048,
);
$form['attributes']['target'] = array(
'#title' => $this->t('Open in new window'),
'#type' => 'checkbox',
'#default_value' => !empty($input['target']),
'#return_value' => '_blank',
);
$form['actions'] = array(
'#type' => 'actions',
);

View file

@ -17,7 +17,7 @@ process:
field_name: field_name
type:
-
plugin: static_map
plugin: field_type
source:
- type
- widget_type
@ -37,12 +37,6 @@ process:
optionwidgets_select: list_float
optionwidgets_buttons: list_float
optionwidgets_onoff: boolean
text:
optionwidgets_select: list_string
optionwidgets_buttons: list_string
optionwidgets_onoff: boolean
text_textfield: text
text_textarea: text_long
email:
email_textfield: email
filefield:
@ -118,6 +112,9 @@ process:
phone_textfield: telephone
int_phone:
phone_textfield: telephone
-
plugin: skip_on_empty
method: row
cardinality:
plugin: static_map
bypass: true

View file

@ -1,30 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\field\Plugin\migrate\process\d6\FieldIdGenerator.
*/
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Generate the file name for field config entities.
*
* @MigrateProcessPlugin(
* id = "field_id_generator"
* )
*/
class FieldIdGenerator extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
return $value[0] . "." . $value[1];
}
}

View file

@ -44,6 +44,7 @@ class FieldSettings extends ProcessPluginBase {
public function getSettings($field_type, $global_settings) {
$max_length = isset($global_settings['max_length']) ? $global_settings['max_length'] : '';
$max_length = empty($max_length) ? 255 : $max_length;
$allowed_values = [];
if (isset($global_settings['allowed_values'])) {
$list = explode("\n", $global_settings['allowed_values']);
$list = array_map('trim', $list);
@ -62,9 +63,6 @@ class FieldSettings extends ProcessPluginBase {
$allowed_values = $list;
}
}
else {
$allowed_values = '';
}
$settings = array(
'text' => array(

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\field\Plugin\migrate\process\d6\FieldType.
*/
namespace Drupal\field\Plugin\migrate\process\d6;
use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\migrate\process\StaticMap;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateProcessPlugin(
* id = "field_type"
* )
*/
class FieldType extends StaticMap implements ContainerFactoryPluginInterface {
/**
* The cckfield plugin manager.
*
* @var \Drupal\Component\Plugin\PluginManagerInterface
*/
protected $cckPluginManager;
/**
* Constructs a FieldType plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Component\Plugin\PluginManagerInterface $cck_plugin_manager
* The cckfield plugin manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, PluginManagerInterface $cck_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->cckPluginManager = $cck_plugin_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('plugin.manager.migrate.cckfield')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list ($field_type, $widget_type) = $value;
try {
return $this->cckPluginManager->createInstance($field_type)
->getFieldType($row);
}
catch (PluginNotFoundException $e) {
return parent::transform($value, $migrate_executable, $row, $destination_property);
}
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\field\ProxyClass\FieldUninstallValidator.
* Contains \Drupal\field\ProxyClass\FieldUninstallValidator.
*/
/**

View file

@ -140,9 +140,26 @@ class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase {
}
/**
* Tests if the translated entity is displayed in an entity reference field.
* Tests if the entity is displayed in an entity reference field.
*/
public function testTranslatedEntityReferenceDisplay() {
public function testEntityReferenceDisplay() {
// Create a translated referrer entity.
$this->referrerEntity = $this->createReferrerEntity();
$this->assertEntityReferenceDisplay();
// Disable translation for referrer content type.
$this->drupalLogin($this->rootUser);
$this->drupalPostForm('admin/config/regional/content-language', ['settings[node][referrer][translatable]' => FALSE], t('Save configuration'));
// Create a referrer entity without translation.
$this->referrerEntity = $this->createReferrerEntity(FALSE);
$this->assertEntityReferenceDisplay();
}
/**
* Assert entity reference display.
*/
protected function assertEntityReferenceDisplay() {
$url = $this->referrerEntity->urlInfo();
$translation_url = $this->referrerEntity->urlInfo('canonical', ['language' => ConfigurableLanguage::load($this->translateToLangcode)]);
@ -169,7 +186,6 @@ class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase {
protected function createContent() {
$this->referencedEntityWithTranslation = $this->createReferencedEntityWithTranslation();
$this->referencedEntityWithoutTranslation = $this->createNotTranslatedReferencedEntity();
$this->referrerEntity = $this->createReferrerEntity();
}
/**
@ -283,7 +299,7 @@ class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase {
/**
* Create the referrer entity.
*/
protected function createReferrerEntity() {
protected function createReferrerEntity($translatable = TRUE) {
/** @var \Drupal\node\Entity\Node $node */
$node = entity_create($this->testEntityTypeName, array(
'title' => $this->randomMachineName(),
@ -298,8 +314,9 @@ class EntityReferenceFieldTranslatedReferenceViewTest extends WebTestBase {
),
'langcode' => $this->baseLangcode,
));
$node->save();
$node->addTranslation($this->translateToLangcode, $node->toArray());
if ($translatable) {
$node->addTranslation($this->translateToLangcode, $node->toArray());
}
$node->save();
return $node;

View file

@ -27,10 +27,11 @@ class MigrateFieldInstanceTest extends MigrateDrupal6TestBase {
$entity = Node::create(['type' => 'story']);
// Test a text field.
/** @var \Drupal\field\FieldConfigInterface $field */
$field = FieldConfig::load('node.story.field_test');
$this->assertIdentical('Text Field', $field->label());
$expected = array('max_length' => 255);
$this->assertIdentical($expected, $field->getSettings());
// field_test is a text_long field, which have no settings.
$this->assertIdentical([], $field->getSettings());
$this->assertIdentical('text for default value', $entity->field_test->value);
// Test a number field.

View file

@ -33,9 +33,9 @@ class MigrateFieldTest extends MigrateDrupal6TestBase {
// Text field.
/** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
$field_storage = FieldStorageConfig::load('node.field_test');
$expected = array('max_length' => 255);
$this->assertIdentical("text", $field_storage->getType(), t('Field type is @fieldtype. It should be text.', array('@fieldtype' => $field_storage->getType())));
$this->assertIdentical($expected, $field_storage->getSettings(), "Field type text settings are correct");
$this->assertIdentical('text_long', $field_storage->getType());
// text_long fields do not have settings.
$this->assertIdentical([], $field_storage->getSettings());
// Integer field.
$field_storage = FieldStorageConfig::load('node.field_test_two');

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\Tests\field\Unit\Plugin\migrate\process\d6\FieldSettingsTest.
*/
namespace Drupal\Tests\field\Unit\Plugin\migrate\process\d6;
use Drupal\field\Plugin\migrate\process\d6\FieldSettings;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\Tests\UnitTestCase;
/**
* @coversDefaultClass \Drupal\field\Plugin\migrate\process\d6\FieldSettings
* @group field
*/
class FieldSettingsTest extends UnitTestCase {
/**
* @covers ::getSettings
*
* @dataProvider getSettingsProvider
*/
public function testGetSettings($field_type, $field_settings, $allowed_values) {
$migration = $this->getMock(MigrationInterface::class);
$plugin = new FieldSettings([], 'd6_field_settings', [], $migration);
$executable = $this->getMock(MigrateExecutableInterface::class);
$row = $this->getMockBuilder(Row::class)
->disableOriginalConstructor()
->getMock();
$result = $plugin->transform([$field_type, $field_settings], $executable, $row, 'foo');
$this->assertSame($allowed_values, $result['allowed_values']);
}
/**
* Provides field settings for testGetSettings().
*/
public function getSettingsProvider() {
return array(
array(
'list_integer',
array('allowed_values' => "1|One\n2|Two\n3"),
array(
'1' => 'One',
'2' => 'Two',
'3' => '3',
),
),
array(
'list_string',
array('allowed_values' => NULL),
array(),
),
array(
'list_float',
array('allowed_values' => ""),
array(),
),
array(
'boolean',
array(),
array(),
),
);
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\Tests\field\Unit\Plugin\migrate\source\d6\FieldInstancePerViewModeTest.
* Contains \Drupal\Tests\field\Unit\Plugin\migrate\source\d6\FieldInstancePerFormDisplayTest.
*/
namespace Drupal\Tests\field\Unit\Plugin\migrate\source\d6;

View file

@ -16,19 +16,31 @@
line-height: 1em;
}
.field-ui-overview .field-plugin-summary {
float: left;
float: left; /* LTR */
font-size: .9em;
}
[dir="rtl"] .field-ui-overview .field-plugin-summary {
float: right;
}
.field-ui-overview .field-plugin-summary-cell .warning {
display: block;
float: left;
float: left; /* LTR */
margin-right: .5em;
}
.field-ui-overview .field-plugin-settings-edit-wrapper {
[dir="rtl"] .field-ui-overview .field-plugin-summary-cell .warning {
float: right;
}
.field-ui-overview .field-plugin-settings-edit-wrapper {
float: right; /* LTR */
}
[dir="rtl"] .field-ui-overview .field-plugin-settings-edit-wrapper {
float: left;
}
.field-ui-overview .field-plugin-settings-edit {
float: right;
float: right; /* LTR */
}
[dir="rtl"] .field-ui-overview .field-plugin-settings-edit {
float: left;
}
.field-ui-overview .field-plugin-settings-editing td {
vertical-align: top;

View file

@ -53,8 +53,17 @@ function field_ui_help($route_name, RouteMatchInterface $route_match) {
function field_ui_theme() {
return array(
'field_ui_table' => array(
'render element' => 'elements',
'function' => 'theme_field_ui_table',
'variables' => array(
'header' => NULL,
'rows' => NULL,
'footer' => NULL,
'attributes' => array(),
'caption' => NULL,
'colgroups' => array(),
'sticky' => FALSE,
'responsive' => TRUE,
'empty' => '',
),
),
);
}
@ -209,84 +218,17 @@ function field_ui_entity_form_mode_delete(EntityFormModeInterface $form_mode) {
}
/**
* Returns HTML for Field UI overview tables.
* Prepares variables for field UI overview table templates.
*
* @param $variables
* Default template: field-ui-table.html.twig.
*
* @param array $variables
* An associative array containing:
* - elements: An associative array containing a Form API structure to be
* rendered as a table.
*
* @ingroup themeable
*/
function theme_field_ui_table($variables) {
$elements = $variables['elements'];
$table = array('#type' => 'table');
// Add table headers and attributes.
foreach (array('#header', '#attributes') as $key) {
if (isset($elements[$key])) {
$table[$key] = $elements[$key];
}
}
// Determine the colspan to use for region rows, by checking the number of
// columns in the headers.
$columns_count = 0;
foreach ($table['#header'] as $header) {
$columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1);
}
// Render rows, region by region.
foreach ($elements['#regions'] as $region_name => $region) {
$region_name_class = Html::getClass($region_name);
// Add region rows.
if (isset($region['title']) && empty($region['invisible'])) {
$table['#rows'][] = array(
'class' => array('region-title', 'region-' . $region_name_class . '-title'),
'no_striping' => TRUE,
'data' => array(
array('data' => $region['title'], 'colspan' => $columns_count),
),
);
}
if (isset($region['message'])) {
$class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated');
$table['#rows'][] = array(
'class' => array('region-message', 'region-' . $region_name_class . '-message', $class),
'no_striping' => TRUE,
'data' => array(
array('data' => $region['message'], 'colspan' => $columns_count),
),
);
}
// Add form rows, in the order determined at pre-render time.
foreach ($region['rows_order'] as $name) {
$element = $elements[$name];
$row = array('data' => array());
if (isset($element['#attributes'])) {
$row += $element['#attributes'];
}
// Render children as table cells.
foreach (Element::children($element) as $cell_key) {
$child = &$element[$cell_key];
// Do not render a cell for children of #type 'value'.
if (!(isset($child['#type']) && $child['#type'] == 'value')) {
$cell = array('data' => drupal_render($child));
if (isset($child['#cell_attributes'])) {
$cell += $child['#cell_attributes'];
}
$row['data'][] = $cell;
}
}
$table['#rows'][] = $row;
}
}
return drupal_render($table);
function template_preprocess_field_ui_table(&$variables) {
template_preprocess_table($variables);
}
/**

View file

@ -7,23 +7,237 @@
namespace Drupal\field_ui\Element;
use Drupal\Core\Render\Element\RenderElement;
use Drupal\Component\Utility\Html;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\Table;
/**
* Provides a field_ui table element.
*
* @RenderElement("field_ui_table")
*/
class FieldUiTable extends RenderElement {
class FieldUiTable extends Table {
/**
* {@inheritdoc}
*/
public function getInfo() {
return array(
'#theme' => 'field_ui_table',
'#regions' => array('' => array()),
);
$info = parent::getInfo();
$info['#regions'] = ['' => []];
$info['#theme'] = 'field_ui_table';
// Prepend FieldUiTable's prerender callbacks.
array_unshift($info['#pre_render'], [$this, 'tablePreRender'], [$this, 'preRenderRegionRows']);
return $info;
}
/**
* Performs pre-render tasks on field_ui_table elements.
*
* @param array $elements
* A structured array containing two sub-levels of elements. Properties
* used:
* - #tabledrag: The value is a list of $options arrays that are passed to
* drupal_attach_tabledrag(). The HTML ID of the table is added to each
* $options array.
*
* @return array
* The $element with prepared variables ready for field-ui-table.html.twig.
*
* @see drupal_render()
* @see \Drupal\Core\Render\Element\Table::preRenderTable()
*/
public static function tablePreRender($elements) {
$js_settings = array();
// For each region, build the tree structure from the weight and parenting
// data contained in the flat form structure, to determine row order and
// indentation.
$regions = $elements['#regions'];
$tree = ['' => ['name' => '', 'children' => []]];
$trees = array_fill_keys(array_keys($regions), $tree);
$parents = [];
$children = Element::children($elements);
$list = array_combine($children, $children);
// Iterate on rows until we can build a known tree path for all of them.
while ($list) {
foreach ($list as $name) {
$row = &$elements[$name];
$parent = $row['parent_wrapper']['parent']['#value'];
// Proceed if parent is known.
if (empty($parent) || isset($parents[$parent])) {
// Grab parent, and remove the row from the next iteration.
$parents[$name] = $parent ? array_merge($parents[$parent], [$parent]) : [];
unset($list[$name]);
// Determine the region for the row.
$region_name = call_user_func($row['#region_callback'], $row);
// Add the element in the tree.
$target = &$trees[$region_name][''];
foreach ($parents[$name] as $key) {
$target = &$target['children'][$key];
}
$target['children'][$name] = ['name' => $name, 'weight' => $row['weight']['#value']];
// Add tabledrag indentation to the first row cell.
if ($depth = count($parents[$name])) {
$children = Element::children($row);
$cell = current($children);
$row[$cell]['#prefix'] = [
'#theme' => 'indentation',
'#size' => $depth,
'#suffix' => isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '',
];
}
// Add row id and associate JS settings.
$id = Html::getClass($name);
$row['#attributes']['id'] = $id;
if (isset($row['#js_settings'])) {
$row['#js_settings'] += [
'rowHandler' => $row['#row_type'],
'name' => $name,
'region' => $region_name,
];
$js_settings[$id] = $row['#js_settings'];
}
}
}
}
// Determine rendering order from the tree structure.
foreach ($regions as $region_name => $region) {
$elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], [static::class, 'reduceOrder']);
}
$elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;
// If the custom #tabledrag is set and there is a HTML ID, add the table's
// HTML ID to the options and attach the behavior.
// @see \Drupal\Core\Render\Element\Table::preRenderTable()
if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
foreach ($elements['#tabledrag'] as $options) {
$options['table_id'] = $elements['#attributes']['id'];
drupal_attach_tabledrag($elements, $options);
}
}
return $elements;
}
/**
* Performs pre-render to move #regions to rows.
*
* @param array $elements
* A structured array containing two sub-levels of elements. Properties
* used:
* - #tabledrag: The value is a list of $options arrays that are passed to
* drupal_attach_tabledrag(). The HTML ID of the table is added to each
* $options array.
*
* @return array
* The $element with prepared variables ready for field-ui-table.html.twig.
*/
public static function preRenderRegionRows($elements) {
// Determine the colspan to use for region rows, by checking the number of
// columns in the headers.
$columns_count = 0;
foreach ($elements['#header'] as $header) {
$columns_count += (is_array($header) && isset($header['colspan']) ? $header['colspan'] : 1);
}
$rows = [];
foreach (Element::children($elements) as $key) {
$rows[$key] = $elements[$key];
unset($elements[$key]);
}
// Render rows, region by region.
foreach ($elements['#regions'] as $region_name => $region) {
$region_name_class = Html::getClass($region_name);
// Add region rows.
if (isset($region['title']) && empty($region['invisible'])) {
$elements['#rows'][] = [
'class' => [
'region-title',
'region-' . $region_name_class . '-title'
],
'no_striping' => TRUE,
'data' => [
['data' => $region['title'], 'colspan' => $columns_count],
],
];
}
if (isset($region['message'])) {
$class = (empty($region['rows_order']) ? 'region-empty' : 'region-populated');
$elements['#rows'][] = [
'class' => [
'region-message',
'region-' . $region_name_class . '-message', $class,
],
'no_striping' => TRUE,
'data' => [
['data' => $region['message'], 'colspan' => $columns_count],
],
];
}
// Add form rows, in the order determined at pre-render time.
foreach ($region['rows_order'] as $name) {
$element = $rows[$name];
$row = ['data' => []];
if (isset($element['#attributes'])) {
$row += $element['#attributes'];
}
// Render children as table cells.
foreach (Element::children($element) as $cell_key) {
$child = $element[$cell_key];
// Do not render a cell for children of #type 'value'.
if (!(isset($child['#type']) && $child['#type'] == 'value')) {
$cell = ['data' => $child];
if (isset($child['#cell_attributes'])) {
$cell += $child['#cell_attributes'];
}
$row['data'][] = $cell;
}
}
$elements['#rows'][] = $row;
}
}
return $elements;
}
/**
* Determines the rendering order of an array representing a tree.
*
* Callback for array_reduce() within ::tablePreRender().
*
* @param mixed $array
* Holds the return value of the previous iteration; in the case of the
* first iteration it instead holds the value of the initial array.
* @param mixed $a
* Holds the value of the current iteration.
*
* @return array
* Array where rendering order has been determined.
*/
public static function reduceOrder($array, $a) {
$array = !$array ? [] : $array;
if ($a['name']) {
$array[] = $a['name'];
}
if (!empty($a['children'])) {
uasort($a['children'], ['Drupal\Component\Utility\SortArray', 'sortByWeightElement']);
$array = array_merge($array, array_reduce($a['children'], [static::class, 'reduceOrder']));
}
return $array;
}
}

View file

@ -19,6 +19,7 @@ use Drupal\Core\Field\PluginSettingsInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\field_ui\Element\FieldUiTable;
use Drupal\field_ui\FieldUI;
/**
@ -159,17 +160,12 @@ abstract class EntityDisplayFormBase extends EntityForm {
$table = array(
'#type' => 'field_ui_table',
'#pre_render' => array(array($this, 'tablePreRender')),
'#tree' => TRUE,
'#header' => $this->getTableHeader(),
'#regions' => $this->getRegions(),
'#attributes' => array(
'class' => array('field-ui-overview'),
'id' => 'field-display-overview',
),
// Add Ajax wrapper.
'#prefix' => '<div id="field-display-overview-wrapper">',
'#suffix' => '</div>',
'#tabledrag' => array(
array(
'action' => 'order',
@ -691,85 +687,11 @@ abstract class EntityDisplayFormBase extends EntityForm {
*
* @see drupal_render()
* @see \Drupal\Core\Render\Element\Table::preRenderTable()
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
public function tablePreRender($elements) {
$js_settings = array();
// For each region, build the tree structure from the weight and parenting
// data contained in the flat form structure, to determine row order and
// indentation.
$regions = $elements['#regions'];
$tree = array('' => array('name' => '', 'children' => array()));
$trees = array_fill_keys(array_keys($regions), $tree);
$parents = array();
$children = Element::children($elements);
$list = array_combine($children, $children);
// Iterate on rows until we can build a known tree path for all of them.
while ($list) {
foreach ($list as $name) {
$row = &$elements[$name];
$parent = $row['parent_wrapper']['parent']['#value'];
// Proceed if parent is known.
if (empty($parent) || isset($parents[$parent])) {
// Grab parent, and remove the row from the next iteration.
$parents[$name] = $parent ? array_merge($parents[$parent], array($parent)) : array();
unset($list[$name]);
// Determine the region for the row.
$region_name = call_user_func($row['#region_callback'], $row);
// Add the element in the tree.
$target = &$trees[$region_name][''];
foreach ($parents[$name] as $key) {
$target = &$target['children'][$key];
}
$target['children'][$name] = array('name' => $name, 'weight' => $row['weight']['#value']);
// Add tabledrag indentation to the first row cell.
if ($depth = count($parents[$name])) {
$children = Element::children($row);
$cell = current($children);
$indentation = array(
'#theme' => 'indentation',
'#size' => $depth,
);
$row[$cell]['#prefix'] = drupal_render($indentation) . (isset($row[$cell]['#prefix']) ? $row[$cell]['#prefix'] : '');
}
// Add row id and associate JS settings.
$id = Html::getClass($name);
$row['#attributes']['id'] = $id;
if (isset($row['#js_settings'])) {
$row['#js_settings'] += array(
'rowHandler' => $row['#row_type'],
'name' => $name,
'region' => $region_name,
);
$js_settings[$id] = $row['#js_settings'];
}
}
}
}
// Determine rendering order from the tree structure.
foreach ($regions as $region_name => $region) {
$elements['#regions'][$region_name]['rows_order'] = array_reduce($trees[$region_name], array($this, 'reduceOrder'));
}
$elements['#attached']['drupalSettings']['fieldUIRowsData'] = $js_settings;
// If the custom #tabledrag is set and there is a HTML ID, add the table's
// HTML ID to the options and attach the behavior.
// @see \Drupal\Core\Render\Element\Table::preRenderTable()
if (!empty($elements['#tabledrag']) && isset($elements['#attributes']['id'])) {
foreach ($elements['#tabledrag'] as $options) {
$options['table_id'] = $elements['#attributes']['id'];
drupal_attach_tabledrag($elements, $options);
}
}
return $elements;
return FieldUiTable::tablePreRender($elements);
}
/**
@ -777,17 +699,11 @@ abstract class EntityDisplayFormBase extends EntityForm {
*
* Callback for array_reduce() within
* \Drupal\field_ui\Form\EntityDisplayFormBase::tablePreRender().
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
*/
public function reduceOrder($array, $a) {
$array = !isset($array) ? array() : $array;
if ($a['name']) {
$array[] = $a['name'];
}
if (!empty($a['children'])) {
uasort($a['children'], array('Drupal\Component\Utility\SortArray', 'sortByWeightElement'));
$array = array_merge($array, array_reduce($a['children'], array($this, 'reduceOrder')));
}
return $array;
return FieldUiTable::reduceOrder($array, $a);
}
/**

View file

@ -0,0 +1,47 @@
{#
/**
* @file
* Default theme implementation to display a Field UI table.
*
* Available variables:
* - attributes: HTML attributes to apply to the <table> tag.
* - caption: A localized string for the <caption> tag.
* - colgroups: Column groups. Each group contains the following properties:
* - attributes: HTML attributes to apply to the <col> tag.
* Note: Drupal currently supports only one table header row, see
* https://www.drupal.org/node/893530 and
* http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
* - header: Table header cells. Each cell contains the following properties:
* - tag: The HTML tag name to use; either TH or TD.
* - attributes: HTML attributes to apply to the tag.
* - content: A localized string for the title of the column.
* - field: Field name (required for column sorting).
* - sort: Default sort order for this column ("asc" or "desc").
* - sticky: A flag indicating whether to use a "sticky" table header.
* - rows: Table rows. Each row contains the following properties:
* - attributes: HTML attributes to apply to the <tr> tag.
* - data: Table cells.
* - no_striping: A flag indicating that the row should receive no
* 'even / odd' styling. Defaults to FALSE.
* - cells: Table cells of the row. Each cell contains the following keys:
* - tag: The HTML tag name to use; either TH or TD.
* - attributes: Any HTML attributes, such as "colspan", to apply to the
* table cell.
* - content: The string to display in the table cell.
* - active_table_sort: A boolean indicating whether the cell is the active
table sort.
* - footer: Table footer rows, in the same format as the rows variable.
* - empty: The message to display in an extra row if table does not have
* any rows.
* - no_striping: A boolean indicating that the row should receive no striping.
* - header_columns: The number of columns in the header.
*
* @see template_preprocess_field_ui_table()
*
* @ingroup themeable
*/
#}
{# Add Ajax wrapper. #}
<div id="field-display-overview-wrapper">
{% include 'table.html.twig' %}
</div>

View file

@ -5,8 +5,6 @@ drupal.file:
css:
theme:
css/file.admin.css: {}
component:
css/file.theme.css: {}
dependencies:
- core/jquery
- core/jquery.once

View file

@ -45,7 +45,7 @@ class DatabaseFileUsageBackend extends FileUsageBase {
}
/**
* Implements Drupal\file\FileUsage\FileUsageInterface::add().
* {@inheritdoc}
*/
public function add(FileInterface $file, $module, $type, $id, $count = 1) {
$this->connection->merge($this->tableName)
@ -63,7 +63,7 @@ class DatabaseFileUsageBackend extends FileUsageBase {
}
/**
* Implements Drupal\file\FileUsage\FileUsageInterface::delete().
* {@inheritdoc}
*/
public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $count = 1) {
// Delete rows that have a exact or less value to prevent empty rows.
@ -98,7 +98,7 @@ class DatabaseFileUsageBackend extends FileUsageBase {
}
/**
* Implements Drupal\file\FileUsage\FileUsageInterface::listUsage().
* {@inheritdoc}
*/
public function listUsage(FileInterface $file) {
$result = $this->connection->select($this->tableName, 'f')

View file

@ -15,7 +15,7 @@ use Drupal\file\FileInterface;
abstract class FileUsageBase implements FileUsageInterface {
/**
* Implements Drupal\file\FileUsage\FileUsageInterface::add().
* {@inheritdoc}
*/
public function add(FileInterface $file, $module, $type, $id, $count = 1) {
// Make sure that a used file is permanent.
@ -26,7 +26,7 @@ abstract class FileUsageBase implements FileUsageInterface {
}
/**
* Implements Drupal\file\FileUsage\FileUsageInterface::delete().
* {@inheritdoc}
*/
public function delete(FileInterface $file, $module, $type = NULL, $id = NULL, $count = 1) {
// If there are no more remaining usages of this file, mark it as temporary,

View file

@ -8,10 +8,13 @@
namespace Drupal\file\Plugin\migrate\cckfield;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @PluginID("filefield")
* @MigrateCckField(
* id = "filefield"
* )
*/
class FileField extends CckFieldPluginBase {
@ -49,4 +52,11 @@ class FileField extends CckFieldPluginBase {
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFile.
* Contains \Drupal\file\Plugin\migrate\destination\EntityFile.
*/
namespace Drupal\file\Plugin\migrate\destination;

View file

@ -26,7 +26,7 @@ class File extends WizardPluginBase {
protected $createdColumn = 'created';
/**
* Overrides Drupal\views\Plugin\views\wizard\WizardPluginBase::defaultDisplayOptions().
* {@inheritdoc}
*/
protected function defaultDisplayOptions() {
$display_options = parent::defaultDisplayOptions();

View file

@ -17,8 +17,9 @@ class FilterController {
/**
* Displays a page with long filter tips.
*
* @param \Drupal\filter\FilterFormatInterface|null $format
* A filter format, or NULL to show tips for all formats. Defaults to NULL.
* @param \Drupal\filter\FilterFormatInterface|null $filter_format
* (optional) A filter format, or NULL to show tips for all formats.
* Defaults to NULL.
*
* @return array
* A renderable array.

View file

@ -138,7 +138,11 @@ class ProcessedText extends RenderElement {
/**
* Wraps a logger channel.
*
* @param string $channel
* The name of the channel.
*
* @return \Psr\Log\LoggerInterface
* The logger for this channel.
*/
protected static function logger($channel) {
return \Drupal::logger($channel);

View file

@ -138,12 +138,10 @@ class FilterProcessResult extends BubbleableMetadata {
*/
public function createPlaceholder($callback, array $args) {
// Generate placeholder markup.
// @see \Drupal\Core\Render\Renderer::createPlaceholder()
$attributes = new Attribute();
$attributes['callback'] = $callback;
$attributes['arguments'] = UrlHelper::buildQuery($args);
$attributes['token'] = hash('sha1', serialize([$callback, $args]));
$placeholder_markup = Html::normalize('<drupal-filter-placeholder' . $attributes . '></drupal-filter-placeholder>');
// @see \Drupal\Core\Render\PlaceholderGenerator::createPlaceholder()
$arguments = UrlHelper::buildQuery($args);
$token = hash('crc32b', serialize([$callback, $args]));
$placeholder_markup = '<drupal-filter-placeholder callback="' . Html::escape($callback) . '" arguments="' . Html::escape($arguments) . '" token="' . Html::escape($token) . '"></drupal-filter-placeholder>';
// Add the placeholder attachment.
$this->addAttachments([

View file

@ -55,15 +55,17 @@ class FilterCaption extends FilterBase {
// Given the updated node and caption: re-render it with a caption, but
// bubble up the value of the class attribute of the captioned element,
// this allows it to collaborate with e.g. the filter_align filter.
$tag = $node->tagName;
$classes = $node->getAttribute('class');
$node->removeAttribute('class');
$node = ($node->parentNode->tagName === 'a') ? $node->parentNode : $node;
$filter_caption = array(
'#theme' => 'filter_caption',
// We pass the unsanitized string because this is a text format
// filter, and after filtering, we always assume the output is safe.
// @see \Drupal\filter\Element\ProcessedText::preRenderText()
'#node' => FilteredMarkup::create($node->C14N()),
'#tag' => $node->tagName,
'#tag' => $tag,
'#caption' => $caption,
'#classes' => $classes,
);
@ -78,7 +80,7 @@ class FilterCaption extends FilterBase {
// Import the updated node from the new DOMDocument into the original
// one, importing also the child nodes of the updated node.
$updated_node = $dom->importNode($updated_node, TRUE);
// Finally, replace the original image node with the new image node!
// Finally, replace the original node with the new node.
$node->parentNode->replaceChild($updated_node, $node);
}

View file

@ -275,16 +275,27 @@ class FilterHtml extends FilterBase {
foreach ($node->attributes as $name => $attribute) {
// Put back any trailing * on wildcard attribute name.
$name = str_replace($star_protector, '*', $name);
if ($attribute->value === '') {
// Put back any trailing * on wildcard attribute value and parse out
// the allowed attribute values.
$allowed_attribute_values = preg_split('/\s+/', str_replace($star_protector, '*', $attribute->value), -1, PREG_SPLIT_NO_EMPTY);
// Sanitize the attribute value: it lists the allowed attribute values
// but one allowed attribute value that some may be tempted to use
// is specifically nonsensical: the asterisk. A prefix is required for
// allowed attribute values with a wildcard. A wildcard by itself
// would mean whitelisting all possible attribute values. But in that
// case, one would not specify an attribute value at all.
$allowed_attribute_values = array_filter($allowed_attribute_values, function ($value) use ($star_protector) { return $value !== '*'; });
if (empty($allowed_attribute_values)) {
// If the value is the empty string all values are allowed.
$restrictions['allowed'][$tag][$name] = TRUE;
}
else {
// A non-empty attribute value is assigned, mark each of the
// specified attribute values as allowed.
foreach (preg_split('/\s+/', $attribute->value, -1, PREG_SPLIT_NO_EMPTY) as $value) {
// Put back any trailing * on wildcard attribute value.
$value = str_replace($star_protector, '*', $value);
foreach ($allowed_attribute_values as $value) {
$restrictions['allowed'][$tag][$name][$value] = TRUE;
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\FilterSettings.
* Contains \Drupal\filter\Plugin\migrate\process\FilterSettings.
*/
namespace Drupal\filter\Plugin\migrate\process;

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\filter\ProxyClass\FilterUninstallValidator.
* Contains \Drupal\filter\ProxyClass\FilterUninstallValidator.
*/
/**

View file

@ -207,6 +207,41 @@ class FilterAPITest extends EntityUnitTestBase {
array(FilterInterface::TYPE_HTML_RESTRICTOR),
'FilterFormatInterface::getFilterTypes() works as expected for the very_restricted_html format.'
);
// Test on nonsensical_restricted_html, where the allowed attribute values
// contain asterisks, which do not have any meaning, but which we also
// cannot prevent because configuration can be modified outside of forms.
$nonsensical_restricted_html = \Drupal\filter\Entity\FilterFormat::create(array(
'format' => 'nonsensical_restricted_html',
'name' => 'Nonsensical Restricted HTML',
'filters' => array(
'filter_html' => array(
'status' => 1,
'settings' => array(
'allowed_html' => '<a> <b class> <c class="*"> <d class="foo bar-* *">',
),
),
)
));
$nonsensical_restricted_html->save();
$this->assertIdentical(
$nonsensical_restricted_html->getHtmlRestrictions(),
array(
'allowed' => array(
'a' => FALSE,
'b' => array('class' => TRUE),
'c' => array('class' => TRUE),
'd' => array('class' => array('foo' => TRUE, 'bar-*' => TRUE)),
'*' => array('style' => FALSE, 'on*' => FALSE, 'lang' => TRUE, 'dir' => array('ltr' => TRUE, 'rtl' => TRUE)),
),
),
'FilterFormatInterface::getHtmlRestrictions() works as expected for the nonsensical_restricted_html format.'
);
$this->assertIdentical(
$very_restricted_html_format->getFilterTypes(),
array(FilterInterface::TYPE_HTML_RESTRICTOR),
'FilterFormatInterface::getFilterTypes() works as expected for the very_restricted_html format.'
);
}
/**

View file

@ -184,6 +184,13 @@ class FilterUnitTest extends KernelTestBase {
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAttachments());
// Ensure the caption filter works for linked images.
$input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" /></a>';
$expected = '<figure role="group"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAttachments());
// So far we've tested that the caption filter works correctly. But we also
// want to make sure that it works well in tandem with the "Limit allowed
// HTML tags" filter, which it is typically used with.
@ -301,6 +308,13 @@ class FilterUnitTest extends KernelTestBase {
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAttachments());
// Ensure both filters together work for linked images.
$input = '<a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" data-caption="Loquacious llama!" data-align="center" /></a>';
$expected = '<figure role="group" class="align-center"><a href="http://example.com/llamas/are/awesome/but/kittens/are/cool/too"><img src="llama.jpg" /></a>' . "\n" . '<figcaption>Loquacious llama!</figcaption></figure>';
$output = $test($input);
$this->assertIdentical($expected, $output->getProcessedText());
$this->assertIdentical($attached_library, $output->getAttachments());
}
/**
@ -1135,7 +1149,8 @@ body {color:red}
* (optional) Message to display if failed. Defaults to an empty string.
* @param $group
* (optional) The group this message belongs to. Defaults to 'Other'.
* @return
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
function assertNormalized($haystack, $needle, $message = '', $group = 'Other') {
@ -1159,7 +1174,8 @@ body {color:red}
* (optional) Message to display if failed. Defaults to an empty string.
* @param $group
* (optional) The group this message belongs to. Defaults to 'Other'.
* @return
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
function assertNoNormalized($haystack, $needle, $message = '', $group = 'Other') {

View file

@ -9,6 +9,7 @@ namespace Drupal\forum\Plugin\Block;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
@ -87,14 +88,14 @@ abstract class ForumBlockBase extends BlockBase {
* {@inheritdoc}
*/
public function getCacheContexts() {
return ['user.node_grants:view'];
return Cache::mergeContexts(parent::getCacheContexts(), ['user.node_grants:view']);
}
/**
* {@inheritdoc}
*/
public function getCacheTags() {
return ['node_list'];
return Cache::mergeTags(parent::getCacheTags(), ['node_list']);
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains Drupal\forum\ProxyClass\ForumUninstallValidator.
* Contains \Drupal\forum\ProxyClass\ForumUninstallValidator.
*/
/**

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\forum\Tests\d7\MigrateForumSettingsTest.
* Contains \Drupal\forum\Tests\Migrate\d7\MigrateForumSettingsTest.
*/
namespace Drupal\forum\Tests\Migrate\d7;

View file

@ -24,14 +24,14 @@ class JsonEncoder extends SymfonyJsonEncoder {
protected $format = 'hal_json';
/**
* Overrides \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsEncoding()
* {@inheritdoc}
*/
public function supportsEncoding($format) {
return $format == $this->format;
}
/**
* Overrides \Symfony\Component\Serializer\Encoder\JsonEncoder::supportsDecoding()
* {@inheritdoc}
*/
public function supportsDecoding($format) {
return $format == $this->format;

View file

@ -61,7 +61,7 @@ class ContentEntityNormalizer extends NormalizerBase {
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
* {@inheritdoc}
*/
public function normalize($entity, $format = NULL, array $context = array()) {
$context += array(

View file

@ -52,7 +52,7 @@ class EntityReferenceItemNormalizer extends FieldItemNormalizer implements UuidR
}
/**
* Implements \Symfony\Component\Serializer\Normalizer\NormalizerInterface::normalize()
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = array()) {
/** @var $field_item \Drupal\Core\Field\FieldItemInterface */
@ -97,7 +97,7 @@ class EntityReferenceItemNormalizer extends FieldItemNormalizer implements UuidR
}
/**
* Overrides \Drupal\hal\Normalizer\FieldItemNormalizer::constructValue().
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
$field_item = $context['target_instance'];
@ -111,7 +111,7 @@ class EntityReferenceItemNormalizer extends FieldItemNormalizer implements UuidR
}
/**
* Implements \Drupal\serialization\EntityResolver\UuidReferenceInterface::getUuid().
* {@inheritdoc}
*/
public function getUuid($data) {
if (isset($data['uuid'])) {

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