Update to Drupal 8.0.0-rc3. For more information, see https://www.drupal.org/node/2608078
This commit is contained in:
parent
6419a031d7
commit
4afb23bbd3
762 changed files with 20080 additions and 6368 deletions
|
@ -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".
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\aggregator\FeedHtmlRouteProvider.
|
||||
* Contains \Drupal\aggregator\FeedHtmlRouteProvider.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
46
core/modules/aggregator/src/Tests/ItemWithoutFeedTest.php
Normal file
46
core/modules/aggregator/src/Tests/ItemWithoutFeedTest.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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') {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.' ,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\book\ProxyClass\BookUninstallValidator.
|
||||
* Contains \Drupal\book\ProxyClass\BookUninstallValidator.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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'),
|
||||
),
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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.');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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".
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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" = {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.');
|
||||
}
|
||||
|
||||
}
|
|
@ -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: { }
|
|
@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\config\Tests\ConfigEntityListMultilingualTest.
|
||||
* Contains \Drupal\config\Tests\ConfigEntityListMultilingualTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\config\Tests;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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]));
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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'));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>',
|
||||
),
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,9 +11,6 @@ drupal.editor:
|
|||
version: VERSION
|
||||
js:
|
||||
js/editor.js: {}
|
||||
css:
|
||||
component:
|
||||
css/editor.css: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/drupal
|
||||
|
|
|
@ -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',
|
||||
);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\field\ProxyClass\FieldUninstallValidator.
|
||||
* Contains \Drupal\field\ProxyClass\FieldUninstallValidator.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
47
core/modules/field_ui/templates/field-ui-table.html.twig
Normal file
47
core/modules/field_ui/templates/field-ui-table.html.twig
Normal 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>
|
|
@ -5,8 +5,6 @@ drupal.file:
|
|||
css:
|
||||
theme:
|
||||
css/file.admin.css: {}
|
||||
component:
|
||||
css/file.theme.css: {}
|
||||
dependencies:
|
||||
- core/jquery
|
||||
- core/jquery.once
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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([
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\filter\ProxyClass\FilterUninstallValidator.
|
||||
* Contains \Drupal\filter\ProxyClass\FilterUninstallValidator.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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']);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\forum\ProxyClass\ForumUninstallValidator.
|
||||
* Contains \Drupal\forum\ProxyClass\ForumUninstallValidator.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\forum\Tests\d7\MigrateForumSettingsTest.
|
||||
* Contains \Drupal\forum\Tests\Migrate\d7\MigrateForumSettingsTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\forum\Tests\Migrate\d7;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
Reference in a new issue