Update to Drupal 8.0-dev-2015-11-17. Commits through da81cd220, Tue Nov 17 15:53:49 2015 +0000, Issue #2617224 by Wim Leers: Move around/fix some documentation.
This commit is contained in:
parent
4afb23bbd3
commit
7784f4c23d
929 changed files with 19798 additions and 5304 deletions
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\aggregator\Tests\Migrate\MigrateAggregatorStubTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\aggregator\Tests\Migrate;
|
||||
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||
use Drupal\migrate_drupal\Tests\StubTestTrait;
|
||||
|
||||
/**
|
||||
* Test stub creation for aggregator feeds and items.
|
||||
*
|
||||
* @group aggregator
|
||||
*/
|
||||
class MigrateAggregatorStubTest extends MigrateDrupalTestBase {
|
||||
|
||||
use StubTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['aggregator'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('aggregator_feed');
|
||||
$this->installEntitySchema('aggregator_item');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of aggregator feed stubs.
|
||||
*/
|
||||
public function testFeedStub() {
|
||||
$this->performStubTest('aggregator_feed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of aggregator feed items.
|
||||
*/
|
||||
public function testItemStub() {
|
||||
try {
|
||||
// We expect an exception, because there's no feed to reference.
|
||||
$this->performStubTest('aggregator_item');
|
||||
$this->fail('Expected exception has not been thrown.');
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertIdentical($e->getMessage(),
|
||||
'Stubbing failed, unable to generate value for field fid');
|
||||
}
|
||||
|
||||
// The stub should pass when there's a feed to point to.
|
||||
$this->createStub('aggregator_feed');
|
||||
$this->performStubTest('aggregator_item');
|
||||
}
|
||||
|
||||
}
|
|
@ -89,7 +89,10 @@ function block_page_top(array &$page_top) {
|
|||
*/
|
||||
function block_themes_installed($theme_list) {
|
||||
foreach ($theme_list as $theme) {
|
||||
block_theme_initialize($theme);
|
||||
// Don't initialize themes that are not displayed in the UI.
|
||||
if (\Drupal::service('theme_handler')->hasUi($theme)) {
|
||||
block_theme_initialize($theme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use Drupal\Component\Utility\Html;
|
|||
use Drupal\Core\Controller\ControllerBase;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Controller routines for admin block routes.
|
||||
|
@ -53,6 +54,10 @@ class BlockController extends ControllerBase {
|
|||
* A #type 'page' render array containing the block region demo.
|
||||
*/
|
||||
public function demo($theme) {
|
||||
if (!$this->themeHandler->hasUi($theme)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
$page = [
|
||||
'#title' => Html::escape($this->themeHandler->getName($theme)),
|
||||
'#type' => 'page',
|
||||
|
|
|
@ -8,13 +8,42 @@
|
|||
namespace Drupal\block\Controller;
|
||||
|
||||
use Drupal\Core\Entity\Controller\EntityListController;
|
||||
use Drupal\Core\Extension\ThemeHandlerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Defines a controller to list blocks.
|
||||
*/
|
||||
class BlockListController extends EntityListController {
|
||||
|
||||
/**
|
||||
* The theme handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ThemeHandlerInterface
|
||||
*/
|
||||
protected $themeHandler;
|
||||
|
||||
/**
|
||||
* Constructs the BlockListController.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler
|
||||
* The theme handler.
|
||||
*/
|
||||
public function __construct(ThemeHandlerInterface $theme_handler) {
|
||||
$this->themeHandler = $theme_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container) {
|
||||
return new static(
|
||||
$container->get('theme_handler')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the block administration page.
|
||||
*
|
||||
|
@ -28,6 +57,10 @@ class BlockListController extends EntityListController {
|
|||
*/
|
||||
public function listing($theme = NULL, Request $request = NULL) {
|
||||
$theme = $theme ?: $this->config('system.theme')->get('default');
|
||||
if (!$this->themeHandler->hasUi($theme)) {
|
||||
throw new NotFoundHttpException();
|
||||
}
|
||||
|
||||
return $this->entityManager()->getListBuilder('block')->render($theme, $request);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class ThemeLocalTask extends DeriverBase implements ContainerDeriverInterface {
|
|||
$default_theme = $this->themeHandler->getDefault();
|
||||
|
||||
foreach ($this->themeHandler->listInfo() as $theme_name => $theme) {
|
||||
if ($theme->status) {
|
||||
if ($this->themeHandler->hasUi($theme_name)) {
|
||||
$this->derivatives[$theme_name] = $base_plugin_definition;
|
||||
$this->derivatives[$theme_name]['title'] = $theme->info['name'];
|
||||
$this->derivatives[$theme_name]['route_parameters'] = array('theme' => $theme_name);
|
||||
|
|
|
@ -56,7 +56,9 @@ class BlockHiddenRegionTest extends WebTestBase {
|
|||
|
||||
// Install "block_test_theme" and set it as the default theme.
|
||||
$theme = 'block_test_theme';
|
||||
\Drupal::service('theme_handler')->install(array($theme));
|
||||
// We need to install a non-hidden theme so that there is more than one
|
||||
// local task.
|
||||
\Drupal::service('theme_handler')->install(array($theme, 'stark'));
|
||||
$this->config('system.theme')
|
||||
->set('default', $theme)
|
||||
->save();
|
||||
|
|
|
@ -197,9 +197,9 @@ class BlockTest extends BlockTestBase {
|
|||
*/
|
||||
public function testBlockThemeSelector() {
|
||||
// Install all themes.
|
||||
\Drupal::service('theme_handler')->install(array('bartik', 'seven'));
|
||||
\Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
|
||||
$theme_settings = $this->config('system.theme');
|
||||
foreach (array('bartik', 'classy', 'seven') as $theme) {
|
||||
foreach (['bartik', 'seven', 'stark'] as $theme) {
|
||||
$this->drupalGet('admin/structure/block/list/' . $theme);
|
||||
$this->assertTitle(t('Block layout') . ' | Drupal');
|
||||
// Select the 'Powered by Drupal' block to be placed.
|
||||
|
|
|
@ -90,6 +90,10 @@ class BlockUiTest extends WebTestBase {
|
|||
\Drupal::service('theme_handler')->install(array('test_theme'));
|
||||
$this->drupalGet('admin/structure/block/demo/test_theme');
|
||||
$this->assertEscaped('<strong>Test theme</strong>');
|
||||
|
||||
\Drupal::service('theme_handler')->install(['stable']);
|
||||
$this->drupalGet('admin/structure/block/demo/stable');
|
||||
$this->assertResponse(404, 'Hidden themes that are not the default theme are not supported by the block demo screen');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,6 +140,28 @@ class BlockUiTest extends WebTestBase {
|
|||
$this->drupalGet('admin/structure/block');
|
||||
$element = $this->xpath('//tr[contains(@class, :class)]', [':class' => 'region-title-header']);
|
||||
$this->assertTrue(!empty($element));
|
||||
|
||||
// Ensure hidden themes do not appear in the UI. Enable another non base
|
||||
// theme and place the local tasks block.
|
||||
$this->assertTrue(\Drupal::service('theme_handler')->themeExists('classy'), 'The classy base theme is enabled');
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']);
|
||||
\Drupal::service('theme_installer')->install(['stable', 'stark']);
|
||||
$this->drupalGet('admin/structure/block');
|
||||
$theme_handler = \Drupal::service('theme_handler');
|
||||
$this->assertLink($theme_handler->getName('classy'));
|
||||
$this->assertLink($theme_handler->getName('stark'));
|
||||
$this->assertNoLink($theme_handler->getName('stable'));
|
||||
|
||||
$this->drupalGet('admin/structure/block/list/stable');
|
||||
$this->assertResponse(404, 'Placing blocks through UI is not possible for a hidden base theme.');
|
||||
|
||||
\Drupal::configFactory()->getEditable('system.theme')->set('admin', 'stable')->save();
|
||||
\Drupal::service('router.builder')->rebuildIfNeeded();
|
||||
$this->drupalPlaceBlock('local_tasks_block', ['region' => 'header', 'theme' => 'stable']);
|
||||
$this->drupalGet('admin/structure/block');
|
||||
$this->assertLink($theme_handler->getName('stable'));
|
||||
$this->drupalGet('admin/structure/block/list/stable');
|
||||
$this->assertResponse(200, 'Placing blocks through UI is possible for a hidden base theme that is the admin theme.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -65,6 +65,14 @@ class NewDefaultThemeBlocksTest extends WebTestBase {
|
|||
unset($new_blocks[str_replace($default_theme . '_', $new_theme . '_', $default_block_name)]);
|
||||
}
|
||||
$this->assertTrue(empty($new_blocks), 'The new theme has exactly the same blocks as the previous default theme.');
|
||||
|
||||
// Install a hidden base theme and ensure blocks are not copied.
|
||||
$base_theme = 'test_basetheme';
|
||||
\Drupal::service('theme_handler')->install([$base_theme]);
|
||||
$new_blocks = $this->container->get('entity.query')->get('block')
|
||||
->condition('theme', $base_theme)
|
||||
->execute();
|
||||
$this->assertTrue(empty($new_blocks), 'Installing a hidden base theme does not copy blocks from the default theme.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -27,7 +27,11 @@ class BlockLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
|
||||
$themes = array();
|
||||
$themes['test_a'] = (object) array(
|
||||
'status' => 0,
|
||||
'status' => 1,
|
||||
'info' => array(
|
||||
'name' => 'test_a',
|
||||
'hidden' => TRUE,
|
||||
),
|
||||
);
|
||||
$themes['test_b'] = (object) array(
|
||||
'status' => 1,
|
||||
|
@ -45,6 +49,13 @@ class BlockLocalTasksTest extends LocalTaskIntegrationTestBase {
|
|||
$theme_handler->expects($this->any())
|
||||
->method('listInfo')
|
||||
->will($this->returnValue($themes));
|
||||
$theme_handler->expects($this->any())
|
||||
->method('hasUi')
|
||||
->willReturnMap([
|
||||
['test_a', FALSE],
|
||||
['test_b', TRUE],
|
||||
['test_c', TRUE],
|
||||
]);
|
||||
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('config.factory', $config_factory);
|
||||
|
|
|
@ -44,6 +44,7 @@ entity.block_content.canonical:
|
|||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.update'
|
||||
block_content: \d+
|
||||
|
||||
entity.block_content.edit_form:
|
||||
path: '/block/{block_content}'
|
||||
|
@ -53,6 +54,7 @@ entity.block_content.edit_form:
|
|||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.update'
|
||||
block_content: \d+
|
||||
|
||||
entity.block_content.delete_form:
|
||||
path: '/block/{block_content}/delete'
|
||||
|
@ -63,6 +65,7 @@ entity.block_content.delete_form:
|
|||
_admin_route: TRUE
|
||||
requirements:
|
||||
_entity_access: 'block_content.delete'
|
||||
block_content: \d+
|
||||
|
||||
block_content.type_add:
|
||||
path: '/admin/structure/block/block-content/types/add'
|
||||
|
|
|
@ -185,17 +185,15 @@ class BlockContentTypeTest extends BlockContentTestBase {
|
|||
->getStorage('block_content');
|
||||
|
||||
// Install all themes.
|
||||
\Drupal::service('theme_handler')->install(array('bartik', 'seven'));
|
||||
$themes = array('bartik', 'seven', 'classy');
|
||||
\Drupal::service('theme_handler')->install(['bartik', 'seven', 'stark']);
|
||||
$theme_settings = $this->config('system.theme');
|
||||
foreach ($themes as $default_theme) {
|
||||
foreach (['bartik', 'seven', 'stark'] as $default_theme) {
|
||||
// Change the default theme.
|
||||
$theme_settings->set('default', $default_theme)->save();
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
|
||||
// For each installed theme, go to its block page and test the redirects.
|
||||
$themes = array('bartik', 'classy', 'seven');
|
||||
foreach ($themes as $theme) {
|
||||
foreach (['bartik', 'seven', 'stark'] as $theme) {
|
||||
// Test that adding a block from the 'place blocks' form sends you to the
|
||||
// block configure form.
|
||||
$path = $theme == $default_theme ? 'admin/structure/block' : "admin/structure/block/list/$theme";
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\block_content\Tests\Migrate\MigrateBlockContentStubTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\block_content\Tests\Migrate;
|
||||
|
||||
use Drupal\block_content\Entity\BlockContentType;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||
use Drupal\migrate_drupal\Tests\StubTestTrait;
|
||||
|
||||
/**
|
||||
* Test stub creation for block_content entities.
|
||||
*
|
||||
* @group block_content
|
||||
*/
|
||||
class MigrateBlockContentStubTest extends MigrateDrupalTestBase {
|
||||
|
||||
use StubTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['block_content'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('block_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of block content stubs with no block_content_type available.
|
||||
*/
|
||||
public function testStubFailure() {
|
||||
$message = 'Expected MigrateException thrown when no bundles exist.';
|
||||
try {
|
||||
$this->createStub('block_content');
|
||||
$this->fail($message);
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->pass($message);
|
||||
$this->assertEqual('Stubbing failed, no bundles available for entity type: block_content', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of block content stubs when there is a block_content_type.
|
||||
*/
|
||||
public function testStubSuccess() {
|
||||
BlockContentType::create([
|
||||
'id' => 'test_block_content_type',
|
||||
'label' => 'Test block content type',
|
||||
])->save();
|
||||
$this->performStubTest('block_content');
|
||||
}
|
||||
|
||||
}
|
|
@ -29,6 +29,7 @@ book.export:
|
|||
requirements:
|
||||
_permission: 'access printer-friendly version'
|
||||
_entity_access: 'node.view'
|
||||
node: \d+
|
||||
|
||||
entity.node.book_outline_form:
|
||||
path: '/node/{node}/outline'
|
||||
|
@ -38,6 +39,7 @@ entity.node.book_outline_form:
|
|||
requirements:
|
||||
_permission: 'administer book outlines'
|
||||
_entity_access: 'node.view'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
|
||||
|
@ -62,3 +64,4 @@ entity.node.book_remove_form:
|
|||
_permission: 'administer book outlines'
|
||||
_entity_access: 'node.view'
|
||||
_access_book_removable: 'TRUE'
|
||||
node: \d+
|
||||
|
|
|
@ -9,6 +9,27 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
function parseAttributes(element) {
|
||||
var parsedAttributes = {};
|
||||
|
||||
var domElement = element.$;
|
||||
var attribute = null;
|
||||
var attributeName;
|
||||
for (var attrIndex = 0; attrIndex < domElement.attributes.length; attrIndex++) {
|
||||
attribute = domElement.attributes.item(attrIndex);
|
||||
attributeName = attribute.nodeName.toLowerCase();
|
||||
// Don't consider data-cke-saved- attributes; they're just there to work
|
||||
// around browser quirks.
|
||||
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
|
||||
continue;
|
||||
}
|
||||
// Store the value for this attribute, unless there's a data-cke-saved-
|
||||
// alternative for it, which will contain the quirk-free, original value.
|
||||
parsedAttributes[attributeName] = element.data('cke-saved-' + attributeName) || attribute.nodeValue;
|
||||
}
|
||||
return parsedAttributes;
|
||||
}
|
||||
|
||||
CKEDITOR.plugins.add('drupallink', {
|
||||
init: function (editor) {
|
||||
// Add the commands for link and unlink.
|
||||
|
@ -16,8 +37,7 @@
|
|||
allowedContent: {
|
||||
a: {
|
||||
attributes: {
|
||||
'!href': true,
|
||||
'target': true
|
||||
'!href': true
|
||||
},
|
||||
classes: {}
|
||||
}
|
||||
|
@ -34,35 +54,16 @@
|
|||
var drupalImageUtils = CKEDITOR.plugins.drupalimage;
|
||||
var focusedImageWidget = drupalImageUtils && drupalImageUtils.getFocusedWidget(editor);
|
||||
var linkElement = getSelectedLink(editor);
|
||||
var linkDOMElement = null;
|
||||
|
||||
// Set existing values based on selected element.
|
||||
var existingValues = {};
|
||||
if (linkElement && linkElement.$) {
|
||||
linkDOMElement = linkElement.$;
|
||||
|
||||
// Populate an array with the link's current attributes.
|
||||
var attribute = null;
|
||||
var attributeName;
|
||||
for (var attrIndex = 0; attrIndex < linkDOMElement.attributes.length; attrIndex++) {
|
||||
attribute = linkDOMElement.attributes.item(attrIndex);
|
||||
attributeName = attribute.nodeName.toLowerCase();
|
||||
// Don't consider data-cke-saved- attributes; they're just there
|
||||
// to work around browser quirks.
|
||||
if (attributeName.substring(0, 15) === 'data-cke-saved-') {
|
||||
continue;
|
||||
}
|
||||
// Store the value for this attribute, unless there's a
|
||||
// data-cke-saved- alternative for it, which will contain the
|
||||
// quirk-free, original value.
|
||||
existingValues[attributeName] = linkElement.data('cke-saved-' + attributeName) || attribute.nodeValue;
|
||||
}
|
||||
existingValues = parseAttributes(linkElement);
|
||||
}
|
||||
// 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;
|
||||
existingValues = CKEDITOR.tools.clone(focusedImageWidget.data.link);
|
||||
}
|
||||
|
||||
// Prepare a save callback to be used upon saving the dialog.
|
||||
|
@ -70,14 +71,7 @@
|
|||
// 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]
|
||||
}
|
||||
});
|
||||
focusedImageWidget.setData('link', CKEDITOR.tools.extend(returnValues.attributes, focusedImageWidget.data.link));
|
||||
editor.fire('saveSnapshot');
|
||||
return;
|
||||
}
|
||||
|
@ -97,11 +91,6 @@
|
|||
range.selectNodeContents(text);
|
||||
}
|
||||
|
||||
// Ignore a disabled target attribute.
|
||||
if (returnValues.attributes.target === 0) {
|
||||
delete returnValues.attributes.target;
|
||||
}
|
||||
|
||||
// Create the new link by applying a style to the new text.
|
||||
var style = new CKEDITOR.style({element: 'a', attributes: returnValues.attributes});
|
||||
style.type = CKEDITOR.STYLE_INLINE;
|
||||
|
@ -150,8 +139,7 @@
|
|||
allowedContent: {
|
||||
a: {
|
||||
attributes: {
|
||||
'!href': true,
|
||||
'target': true
|
||||
'!href': true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -280,8 +268,6 @@
|
|||
return null;
|
||||
}
|
||||
|
||||
var urlRegex = /^((?:http|https):\/\/)?(.*)$/;
|
||||
|
||||
/**
|
||||
* The image2 plugin is currently tightly coupled to the link plugin: it
|
||||
* calls CKEDITOR.plugins.link.parseLinkAttributes().
|
||||
|
@ -296,28 +282,20 @@
|
|||
*/
|
||||
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]
|
||||
}
|
||||
};
|
||||
return parseAttributes(element);
|
||||
},
|
||||
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'];
|
||||
for (var attributeName in data) {
|
||||
if (data.hasOwnProperty(attributeName)) {
|
||||
set[attributeName] = data[attributeName];
|
||||
}
|
||||
}
|
||||
|
||||
// CKEditor tracks the *actual* saved href in a data-cke-saved-* attribute
|
||||
// to work around browser quirks. We need to update it.
|
||||
set['data-cke-saved-href'] = set.href;
|
||||
|
||||
// Remove all attributes which are not currently set.
|
||||
var removed = {};
|
||||
for (var s in set) {
|
||||
|
|
|
@ -23,6 +23,7 @@ entity.comment.edit_form:
|
|||
_entity_form: 'comment.default'
|
||||
requirements:
|
||||
_entity_access: 'comment.update'
|
||||
comment: \d+
|
||||
|
||||
comment.approve:
|
||||
path: '/comment/{comment}/approve'
|
||||
|
@ -33,6 +34,7 @@ comment.approve:
|
|||
requirements:
|
||||
_entity_access: 'comment.approve'
|
||||
_csrf_token: 'TRUE'
|
||||
comment: \d+
|
||||
|
||||
entity.comment.canonical:
|
||||
path: '/comment/{comment}'
|
||||
|
@ -41,6 +43,7 @@ entity.comment.canonical:
|
|||
_controller: '\Drupal\comment\Controller\CommentController::commentPermalink'
|
||||
requirements:
|
||||
_entity_access: 'comment.view'
|
||||
comment: \d+
|
||||
|
||||
entity.comment.delete_form:
|
||||
path: '/comment/{comment}/delete'
|
||||
|
@ -49,6 +52,7 @@ entity.comment.delete_form:
|
|||
_entity_form: 'comment.delete'
|
||||
requirements:
|
||||
_entity_access: 'comment.delete'
|
||||
comment: \d+
|
||||
|
||||
comment.reply:
|
||||
path: '/comment/reply/{entity_type}/{entity}/{field_name}/{pid}'
|
||||
|
@ -77,6 +81,7 @@ comment.node_redirect:
|
|||
requirements:
|
||||
_entity_access: 'node.view'
|
||||
_module_dependencies: 'node'
|
||||
node: \d+
|
||||
|
||||
entity.comment_type.collection:
|
||||
path: '/admin/structure/comment'
|
||||
|
|
|
@ -39,6 +39,34 @@ class CommentSelection extends DefaultSelection {
|
|||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$comment = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable comment, it needs to published.
|
||||
/** @var \Drupal\comment\CommentInterface $comment */
|
||||
$comment->setPublished(TRUE);
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
if (!$this->currentUser->hasPermission('administer comments')) {
|
||||
$entities = array_filter($entities, function ($comment) {
|
||||
/** @var \Drupal\comment\CommentInterface $comment */
|
||||
return $comment->isPublished();
|
||||
});
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Drupal\comment\Plugin\migrate\destination;
|
|||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
|
@ -62,13 +63,15 @@ class EntityComment extends EntityContentBase {
|
|||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager service.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state storage object.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $entity_query
|
||||
* The query object that can query the given entity type.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StateInterface $state, QueryFactory $entity_query) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, StateInterface $state, QueryFactory $entity_query) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager);
|
||||
$this->state = $state;
|
||||
$this->entityQuery = $entity_query;
|
||||
}
|
||||
|
@ -86,6 +89,7 @@ class EntityComment extends EntityContentBase {
|
|||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('plugin.manager.field.field_type'),
|
||||
$container->get('state'),
|
||||
$container->get('entity.query')
|
||||
);
|
||||
|
@ -110,32 +114,9 @@ class EntityComment extends EntityContentBase {
|
|||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
parent::processStubRow($row);
|
||||
$stub_commented_entity_type = $row->getDestinationProperty('entity_type');
|
||||
|
||||
// While parent::getEntity() fills the bundle property for stub entities
|
||||
// if it's still empty, here we must also make sure entity_id/entity_type
|
||||
// are filled (so $comment->getCommentedEntity() always returns a value).
|
||||
if (empty($this->stubCommentedEntityIds[$stub_commented_entity_type])) {
|
||||
// Fill stub entity id. Any id will do, as long as it exists.
|
||||
$entity_type = $this->entityManager->getDefinition($stub_commented_entity_type);
|
||||
$id_key = $entity_type->getKey('id');
|
||||
$result = $this->entityQuery
|
||||
->get($stub_commented_entity_type)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
if ($result) {
|
||||
$this->stubCommentedEntityIds[$stub_commented_entity_type] = array_pop($result);
|
||||
$row->setSourceProperty($id_key, $this->stubCommentedEntityIds[$stub_commented_entity_type]);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException(t('Could not find parent entity to use for comment %id', ['%id' => implode(':', $row->getSourceIdValues())]), MigrationInterface::MESSAGE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
$row->setDestinationProperty('entity_id', $this->stubCommentedEntityIds[$stub_commented_entity_type]);
|
||||
$row->setDestinationProperty('entity_type', $stub_commented_entity_type);
|
||||
$row->setDestinationProperty('created', REQUEST_TIME);
|
||||
$row->setDestinationProperty('changed', REQUEST_TIME);
|
||||
// Neither uid nor name is required in itself, but it is required to set one
|
||||
// of them.
|
||||
$row->setDestinationProperty('name', 'anonymous_stub');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class CommentValidationTest extends EntityUnitTestBase {
|
|||
*/
|
||||
public function testValidation() {
|
||||
// Add a user.
|
||||
$user = User::create(array('name' => 'test'));
|
||||
$user = User::create(array('name' => 'test', 'status' => TRUE));
|
||||
$user->save();
|
||||
|
||||
// Add comment type.
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\comment\Tests\Migrate\MigrateCommentStubTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\comment\Tests\Migrate;
|
||||
|
||||
use Drupal\comment\Entity\CommentType;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||
use Drupal\migrate_drupal\Tests\StubTestTrait;
|
||||
use Drupal\node\Entity\NodeType;
|
||||
|
||||
/**
|
||||
* Test stub creation for comment entities.
|
||||
*
|
||||
* @group comment
|
||||
*/
|
||||
class MigrateCommentStubTest extends MigrateDrupalTestBase {
|
||||
|
||||
use StubTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['comment', 'node'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('node');
|
||||
// Make sure uid 0 is created (default uid for comments is 0).
|
||||
$storage = \Drupal::entityManager()->getStorage('user');
|
||||
// Insert a row for the anonymous user.
|
||||
$storage
|
||||
->create(array(
|
||||
'uid' => 0,
|
||||
'status' => 0,
|
||||
'name' => '',
|
||||
))
|
||||
->save();
|
||||
// Need at least one node type and comment type present.
|
||||
NodeType::create([
|
||||
'type' => 'testnodetype',
|
||||
'name' => 'Test node type',
|
||||
])->save();
|
||||
CommentType::create([
|
||||
'id' => 'testcommenttype',
|
||||
'label' => 'Test comment type',
|
||||
'target_entity_type_id' => 'node',
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of comment stubs.
|
||||
*/
|
||||
public function testStub() {
|
||||
try {
|
||||
// We expect an exception, because there's no node to reference.
|
||||
$this->performStubTest('comment');
|
||||
$this->fail('Expected exception has not been thrown.');
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->assertIdentical($e->getMessage(),
|
||||
'Stubbing failed, unable to generate value for field entity_id');
|
||||
}
|
||||
|
||||
// The stub should pass when there's a node to point to.
|
||||
$this->createStub('node');
|
||||
$this->performStubTest('comment');
|
||||
}
|
||||
|
||||
}
|
|
@ -22,13 +22,7 @@ class MigrateCommentTest extends MigrateDrupal6TestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'comment',
|
||||
// Directly testing that a stub comment's entity_id is populated upon
|
||||
// importing is not straightforward, but RDF module serves as an implicit
|
||||
// test - its hook_comment_storage_load() references a stubbed comment.
|
||||
'rdf',
|
||||
];
|
||||
public static $modules = ['comment'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
|
|
@ -67,6 +67,7 @@ class ConfigImportInstallProfileTest extends WebTestBase {
|
|||
$core['module']['testing_config_import'] = 0;
|
||||
unset($core['module']['syslog']);
|
||||
unset($core['theme']['stark']);
|
||||
$core['theme']['stable'] = 0;
|
||||
$core['theme']['classy'] = 0;
|
||||
$sync->write('core.extension', $core);
|
||||
$sync->deleteAll('syslog.');
|
||||
|
|
|
@ -92,12 +92,12 @@ class ConfigInstallProfileOverrideTest extends WebTestBase {
|
|||
}
|
||||
|
||||
// Install the config_test module and ensure that the override from the
|
||||
// install profile is not used. Optional configuration can not override
|
||||
// install profile is used. Optional configuration can override
|
||||
// configuration in a modules config/install directory.
|
||||
$this->container->get('module_installer')->install(['config_test']);
|
||||
$this->rebuildContainer();
|
||||
$config_test_storage = \Drupal::entityManager()->getStorage('config_test');
|
||||
$this->assertEqual($config_test_storage->load('dotted.default')->label(), 'Default', 'The config_test entity is not overridden by the profile optional configuration.');
|
||||
$this->assertEqual($config_test_storage->load('dotted.default')->label(), 'Default install profile override', 'The config_test entity is overridden by the profile optional configuration.');
|
||||
// Test that override of optional configuration does work.
|
||||
$this->assertEqual($config_test_storage->load('override')->label(), 'Override', 'The optional config_test entity is overridden by the profile optional configuration.');
|
||||
// Test that override of optional configuration which introduces an unmet
|
||||
|
|
|
@ -95,6 +95,7 @@ class ConfigInstallProfileUnmetDependenciesTest extends InstallerTestBase {
|
|||
else {
|
||||
$this->fail('Expected Drupal\Core\Config\UnmetDependenciesException exception thrown');
|
||||
}
|
||||
$this->assertErrorLogged('Configuration objects (system.action.user_block_user_action) provided by user have unmet dependencies in');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -54,3 +54,4 @@ entity.user.contact_form:
|
|||
_controller: '\Drupal\contact\Controller\ContactController::contactPersonalPage'
|
||||
requirements:
|
||||
_access_contact_personal_tab: 'TRUE'
|
||||
user: \d+
|
||||
|
|
|
@ -29,3 +29,19 @@ function content_translation_enable() {
|
|||
$message = t('<a href=":settings_url">Enable translation</a> for <em>content types</em>, <em>taxonomy vocabularies</em>, <em>accounts</em>, or any other element you wish to translate.', $t_args);
|
||||
drupal_set_message($message, 'warning');
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup updates-8.0.0-rc
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebuild the routes as the content translation routes have now new names.
|
||||
*/
|
||||
function content_translation_update_8001() {
|
||||
\Drupal::service('router.builder')->rebuild();
|
||||
}
|
||||
|
||||
/**
|
||||
* @} End of "addtogroup updates-8.0.0-rc".
|
||||
*/
|
||||
|
|
|
@ -53,7 +53,7 @@ function content_translation_help($route_name, RouteMatchInterface $route_match)
|
|||
*/
|
||||
function content_translation_module_implements_alter(&$implementations, $hook) {
|
||||
switch ($hook) {
|
||||
// Move some of our hook implementations to the end of the list.
|
||||
// Move our hook_entity_type_alter() implementation to the end of the list.
|
||||
case 'entity_type_alter':
|
||||
$group = $implementations['content_translation'];
|
||||
unset($implementations['content_translation']);
|
||||
|
@ -140,7 +140,11 @@ function content_translation_entity_type_alter(array &$entity_types) {
|
|||
if ($entity_type->hasLinkTemplate('canonical')) {
|
||||
// Provide default route names for the translation paths.
|
||||
if (!$entity_type->hasLinkTemplate('drupal:content-translation-overview')) {
|
||||
$entity_type->setLinkTemplate('drupal:content-translation-overview', $entity_type->getLinkTemplate('canonical') . '/translations');
|
||||
$translations_path = $entity_type->getLinkTemplate('canonical') . '/translations';
|
||||
$entity_type->setLinkTemplate('drupal:content-translation-overview', $translations_path);
|
||||
$entity_type->setLinkTemplate('drupal:content-translation-add', $translations_path . '/add/{source}/{target}');
|
||||
$entity_type->setLinkTemplate('drupal:content-translation-edit', $translations_path . '/edit/{language}');
|
||||
$entity_type->setLinkTemplate('drupal:content-translation-delete', $translations_path . '/delete/{language}');
|
||||
}
|
||||
// @todo Remove this as soon as menu access checks rely on the
|
||||
// controller. See https://www.drupal.org/node/2155787.
|
||||
|
|
|
@ -652,7 +652,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$source = $form_state->getValue(array('source_langcode', 'source'));
|
||||
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$form_state->setRedirect('content_translation.translation_add_' . $entity_type_id, array(
|
||||
$form_state->setRedirect("entity.$entity_type_id.content_translation_add", array(
|
||||
$entity_type_id => $entity->id(),
|
||||
'source' => $source,
|
||||
'target' => $form_object->getFormLangcode($form_state),
|
||||
|
@ -689,7 +689,7 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
|
|||
$form_state->setRedirectUrl($entity->urlInfo('delete-form'));
|
||||
}
|
||||
else {
|
||||
$form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, [
|
||||
$form_state->setRedirect("entity.$entity_type_id.content_translation_delete", [
|
||||
$entity_type_id => $entity->id(),
|
||||
'language' => $form_object->getFormLangcode($form_state),
|
||||
]);
|
||||
|
|
|
@ -127,7 +127,7 @@ class ContentTranslationController extends ControllerBase {
|
|||
$langcode = $language->getId();
|
||||
|
||||
$add_url = new Url(
|
||||
'content_translation.translation_add_' . $entity_type_id,
|
||||
"entity.$entity_type_id.content_translation_add",
|
||||
array(
|
||||
'source' => $original,
|
||||
'target' => $language->getId(),
|
||||
|
@ -138,7 +138,7 @@ class ContentTranslationController extends ControllerBase {
|
|||
)
|
||||
);
|
||||
$edit_url = new Url(
|
||||
'content_translation.translation_edit_' . $entity_type_id,
|
||||
"entity.$entity_type_id.content_translation_edit",
|
||||
array(
|
||||
'language' => $language->getId(),
|
||||
$entity_type_id => $entity->id(),
|
||||
|
@ -148,7 +148,7 @@ class ContentTranslationController extends ControllerBase {
|
|||
)
|
||||
);
|
||||
$delete_url = new Url(
|
||||
'content_translation.translation_delete_' . $entity_type_id,
|
||||
"entity.$entity_type_id.content_translation_delete",
|
||||
array(
|
||||
'language' => $language->getId(),
|
||||
$entity_type_id => $entity->id(),
|
||||
|
|
|
@ -112,7 +112,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_admin_route' => $is_admin,
|
||||
)
|
||||
);
|
||||
$collection->add("content_translation.translation_add_$entity_type_id", $route);
|
||||
$collection->add("entity.$entity_type_id.content_translation_add", $route);
|
||||
|
||||
$route = new Route(
|
||||
$path . '/edit/{language}',
|
||||
|
@ -137,7 +137,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_admin_route' => $is_admin,
|
||||
)
|
||||
);
|
||||
$collection->add("content_translation.translation_edit_$entity_type_id", $route);
|
||||
$collection->add("entity.$entity_type_id.content_translation_edit", $route);
|
||||
|
||||
$route = new Route(
|
||||
$path . '/delete/{language}',
|
||||
|
@ -162,7 +162,7 @@ class ContentTranslationRouteSubscriber extends RouteSubscriberBase {
|
|||
'_admin_route' => $is_admin,
|
||||
)
|
||||
);
|
||||
$collection->add("content_translation.translation_delete_$entity_type_id", $route);
|
||||
$collection->add("entity.$entity_type_id.content_translation_delete", $route);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ class ContentTranslationEnableTest extends WebTestBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['entity_test', 'menu_link_content'];
|
||||
public static $modules = ['entity_test', 'menu_link_content', 'node'];
|
||||
|
||||
/**
|
||||
* Tests that entity schemas are up-to-date after enabling translation.
|
||||
|
@ -39,6 +39,9 @@ class ContentTranslationEnableTest extends WebTestBase {
|
|||
$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]));
|
||||
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
// The node entity type should not be an option because it has no bundles.
|
||||
$this->assertNoRaw('entity_types[node]');
|
||||
// Enable content translation on entity types that have will have a
|
||||
// content_translation_uid.
|
||||
$edit = [
|
||||
|
@ -47,12 +50,23 @@ class ContentTranslationEnableTest extends WebTestBase {
|
|||
'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'));
|
||||
$this->drupalPostForm(NULL, $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]));
|
||||
|
||||
// Create a node type and check the content translation settings are now
|
||||
// available for nodes.
|
||||
$edit = array(
|
||||
'name' => 'foo',
|
||||
'title_label' => 'title for foo',
|
||||
'type' => 'foo',
|
||||
);
|
||||
$this->drupalPostForm('admin/structure/types/add', $edit, t('Save content type'));
|
||||
$this->drupalGet('admin/config/regional/content-language');
|
||||
$this->assertRaw('entity_types[node]');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -107,7 +107,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$language = ConfigurableLanguage::load($langcode);
|
||||
$values[$langcode] = $this->getNewEntityValues($langcode);
|
||||
|
||||
$add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode
|
||||
|
@ -167,7 +168,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
$language = ConfigurableLanguage::load($langcode);
|
||||
$source_langcode = 'it';
|
||||
$edit = array('source_langcode[source]' => $source_langcode);
|
||||
$add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $default_langcode,
|
||||
'target' => $langcode
|
||||
|
@ -180,7 +182,8 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
// Add another translation and mark the other ones as outdated.
|
||||
$values[$langcode] = $this->getNewEntityValues($langcode);
|
||||
$edit = $this->getEditValues($values, $langcode) + array('content_translation[retranslate]' => TRUE);
|
||||
$add_url = Url::fromRoute('content_translation.translation_add_' . $entity->getEntityTypeId(), [
|
||||
$entity_type_id = $entity->getEntityTypeId();
|
||||
$add_url = Url::fromRoute("entity.$entity_type_id.content_translation_add", [
|
||||
$entity->getEntityTypeId() => $entity->id(),
|
||||
'source' => $source_langcode,
|
||||
'target' => $langcode
|
||||
|
@ -207,13 +210,15 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
*/
|
||||
protected function doTestTranslationOverview() {
|
||||
$entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
|
||||
$this->drupalGet($entity->urlInfo('drupal:content-translation-overview'));
|
||||
$translate_url = $entity->urlInfo('drupal:content-translation-overview');
|
||||
$this->drupalGet($translate_url);
|
||||
$translate_url->setAbsolute(FALSE);
|
||||
|
||||
foreach ($this->langcodes as $langcode) {
|
||||
if ($entity->hasTranslation($langcode)) {
|
||||
$language = new Language(array('id' => $langcode));
|
||||
$view_path = $entity->url('canonical', array('language' => $language));
|
||||
$elements = $this->xpath('//table//a[@href=:href]', array(':href' => $view_path));
|
||||
$view_url = $entity->url('canonical', ['language' => $language]);
|
||||
$elements = $this->xpath('//table//a[@href=:href]', [':href' => $view_url]);
|
||||
$this->assertEqual((string) $elements[0], $entity->getTranslation($langcode)->label(), format_string('Label correctly shown for %language translation.', array('%language' => $langcode)));
|
||||
$edit_path = $entity->url('edit-form', array('language' => $language));
|
||||
$elements = $this->xpath('//table//ul[@class="dropbutton"]/li/a[@href=:href]', array(':href' => $edit_path));
|
||||
|
@ -343,7 +348,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
|
|||
|
||||
// Check that the translator cannot delete the original translation.
|
||||
$args = [$this->entityTypeId => $entity->id(), 'language' => 'en'];
|
||||
$this->drupalGet(Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, $args));
|
||||
$this->drupalGet(Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", $args));
|
||||
$this->assertResponse(403);
|
||||
}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
|
||||
// Create a translation.
|
||||
$this->drupalLogin($this->translator);
|
||||
$add_translation_url = Url::fromRoute('content_translation.translation_add_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $this->langcodes[2]]);
|
||||
$add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $this->langcodes[2]]);
|
||||
$this->drupalPostForm($add_translation_url, array(), t('Save'));
|
||||
$this->rebuildContainer();
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
$this->assertResponse($expected_status['overview'], SafeMarkup::format('The @user_label has the expected translation overview access.', $args));
|
||||
|
||||
// Check whether the user is allowed to create a translation.
|
||||
$add_translation_url = Url::fromRoute('content_translation.translation_add_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options);
|
||||
$add_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_add", [$this->entityTypeId => $this->entity->id(), 'source' => $default_langcode, 'target' => $langcode], $options);
|
||||
if ($expected_status['add_translation'] == 200) {
|
||||
$this->clickLink('Add');
|
||||
$this->assertUrl($add_translation_url->toString(), [], 'The translation overview points to the translation form when creating translations.');
|
||||
|
@ -193,7 +193,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
// Check whether the user is allowed to edit a translation.
|
||||
$langcode = $this->langcodes[2];
|
||||
$options['language'] = $languages[$langcode];
|
||||
$edit_translation_url = Url::fromRoute('content_translation.translation_edit_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
$edit_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_edit", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
if ($expected_status['edit_translation'] == 200) {
|
||||
$this->drupalGet($translations_url);
|
||||
$editor = $expected_status['edit'] == 200;
|
||||
|
@ -221,7 +221,7 @@ class ContentTranslationWorkflowsTest extends ContentTranslationTestBase {
|
|||
// Check whether the user is allowed to delete a translation.
|
||||
$langcode = $this->langcodes[2];
|
||||
$options['language'] = $languages[$langcode];
|
||||
$delete_translation_url = Url::fromRoute('content_translation.translation_delete_' . $this->entityTypeId, [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
$delete_translation_url = Url::fromRoute("entity.$this->entityTypeId.content_translation_delete", [$this->entityTypeId => $this->entity->id(), 'language' => $langcode], $options);
|
||||
if ($expected_status['delete_translation'] == 200) {
|
||||
$this->drupalGet($translations_url);
|
||||
$editor = $expected_status['delete'] == 200;
|
||||
|
|
|
@ -464,7 +464,11 @@ function _editor_get_file_uuids_by_field(EntityInterface $entity) {
|
|||
|
||||
$formatted_text_fields = _editor_get_formatted_text_fields($entity);
|
||||
foreach ($formatted_text_fields as $formatted_text_field) {
|
||||
$text = $entity->get($formatted_text_field)->value;
|
||||
$text = '';
|
||||
$field_items = $entity->get($formatted_text_field);
|
||||
foreach ($field_items as $field_item) {
|
||||
$text .= $field_item->value;
|
||||
}
|
||||
$uuids[$formatted_text_field] = _editor_parse_file_uuids($text);
|
||||
}
|
||||
return $uuids;
|
||||
|
|
|
@ -14,11 +14,43 @@ use Drupal\Component\Annotation\Plugin;
|
|||
*
|
||||
* Plugin Namespace: Plugin\Editor
|
||||
*
|
||||
* Text editor plugin implementations need to define a plugin definition array
|
||||
* through annotation. These definition arrays may be altered through
|
||||
* hook_editor_info_alter(). The definition includes the following keys:
|
||||
*
|
||||
* - id: The unique, system-wide identifier of the text editor. Typically named
|
||||
* the same as the editor library.
|
||||
* - label: The human-readable name of the text editor, translated.
|
||||
* - supports_content_filtering: Whether the editor supports "allowed content
|
||||
* only" filtering.
|
||||
* - supports_inline_editing: Whether the editor supports the inline editing
|
||||
* provided by the Edit module.
|
||||
* - is_xss_safe: Whether this text editor is not vulnerable to XSS attacks.
|
||||
* - supported_element_types: On which form element #types this text editor is
|
||||
* capable of working.
|
||||
*
|
||||
* A complete sample plugin definition should be defined as in this example:
|
||||
*
|
||||
* @code
|
||||
* @Editor(
|
||||
* id = "myeditor",
|
||||
* label = @Translation("My Editor"),
|
||||
* supports_content_filtering = FALSE,
|
||||
* supports_inline_editing = FALSE,
|
||||
* is_xss_safe = FALSE,
|
||||
* supported_element_types = {
|
||||
* "textarea",
|
||||
* "textfield",
|
||||
* }
|
||||
* )
|
||||
* @endcode
|
||||
*
|
||||
* For a working example, see \Drupal\ckeditor\Plugin\Editor\CKEditor
|
||||
*
|
||||
* @see \Drupal\editor\Plugin\EditorPluginInterface
|
||||
* @see \Drupal\editor\Plugin\EditorBase
|
||||
* @see \Drupal\editor\Plugin\EditorManager
|
||||
* @see hook_editor_info_alter()
|
||||
* @see plugin_api
|
||||
*
|
||||
* @Annotation
|
||||
|
|
|
@ -60,6 +60,9 @@ class EditorController extends ControllerBase {
|
|||
* @return \Symfony\Component\HttpFoundation\JsonResponse
|
||||
* A JSON response containing the XSS-filtered value.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
|
||||
* Thrown if no value to filter is specified.
|
||||
*
|
||||
* @see editor_filter_xss()
|
||||
*/
|
||||
public function filterXss(Request $request, FilterFormatInterface $filter_format) {
|
||||
|
|
|
@ -17,30 +17,8 @@ use Drupal\editor\Entity\Editor;
|
|||
* This class provides default implementations of the EditorPluginInterface so
|
||||
* that classes extending this one do not need to implement every method.
|
||||
*
|
||||
* Plugins extending this class need to define a plugin definition array through
|
||||
* annotation. These definition arrays may be altered through
|
||||
* hook_editor_info_alter(). The definition includes the following keys:
|
||||
*
|
||||
* - id: The unique, system-wide identifier of the text editor. Typically named
|
||||
* the same as the editor library.
|
||||
* - label: The human-readable name of the text editor, translated.
|
||||
* - supports_content_filtering: Whether the editor supports "allowed content
|
||||
* only" filtering.
|
||||
* - supports_inline_editing: Whether the editor supports the inline editing
|
||||
* provided by the Edit module.
|
||||
* - is_xss_safe: Whether this text editor is not vulnerable to XSS attacks.
|
||||
*
|
||||
* A complete sample plugin definition should be defined as in this example:
|
||||
*
|
||||
* @code
|
||||
* @Editor(
|
||||
* id = "myeditor",
|
||||
* label = @Translation("My Editor"),
|
||||
* supports_content_filtering = FALSE,
|
||||
* supports_inline_editing = FALSE,
|
||||
* is_xss_safe = FALSE
|
||||
* )
|
||||
* @endcode
|
||||
* Plugins extending this class need to specify an annotation containing the
|
||||
* plugin definition so the plugin can be discovered.
|
||||
*
|
||||
* @see \Drupal\editor\Annotation\Editor
|
||||
* @see \Drupal\editor\Plugin\EditorPluginInterface
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
namespace Drupal\editor\Tests;
|
||||
|
||||
use Drupal\system\Tests\Entity\EntityUnitTestBase;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\Core\Field\FieldStorageDefinitionInterface;
|
||||
|
||||
/**
|
||||
* Tests tracking of file usage by the Text Editor module.
|
||||
|
@ -39,6 +41,11 @@ class EditorFileUsageTest extends EntityUnitTestBase {
|
|||
));
|
||||
$filtered_html_format->save();
|
||||
|
||||
// Set cardinality for body field.
|
||||
FieldStorageConfig::loadByName('node', 'body')
|
||||
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
|
||||
->save();
|
||||
|
||||
// Set up text editor.
|
||||
$editor = entity_create('editor', array(
|
||||
'format' => 'filtered_html',
|
||||
|
@ -56,33 +63,56 @@ class EditorFileUsageTest extends EntityUnitTestBase {
|
|||
* Tests the configurable text editor manager.
|
||||
*/
|
||||
public function testEditorEntityHooks() {
|
||||
$image = entity_create('file');
|
||||
$image->setFileUri('core/misc/druplicon.png');
|
||||
$image->setFilename(drupal_basename($image->getFileUri()));
|
||||
$image->save();
|
||||
$file_usage = $this->container->get('file.usage');
|
||||
$this->assertIdentical(array(), $file_usage->listUsage($image), 'The image has zero usages.');
|
||||
$image_paths = array(
|
||||
0 => 'core/misc/druplicon.png',
|
||||
1 => 'core/misc/tree.png',
|
||||
2 => 'core/misc/help.png',
|
||||
);
|
||||
|
||||
$image_entities = array();
|
||||
foreach ($image_paths as $key => $image_path) {
|
||||
$image = entity_create('file');
|
||||
$image->setFileUri($image_path);
|
||||
$image->setFilename(drupal_basename($image->getFileUri()));
|
||||
$image->save();
|
||||
|
||||
$file_usage = $this->container->get('file.usage');
|
||||
$this->assertIdentical(array(), $file_usage->listUsage($image), 'The image ' . $image_paths[$key] . ' has zero usages.');
|
||||
|
||||
$image_entities[] = $image;
|
||||
}
|
||||
|
||||
$body = array();
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
// Don't be rude, say hello.
|
||||
$body_value = '<p>Hello, world!</p>';
|
||||
// Test handling of a valid image entry.
|
||||
$body_value .= '<img src="awesome-llama-' . $key . '.jpg" data-entity-type="file" data-entity-uuid="' . $image_entity->uuid() . '" />';
|
||||
// Test handling of an invalid data-entity-uuid attribute.
|
||||
$body_value .= '<img src="awesome-llama-' . $key . '.jpg" data-entity-type="file" data-entity-uuid="invalid-entity-uuid-value" />';
|
||||
// Test handling of an invalid data-entity-type attribute.
|
||||
$body_value .= '<img src="awesome-llama-' . $key . '.jpg" data-entity-type="invalid-entity-type-value" data-entity-uuid="' . $image_entity->uuid() . '" />';
|
||||
// Test handling of a non-existing UUID.
|
||||
$body_value .= '<img src="awesome-llama-' . $key . '.jpg" data-entity-type="file" data-entity-uuid="30aac704-ba2c-40fc-b609-9ed121aa90f4" />';
|
||||
|
||||
$body[] = array(
|
||||
'value' => $body_value,
|
||||
'format' => 'filtered_html',
|
||||
);
|
||||
}
|
||||
|
||||
$body_value = '<p>Hello, world!</p><img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="' . $image->uuid() . '" />';
|
||||
// Test handling of an invalid data-entity-uuid attribute.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="invalid-entity-uuid-value" />';
|
||||
// Test handling of an invalid data-entity-type attribute.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="invalid-entity-type-value" data-entity-uuid="' . $image->uuid() . '" />';
|
||||
// Test handling of a non-existing UUID.
|
||||
$body_value .= '<img src="awesome-llama.jpg" data-entity-type="file" data-entity-uuid="30aac704-ba2c-40fc-b609-9ed121aa90f4" />';
|
||||
// Test editor_entity_insert(): increment.
|
||||
$this->createUser();
|
||||
$node = entity_create('node', array(
|
||||
'type' => 'page',
|
||||
'title' => 'test',
|
||||
'body' => array(
|
||||
'value' => $body_value,
|
||||
'format' => 'filtered_html',
|
||||
),
|
||||
'body' => $body,
|
||||
'uid' => 1,
|
||||
));
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '1'))), $file_usage->listUsage($image), 'The image has 1 usage.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '1'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 1 usage.');
|
||||
}
|
||||
|
||||
// Test editor_entity_update(): increment, twice, by creating new revisions.
|
||||
$node->setNewRevision(TRUE);
|
||||
|
@ -90,45 +120,68 @@ class EditorFileUsageTest extends EntityUnitTestBase {
|
|||
$second_revision_id = $node->getRevisionId();
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 3 usages.');
|
||||
}
|
||||
|
||||
// Test hook_entity_update(): decrement, by modifying the last revision:
|
||||
// remove the data-entity-type attribute from the body field.
|
||||
$body = $node->get('body')->first()->get('value');
|
||||
$original_value = $body->getValue();
|
||||
$new_value = str_replace('data-entity-type', 'data-entity-type-modified', $original_value);
|
||||
$body->setValue($new_value);
|
||||
$original_values = array();
|
||||
for ($i = 0; $i < count($image_entities); $i++) {
|
||||
$original_value = $node->body[$i]->value;
|
||||
$new_value = str_replace('data-entity-type', 'data-entity-type-modified', $original_value);
|
||||
$node->body[$i]->value = $new_value;
|
||||
$original_values[$i] = $original_value;
|
||||
}
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image), 'The image has 2 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 2 usages.');
|
||||
}
|
||||
|
||||
// Test editor_entity_update(): increment again by creating a new revision:
|
||||
// read the data- attributes to the body field.
|
||||
$node->setNewRevision(TRUE);
|
||||
$node->get('body')->first()->get('value')->setValue($original_value);
|
||||
foreach ($original_values as $key => $original_value) {
|
||||
$node->body[$key]->value = $original_value;
|
||||
}
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 3 usages.');
|
||||
}
|
||||
|
||||
// Test hook_entity_update(): decrement, by modifying the last revision:
|
||||
// remove the data-entity-uuid attribute from the body field.
|
||||
$body = $node->get('body')->first()->get('value');
|
||||
$new_value = str_replace('data-entity-uuid', 'data-entity-uuid-modified', $original_value);
|
||||
$body->setValue($new_value);
|
||||
foreach ($original_values as $key => $original_value) {
|
||||
$original_value = $node->body[$key]->value;
|
||||
$new_value = str_replace('data-entity-type', 'data-entity-type-modified', $original_value);
|
||||
$node->body[$key]->value = $new_value;
|
||||
}
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image), 'The image has 2 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 2 usages.');
|
||||
}
|
||||
|
||||
// Test hook_entity_update(): increment, by modifying the last revision:
|
||||
// read the data- attributes to the body field.
|
||||
$node->get('body')->first()->get('value')->setValue($original_value);
|
||||
foreach ($original_values as $key => $original_value) {
|
||||
$node->body[$key]->value = $original_value;
|
||||
}
|
||||
$node->save();
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image), 'The image has 3 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '3'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 3 usages.');
|
||||
}
|
||||
|
||||
// Test editor_entity_revision_delete(): decrement, by deleting a revision.
|
||||
entity_revision_delete('node', $second_revision_id);
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image), 'The image has 2 usages.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array('editor' => array('node' => array(1 => '2'))), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has 2 usages.');
|
||||
}
|
||||
|
||||
// Test editor_entity_delete().
|
||||
$node->delete();
|
||||
$this->assertIdentical(array(), $file_usage->listUsage($image), 'The image has zero usages again.');
|
||||
foreach ($image_entities as $key => $image_entity) {
|
||||
$this->assertIdentical(array(), $file_usage->listUsage($image_entity), 'The image ' . $image_paths[$key] . ' has zero usages again.');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ class FieldInstance extends DrupalSqlBase {
|
|||
public function fields() {
|
||||
return array(
|
||||
'field_name' => $this->t('The machine name of field.'),
|
||||
'type_name' => $this->t('Content type where is used this field.'),
|
||||
'type_name' => $this->t('Content type where this field is in use.'),
|
||||
'weight' => $this->t('Weight.'),
|
||||
'label' => $this->t('A name to show.'),
|
||||
'widget_type' => $this->t('Widget type.'),
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\field\Tests\EntityReference;
|
||||
|
||||
use Drupal\comment\Entity\Comment;
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
|
@ -17,8 +19,11 @@ use Drupal\entity_test\Entity\EntityTestStringId;
|
|||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Tests\FieldUnitTestBase;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\node\Entity\Node;
|
||||
use Drupal\taxonomy\Entity\Term;
|
||||
use Drupal\taxonomy\Entity\Vocabulary;
|
||||
use Drupal\user\Entity\User;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -35,7 +40,7 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
|
|||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['taxonomy', 'text', 'filter', 'views', 'field'];
|
||||
public static $modules = ['node', 'comment', 'file', 'taxonomy', 'text', 'filter', 'views', 'field'];
|
||||
|
||||
/**
|
||||
* The taxonomy vocabulary to test with.
|
||||
|
@ -66,6 +71,11 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
|
|||
|
||||
$this->installEntitySchema('entity_test_string_id');
|
||||
$this->installEntitySchema('taxonomy_term');
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('comment');
|
||||
$this->installEntitySchema('file');
|
||||
|
||||
$this->installSchema('comment', ['comment_entity_statistics']);
|
||||
|
||||
$this->vocabulary = entity_create('taxonomy_vocabulary', array(
|
||||
'name' => $this->randomMachineName(),
|
||||
|
@ -90,6 +100,10 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
|
|||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_term', 'Test content entity reference', 'taxonomy_term');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_entity_test_string_id', 'Test content entity reference with string ID', 'entity_test_string_id');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_taxonomy_vocabulary', 'Test config entity reference', 'taxonomy_vocabulary');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_node', 'Test node entity reference', 'node');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_user', 'Test user entity reference', 'user');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_comment', 'Test comment entity reference', 'comment');
|
||||
$this->createEntityReferenceField('entity_test', 'entity_test', 'field_test_file', 'Test file entity reference', 'file');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -333,9 +347,9 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
|
|||
}
|
||||
|
||||
/**
|
||||
* Tests validation constraint.
|
||||
* Tests ValidReferenceConstraint with newly created and unsaved entities.
|
||||
*/
|
||||
public function testValidation() {
|
||||
public function testAutocreateValidation() {
|
||||
// The term entity is unsaved here.
|
||||
$term = Term::create(array(
|
||||
'name' => $this->randomMachineName(),
|
||||
|
@ -367,6 +381,100 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
|
|||
$entity->save();
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(0, count($errors));
|
||||
|
||||
// Test with an unpublished and unsaved node.
|
||||
$title = $this->randomString();
|
||||
$node = Node::create([
|
||||
'title' => $title,
|
||||
'type' => 'node',
|
||||
'status' => NODE_NOT_PUBLISHED,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_node' => [
|
||||
'entity' => $node,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(1, count($errors));
|
||||
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $title]));
|
||||
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
|
||||
|
||||
// Publish the node and try again.
|
||||
$node->setPublished(TRUE);
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(0, count($errors));
|
||||
|
||||
// Test with an unpublished and unsaved comment.
|
||||
$title = $this->randomString();
|
||||
$comment = Comment::create([
|
||||
'subject' => $title,
|
||||
'comment_type' => 'comment',
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_comment' => [
|
||||
'entity' => $comment,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(1, count($errors));
|
||||
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'comment', '%label' => $title]));
|
||||
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_comment.0.entity');
|
||||
|
||||
// Publish the comment and try again.
|
||||
$comment->setPublished(TRUE);
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(0, count($errors));
|
||||
|
||||
// Test with an inactive and unsaved user.
|
||||
$name = $this->randomString();
|
||||
$user = User::create([
|
||||
'name' => $name,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_user' => [
|
||||
'entity' => $user,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(1, count($errors));
|
||||
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'user', '%label' => $name]));
|
||||
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_user.0.entity');
|
||||
|
||||
// Activate the user and try again.
|
||||
$user->activate();
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(0, count($errors));
|
||||
|
||||
// Test with a temporary and unsaved file.
|
||||
$filename = $this->randomMachineName() . '.txt';
|
||||
$file = File::create([
|
||||
'filename' => $filename,
|
||||
'status' => 0,
|
||||
]);
|
||||
|
||||
$entity = EntityTest::create([
|
||||
'field_test_file' => [
|
||||
'entity' => $file,
|
||||
],
|
||||
]);
|
||||
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(1, count($errors));
|
||||
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'file', '%label' => $filename]));
|
||||
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_file.0.entity');
|
||||
|
||||
// Set the file as permanent and try again.
|
||||
$file->setPermanent();
|
||||
$errors = $entity->validate();
|
||||
$this->assertEqual(0, count($errors));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -68,6 +68,11 @@ class UriItemTest extends FieldUnitTestBase {
|
|||
'type' => 'uri',
|
||||
])
|
||||
->save();
|
||||
|
||||
// Test the generateSampleValue() method.
|
||||
$entity = entity_create('entity_test');
|
||||
$entity->$field_name->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,3 +5,6 @@ migrate.destination.entity:file:
|
|||
source_path_property:
|
||||
type: string
|
||||
label: 'Source path'
|
||||
urlencode:
|
||||
type: boolean
|
||||
label: 'Whether to urlencode incoming file paths'
|
||||
|
|
|
@ -9,27 +9,6 @@ use Drupal\Core\Field\FieldDefinitionInterface;
|
|||
use Drupal\Core\Field\FieldFilteredMarkup;
|
||||
use Drupal\Core\Render\Element;
|
||||
|
||||
/**
|
||||
* Returns HTML for an individual file upload widget.
|
||||
*
|
||||
* Default template: file-widget.html.twig.
|
||||
*
|
||||
* @param array $variables
|
||||
* An associative array containing:
|
||||
* - element: A render element representing the file.
|
||||
*/
|
||||
function template_preprocess_file_widget(&$variables) {
|
||||
$element = $variables['element'];
|
||||
if (!empty($element['fids']['#value'])) {
|
||||
// Add the file size after the file name.
|
||||
$file = reset($element['#files']);
|
||||
$element['file_' . $file->id()]['filename']['#suffix'] = ' <span class="file-size">(' . format_size($file->getSize()) . ')</span> ';
|
||||
}
|
||||
$variables['element'] = $element;
|
||||
// The "js-form-managed-file" class is required for proper Ajax functionality.
|
||||
$variables['attributes'] = array('class' => array('file-widget', 'js-form-managed-file', 'form-managed-file', 'clearfix'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares variables for multi file form widget templates.
|
||||
*
|
||||
|
|
|
@ -110,10 +110,10 @@
|
|||
*/
|
||||
Drupal.behaviors.filePreviewLinks = {
|
||||
attach: function (context) {
|
||||
$(context).find('div.js-form-managed-file .file a, .file-widget .file a').on('click', Drupal.file.openInNewWindow);
|
||||
$(context).find('div.js-form-managed-file .file a').on('click', Drupal.file.openInNewWindow);
|
||||
},
|
||||
detach: function (context) {
|
||||
$(context).find('div.js-form-managed-file .file a, .file-widget .file a').off('click', Drupal.file.openInNewWindow);
|
||||
$(context).find('div.js-form-managed-file .file a').off('click', Drupal.file.openInNewWindow);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -555,10 +555,6 @@ function file_theme() {
|
|||
),
|
||||
|
||||
// From file.field.inc.
|
||||
'file_widget' => array(
|
||||
'render element' => 'element',
|
||||
'file' => 'file.field.inc',
|
||||
),
|
||||
'file_widget_multiple' => array(
|
||||
'render element' => 'element',
|
||||
'file' => 'file.field.inc',
|
||||
|
|
|
@ -23,3 +23,4 @@ process:
|
|||
uid: uid
|
||||
destination:
|
||||
plugin: entity:file
|
||||
urlencode: true
|
||||
|
|
|
@ -23,3 +23,4 @@ process:
|
|||
destination:
|
||||
plugin: entity:file
|
||||
source_path_property: filepath
|
||||
urlencode: true
|
||||
|
|
|
@ -27,8 +27,41 @@ class FileSelection extends DefaultSelection {
|
|||
*/
|
||||
protected function buildEntityQuery($match = NULL, $match_operator = 'CONTAINS') {
|
||||
$query = parent::buildEntityQuery($match, $match_operator);
|
||||
$query->condition('status', FILE_STATUS_PERMANENT);
|
||||
// Allow referencing :
|
||||
// - files with status "permanent"
|
||||
// - or files uploaded by the current user (since newly uploaded files only
|
||||
// become "permanent" after the containing entity gets validated and
|
||||
// saved.)
|
||||
$query->condition($query->orConditionGroup()
|
||||
->condition('status', FILE_STATUS_PERMANENT)
|
||||
->condition('uid', $this->currentUser->id()));
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$file = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable file, it needs to have a "permanent"
|
||||
// status.
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
$file->setPermanent();
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
$entities = array_filter($entities, function ($file) {
|
||||
/** @var \Drupal\file\FileInterface $file */
|
||||
return $file->isPermanent() || $file->getOwnerId() === $this->currentUser->id();
|
||||
});
|
||||
return $entities;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ use Drupal\Core\TypedData\DataDefinition;
|
|||
* default_widget = "file_generic",
|
||||
* default_formatter = "file_default",
|
||||
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
|
||||
* constraints = {"ValidReference" = {}, "ReferenceAccess" = {}}
|
||||
* constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
|
||||
* )
|
||||
*/
|
||||
class FileItem extends EntityReferenceItem {
|
||||
|
|
|
@ -18,8 +18,9 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
|||
use Drupal\Core\Render\Element;
|
||||
use Drupal\Core\Render\ElementInfoManagerInterface;
|
||||
use Drupal\file\Element\ManagedFile;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\file\Entity\File;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Validator\ConstraintViolationListInterface;
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'file_generic' widget.
|
||||
|
@ -369,11 +370,6 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
|
|||
$item = $element['#value'];
|
||||
$item['fids'] = $element['fids']['#value'];
|
||||
|
||||
// Prevent the file widget from overriding the image widget.
|
||||
if (!isset($element['#theme'])) {
|
||||
$element['#theme'] = 'file_widget';
|
||||
}
|
||||
|
||||
// Add the display field if enabled.
|
||||
if ($element['#display_field']) {
|
||||
$element['display'] = array(
|
||||
|
@ -575,4 +571,15 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
|
|||
static::setWidgetState($parents, $field_name, $form_state, $field_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function flagErrors(FieldItemListInterface $items, ConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
|
||||
// Never flag validation errors for the remove button.
|
||||
$clicked_button = end($form_state->getTriggeringElement()['#parents']);
|
||||
if ($clicked_button !== 'remove_button') {
|
||||
parent::flagErrors($items, $violations, $form, $form_state);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraint.
|
||||
*/
|
||||
|
||||
namespace Drupal\file\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* Validation File constraint.
|
||||
*
|
||||
* @Constraint(
|
||||
* id = "FileValidation",
|
||||
* label = @Translation("File Validation", context = "Validation")
|
||||
* )
|
||||
*/
|
||||
class FileValidationConstraint extends Constraint {
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator.
|
||||
*/
|
||||
|
||||
namespace Drupal\file\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
|
||||
/**
|
||||
* Checks that a file referenced in a file field is valid.
|
||||
*/
|
||||
class FileValidationConstraintValidator extends ConstraintValidator {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validate($value, Constraint $constraint) {
|
||||
// Get the file to execute validators.
|
||||
$file = $value->get('entity')->getTarget()->getValue();
|
||||
// Get the validators.
|
||||
$validators = $value->getUploadValidators();
|
||||
// Checks that a file meets the criteria specified by the validators.
|
||||
if ($errors = file_validate($file, $validators)) {
|
||||
foreach ($errors as $error) {
|
||||
$this->context->addViolation($error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,8 +7,11 @@
|
|||
|
||||
namespace Drupal\file\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\Field\Plugin\Field\FieldType\UriItem;
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\StreamWrapper\LocalStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
|
@ -41,7 +44,7 @@ class EntityFile extends EntityContentBase {
|
|||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
|
||||
$configuration += array(
|
||||
'source_base_path' => '',
|
||||
'source_path_property' => 'filepath',
|
||||
|
@ -49,7 +52,7 @@ class EntityFile extends EntityContentBase {
|
|||
'move' => FALSE,
|
||||
'urlencode' => FALSE,
|
||||
);
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager, $field_type_manager);
|
||||
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
$this->fileSystem = $file_system;
|
||||
|
@ -68,6 +71,7 @@ class EntityFile extends EntityContentBase {
|
|||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager'),
|
||||
$container->get('plugin.manager.field.field_type'),
|
||||
$container->get('stream_wrapper_manager'),
|
||||
$container->get('file_system')
|
||||
);
|
||||
|
@ -77,6 +81,12 @@ class EntityFile extends EntityContentBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
// For stub rows, there is no real file to deal with, let the stubbing
|
||||
// process take its default path.
|
||||
if ($row->isStub()) {
|
||||
return parent::getEntity($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
$destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
|
||||
$entity = $this->storage->loadByProperties(['uri' => $destination]);
|
||||
if ($entity) {
|
||||
|
@ -91,6 +101,12 @@ class EntityFile extends EntityContentBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
// For stub rows, there is no real file to deal with, let the stubbing
|
||||
// process create the stub entity.
|
||||
if ($row->isStub()) {
|
||||
return parent::import($row, $old_destination_id_values);
|
||||
}
|
||||
|
||||
$file = $row->getSourceProperty($this->configuration['source_path_property']);
|
||||
$destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
|
||||
$source = $this->configuration['source_base_path'] . $file;
|
||||
|
@ -256,4 +272,30 @@ class EntityFile extends EntityContentBase {
|
|||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
// We stub the uri value ourselves so we can create a real stub file for it.
|
||||
if (!$row->getDestinationProperty('uri')) {
|
||||
$field_definitions = $this->entityManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(),
|
||||
$this->getKey('bundle'));
|
||||
$value = UriItem::generateSampleValue($field_definitions['uri']);
|
||||
if (empty($value)) {
|
||||
throw new MigrateException('Stubbing failed, unable to generate value for field uri');
|
||||
}
|
||||
// generateSampleValue() wraps the value in an array.
|
||||
$value = reset($value);
|
||||
// Make it into a proper public file uri, stripping off the existing
|
||||
// scheme if present.
|
||||
$value = 'public://' . preg_replace('|^[a-z]+://|i', '', $value);
|
||||
$value = Unicode::substr($value, 0, $field_definitions['uri']->getSetting('max_length'));
|
||||
// Create a real file, so File::preSave() can do filesize() on it.
|
||||
touch($value);
|
||||
$row->setDestinationProperty('uri', $value);
|
||||
}
|
||||
parent::processStubRow($row);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,11 @@ class FileUri extends ProcessPluginBase {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// If we're stubbing a file entity, return a uri of NULL so it will get
|
||||
// stubbed by the general process.
|
||||
if ($row->isStub()) {
|
||||
return NULL;
|
||||
}
|
||||
list($filepath, $file_directory_path, $temp_directory_path, $is_public) = $value;
|
||||
|
||||
// Specific handling using $temp_directory_path for temporary files.
|
||||
|
|
|
@ -159,4 +159,35 @@ class FileFieldValidateTest extends FileFieldTestBase {
|
|||
$this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with extension checking.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that a file can always be removed if it does not pass validation.
|
||||
*/
|
||||
public function testFileRemoval() {
|
||||
$node_storage = $this->container->get('entity.manager')->getStorage('node');
|
||||
$type_name = 'article';
|
||||
$field_name = 'file_test';
|
||||
$this->createFileField($field_name, 'node', $type_name);
|
||||
|
||||
$test_file = $this->getTestFile('image');
|
||||
|
||||
// Disable extension checking.
|
||||
$this->updateFileField($field_name, $type_name, array('file_extensions' => ''));
|
||||
|
||||
// Check that the file can be uploaded with no extension checking.
|
||||
$nid = $this->uploadNodeFile($test_file, $field_name, $type_name);
|
||||
$node_storage->resetCache(array($nid));
|
||||
$node = $node_storage->load($nid);
|
||||
$node_file = File::load($node->{$field_name}->target_id);
|
||||
$this->assertFileExists($node_file, 'File exists after uploading a file with no extension checking.');
|
||||
$this->assertFileEntryExists($node_file, 'File entry exists after uploading a file with no extension checking.');
|
||||
|
||||
// Enable extension checking for text files.
|
||||
$this->updateFileField($field_name, $type_name, array('file_extensions' => 'txt'));
|
||||
|
||||
// Check that the file can still be removed.
|
||||
$this->removeNodeFile($nid);
|
||||
$this->assertNoText('Only files with the following extensions are allowed: txt.');
|
||||
$this->assertText('Article ' . $node->getTitle() . ' has been updated.');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
42
core/modules/file/src/Tests/Migrate/MigrateFileStubTest.php
Normal file
42
core/modules/file/src/Tests/Migrate/MigrateFileStubTest.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\file\Tests\Migrate\MigrateFileStubTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\file\Tests\Migrate;
|
||||
|
||||
use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||
use Drupal\migrate_drupal\Tests\StubTestTrait;
|
||||
|
||||
/**
|
||||
* Test stub creation for file entities.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class MigrateFileStubTest extends MigrateDrupalTestBase {
|
||||
|
||||
use StubTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['file'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('file');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of file stubs.
|
||||
*/
|
||||
public function testStub() {
|
||||
$this->performStubTest('file');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation to display a file widget.
|
||||
*
|
||||
* Available variables:
|
||||
* - element: Form element for the managed file.
|
||||
* - attributes: Remaining HTML attributes for the containing element.
|
||||
*
|
||||
* @see template_preprocess_file_widget()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
<div{{ attributes }}>
|
||||
{{ element }}
|
||||
</div>
|
119
core/modules/file/tests/src/Kernel/FileItemValidationTest.php
Normal file
119
core/modules/file/tests/src/Kernel/FileItemValidationTest.php
Normal file
|
@ -0,0 +1,119 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\file\Kernel\FileItemValidationTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\file\Kernel;
|
||||
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\file\Entity\File;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\user\Entity\User;
|
||||
use org\bovigo\vfs\vfsStream;
|
||||
|
||||
/**
|
||||
* Tests that files referenced in file and image fields are always validated.
|
||||
*
|
||||
* @group file
|
||||
*/
|
||||
class FileItemValidationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['file', 'image', 'entity_test', 'field', 'user', 'system'];
|
||||
|
||||
/**
|
||||
* A user.
|
||||
*
|
||||
* @var \Drupal\user\UserInterface
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->installEntitySchema('user');
|
||||
$this->installEntitySchema('file');
|
||||
$this->installSchema('file', 'file_usage');
|
||||
$this->installSchema('system', 'sequences');
|
||||
|
||||
$this->user = User::create([
|
||||
'name' => 'username',
|
||||
'status' => 1,
|
||||
]);
|
||||
$this->user->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraint
|
||||
* @covers \Drupal\file\Plugin\Validation\Constraint\FileValidationConstraintValidator
|
||||
* @dataProvider getFileTypes
|
||||
*/
|
||||
public function testFileValidationConstraint($file_type) {
|
||||
$field_storage = FieldStorageConfig::create([
|
||||
'field_name' => 'field_test_file',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => $file_type,
|
||||
]);
|
||||
$field_storage->save();
|
||||
|
||||
$field = FieldConfig::create([
|
||||
'field_name' => 'field_test_file',
|
||||
'entity_type' => 'entity_test',
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => [
|
||||
'max_filesize' => '2k',
|
||||
'file_extensions' => 'jpg|png',
|
||||
],
|
||||
]);
|
||||
$field->save();
|
||||
|
||||
vfsStream::setup('drupal_root');
|
||||
vfsStream::create([
|
||||
'sites' => [
|
||||
'default' => [
|
||||
'files' => [
|
||||
'test.txt' => str_repeat('a', 3000),
|
||||
]
|
||||
]
|
||||
]
|
||||
]);
|
||||
|
||||
// Test for max filesize.
|
||||
$file = File::create([
|
||||
'uri' => 'vfs://drupal_root/sites/default/files/test.txt',
|
||||
]);
|
||||
$file->setPermanent();
|
||||
$file->save();
|
||||
|
||||
$entity_test = EntityTest::create([
|
||||
'uid' => $this->user->id(),
|
||||
'field_test_file' => [
|
||||
'target_id' => $file->id(),
|
||||
]
|
||||
]);
|
||||
$result = $entity_test->validate();
|
||||
$this->assertCount(2, $result);
|
||||
|
||||
$this->assertEquals('field_test_file.0', $result->get(0)->getPropertyPath());
|
||||
$this->assertEquals('The file is <em class="placeholder">2.93 KB</em> exceeding the maximum file size of <em class="placeholder">2 KB</em>.', (string) $result->get(0)->getMessage());
|
||||
$this->assertEquals('field_test_file.0', $result->get(1)->getPropertyPath());
|
||||
$this->assertEquals('Only files with the following extensions are allowed: <em class="placeholder">jpg|png</em>.', (string) $result->get(1)->getMessage());
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of file types to test.
|
||||
*/
|
||||
public function getFileTypes() {
|
||||
return [['file'], ['image']];
|
||||
}
|
||||
|
||||
}
|
|
@ -11,3 +11,4 @@ history.read_node:
|
|||
_controller: '\Drupal\history\Controller\HistoryController::readNode'
|
||||
requirements:
|
||||
_entity_access: 'node.view'
|
||||
node: \d+
|
||||
|
|
|
@ -81,8 +81,7 @@ class HistoryUserTimestamp extends FilterPluginBase {
|
|||
|
||||
$clause = '';
|
||||
$clause2 = '';
|
||||
if (\Drupal::moduleHandler()->moduleExists('comment')) {
|
||||
$ces = $this->query->ensureTable('comment_entity_statistics', $this->relationship);
|
||||
if ($ces = $this->query->ensureTable('comment_entity_statistics', $this->relationship)) {
|
||||
$clause = ("OR $ces.last_comment_timestamp > (***CURRENT_TIME*** - $limit)");
|
||||
$clause2 = "OR $field < $ces.last_comment_timestamp";
|
||||
}
|
||||
|
|
|
@ -80,5 +80,14 @@ class HistoryTimestampTest extends ViewTestBase {
|
|||
$this->executeView($view);
|
||||
$this->assertEqual(count($view->result), 1);
|
||||
$this->assertIdenticalResultset($view, array(array('nid' => $nodes[0]->id())), $column_map);
|
||||
|
||||
// Install Comment module and make sure that content types without comment
|
||||
// field will not break the view.
|
||||
// See \Drupal\history\Plugin\views\filter\HistoryUserTimestamp::query()
|
||||
\Drupal::service('module_installer')->install(['comment']);
|
||||
$view = Views::getView('test_history');
|
||||
$view->setDisplay('page_2');
|
||||
$this->executeView($view);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ use Drupal\file\Plugin\Field\FieldType\FileItem;
|
|||
* },
|
||||
* },
|
||||
* list_class = "\Drupal\file\Plugin\Field\FieldType\FileFieldItemList",
|
||||
* constraints = {"ValidReference" = {}, "ReferenceAccess" = {}}
|
||||
* constraints = {"ReferenceAccess" = {}, "FileValidation" = {}}
|
||||
* )
|
||||
*/
|
||||
class ImageItem extends FileItem {
|
||||
|
@ -346,10 +346,10 @@ class ImageItem extends FileItem {
|
|||
if ($path = $random->image(drupal_realpath($destination), $min_resolution, $max_resolution)) {
|
||||
$image = File::create();
|
||||
$image->setFileUri($path);
|
||||
// $image->setOwner($account);
|
||||
$image->setOwnerId(\Drupal::currentUser()->id());
|
||||
$image->setMimeType('image/' . pathinfo($path, PATHINFO_EXTENSION));
|
||||
$image->setFileName(drupal_basename($path));
|
||||
$destination_dir = $settings['uri_scheme'] . '://' . $settings['file_directory'];
|
||||
$destination_dir = static::doGetUploadLocation($settings);
|
||||
file_prepare_directory($destination_dir, FILE_CREATE_DIRECTORY);
|
||||
$destination = $destination_dir . '/' . basename($path);
|
||||
$file = file_move($image, $destination, FILE_CREATE_DIRECTORY);
|
||||
|
|
13
core/modules/language/language.install
Normal file
13
core/modules/language/language.install
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Update functions for Language module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Rebuild the container as services changed.
|
||||
*/
|
||||
function language_update_8001() {
|
||||
\Drupal::service('kernel')->invalidateContainer();
|
||||
}
|
|
@ -9,7 +9,7 @@ services:
|
|||
- [initLanguageManager]
|
||||
language.config_subscriber:
|
||||
class: Drupal\language\EventSubscriber\ConfigSubscriber
|
||||
arguments: ['@language_manager', '@language.default', '@config.factory']
|
||||
arguments: ['@language_manager', '@language.default', '@config.factory', '@language_negotiator']
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
language.config_factory_override:
|
||||
|
|
|
@ -14,6 +14,8 @@ use Drupal\Core\Language\LanguageManagerInterface;
|
|||
use Drupal\Core\Config\ConfigCrudEvent;
|
||||
use Drupal\Core\Config\ConfigEvents;
|
||||
use Drupal\language\ConfigurableLanguageManager;
|
||||
use Drupal\language\HttpKernel\PathProcessorLanguage;
|
||||
use Drupal\language\LanguageNegotiatorInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
|
@ -42,6 +44,20 @@ class ConfigSubscriber implements EventSubscriberInterface {
|
|||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* The language negotiator.
|
||||
*
|
||||
* @var \Drupal\language\LanguageNegotiatorInterface
|
||||
*/
|
||||
protected $languageNegotiator;
|
||||
|
||||
/**
|
||||
* The language path processor.
|
||||
*
|
||||
* @var \Drupal\language\HttpKernel\PathProcessorLanguage
|
||||
*/
|
||||
protected $pathProcessorLanguage;
|
||||
|
||||
/**
|
||||
* Constructs a new class object.
|
||||
*
|
||||
|
@ -51,11 +67,14 @@ class ConfigSubscriber implements EventSubscriberInterface {
|
|||
* The default language.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\language\LanguageNegotiatorInterface $language_negotiator
|
||||
* The language negotiator.
|
||||
*/
|
||||
public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory) {
|
||||
public function __construct(LanguageManagerInterface $language_manager, LanguageDefault $language_default, ConfigFactoryInterface $config_factory, LanguageNegotiatorInterface $language_negotiator) {
|
||||
$this->languageManager = $language_manager;
|
||||
$this->languageDefault = $language_default;
|
||||
$this->configFactory = $config_factory;
|
||||
$this->languageNegotiator = $language_negotiator;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,6 +121,25 @@ class ConfigSubscriber implements EventSubscriberInterface {
|
|||
// Trigger a container rebuild on the next request by invalidating it.
|
||||
ConfigurableLanguageManager::rebuildServices();
|
||||
}
|
||||
elseif ($saved_config->getName() == 'language.types' && $event->isChanged('negotiation')) {
|
||||
// If the negotiation configuration changed the language negotiator and
|
||||
// the language path processor have to be reset so that they regenerate
|
||||
// the method instances and also sort them accordingly to the new config.
|
||||
$this->languageNegotiator->reset();
|
||||
if (isset($this->pathProcessorLanguage)) {
|
||||
$this->pathProcessorLanguage->reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects the language path processors on multilingual site configuration.
|
||||
*
|
||||
* @param \Drupal\language\HttpKernel\PathProcessorLanguage $path_processor_language
|
||||
* The language path processor.
|
||||
*/
|
||||
public function setPathProcessorLanguage(PathProcessorLanguage $path_processor_language) {
|
||||
$this->pathProcessorLanguage = $path_processor_language;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -63,7 +63,7 @@ class ContentLanguageSettingsForm extends FormBase {
|
|||
$bundles = $this->entityManager->getAllBundleInfo();
|
||||
$language_configuration = array();
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode')) {
|
||||
if (!$entity_type instanceof ContentEntityTypeInterface || !$entity_type->hasKey('langcode') || !isset($bundles[$entity_type_id])) {
|
||||
continue;
|
||||
}
|
||||
$labels[$entity_type_id] = $entity_type->getLabel() ?: $entity_type_id;
|
||||
|
|
|
@ -13,6 +13,7 @@ use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
|
|||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\language\ConfigurableLanguageManagerInterface;
|
||||
use Drupal\language\EventSubscriber\ConfigSubscriber;
|
||||
use Drupal\language\LanguageNegotiatorInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
@ -57,6 +58,14 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa
|
|||
*/
|
||||
protected $multilingual;
|
||||
|
||||
/**
|
||||
* The language configuration event subscriber.
|
||||
*
|
||||
* @var \Drupal\language\EventSubscriber\ConfigSubscriber
|
||||
*/
|
||||
protected $configSubscriber;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a PathProcessorLanguage object.
|
||||
*
|
||||
|
@ -68,12 +77,15 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa
|
|||
* The language negotiator.
|
||||
* @param \Drupal\Core\Session\AccountInterface $current_user
|
||||
* The current active user.
|
||||
* @param \Drupal\language\EventSubscriber\ConfigSubscriber $config_subscriber
|
||||
* The language configuration event subscriber.
|
||||
*/
|
||||
public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user) {
|
||||
public function __construct(ConfigFactoryInterface $config, ConfigurableLanguageManagerInterface $language_manager, LanguageNegotiatorInterface $negotiator, AccountInterface $current_user, ConfigSubscriber $config_subscriber) {
|
||||
$this->config = $config;
|
||||
$this->languageManager = $language_manager;
|
||||
$this->negotiator = $negotiator;
|
||||
$this->negotiator->setCurrentUser($current_user);
|
||||
$this->configSubscriber = $config_subscriber;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -152,4 +164,22 @@ class PathProcessorLanguage implements InboundPathProcessorInterface, OutboundPa
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the injected event subscriber with the language path processor.
|
||||
*
|
||||
* The language path processor service is registered only on multilingual
|
||||
* site configuration, thus we inject it in the event subscriber only when
|
||||
* it is initialized.
|
||||
*/
|
||||
public function initConfigSubscriber() {
|
||||
$this->configSubscriber->setPathProcessorLanguage($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the collected processors instances.
|
||||
*/
|
||||
public function reset() {
|
||||
$this->processors = array();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -39,7 +39,9 @@ class LanguageServiceProvider extends ServiceProviderBase {
|
|||
->addArgument(new Reference('config.factory'))
|
||||
->addArgument(new Reference('language_manager'))
|
||||
->addArgument(new Reference('language_negotiator'))
|
||||
->addArgument(new Reference('current_user'));
|
||||
->addArgument(new Reference('current_user'))
|
||||
->addArgument(new Reference('language.config_subscriber'))
|
||||
->addMethodCall('initConfigSubscriber');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,294 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity.
|
||||
*/
|
||||
|
||||
namespace Drupal\language\Plugin\LanguageNegotiation;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\Render\BubbleableMetadata;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\language\LanguageNegotiationMethodBase;
|
||||
use Drupal\language\LanguageSwitcherInterface;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Class for identifying the content translation language.
|
||||
*
|
||||
* @LanguageNegotiation(
|
||||
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::METHOD_ID,
|
||||
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
|
||||
* weight = -9,
|
||||
* name = @Translation("Content language"),
|
||||
* description = @Translation("Determines the content language from a request parameter."),
|
||||
* )
|
||||
*/
|
||||
class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The language negotiation method ID.
|
||||
*/
|
||||
const METHOD_ID = 'language-content-entity';
|
||||
|
||||
/**
|
||||
* The query string parameter.
|
||||
*/
|
||||
const QUERY_PARAMETER = 'language_content_entity';
|
||||
|
||||
/**
|
||||
* A list of all the link paths of enabled content entities.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $contentEntityPaths;
|
||||
|
||||
/**
|
||||
* Static cache for the language negotiation order check.
|
||||
*
|
||||
* @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::hasLowerLanguageNegotiationWeight()
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $hasLowerLanguageNegotiationWeightResult;
|
||||
|
||||
/**
|
||||
* Static cache of outbound route paths per request.
|
||||
*
|
||||
* @var \SplObjectStorage
|
||||
*/
|
||||
protected $paths;
|
||||
|
||||
/**
|
||||
* The entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Constructs a new LanguageNegotiationContentEntity instance.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
*/
|
||||
public function __construct(EntityManagerInterface $entity_manager) {
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->paths = new \SplObjectStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static($container->get('entity.manager'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLangcode(Request $request = NULL) {
|
||||
$langcode = $request->get(static::QUERY_PARAMETER);
|
||||
|
||||
$language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages());
|
||||
return $language_enabled ? $langcode : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
|
||||
// If appropriate, process outbound to add a query parameter to the url and
|
||||
// remove the language option, so that url negotiator does not rewrite the
|
||||
// url.
|
||||
|
||||
// First, check if processing conditions are met.
|
||||
if (!($request && !empty($options['route']) && $this->hasLowerLanguageNegotiationWeight() && $this->meetsContentEntityRoutesCondition($options['route'], $request))) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if (isset($options['language']) || $langcode = $this->getLangcode($request)) {
|
||||
// If the language option is set, unset it, so that the url language
|
||||
// negotiator does not rewrite the url.
|
||||
if (isset($options['language'])) {
|
||||
$langcode = $options['language']->getId();
|
||||
unset($options['language']);
|
||||
}
|
||||
|
||||
if (isset($options['query']) && is_string($options['query'])) {
|
||||
$query = [];
|
||||
parse_str($options['query'], $query);
|
||||
$options['query'] = $query;
|
||||
}
|
||||
else {
|
||||
$options['query'] = [];
|
||||
}
|
||||
|
||||
if (!isset($options['query'][static::QUERY_PARAMETER])) {
|
||||
$query_addon = [static::QUERY_PARAMETER => $langcode];
|
||||
$options['query'] += $query_addon;
|
||||
// @todo Remove this once https://www.drupal.org/node/2507005 lands.
|
||||
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon);
|
||||
}
|
||||
|
||||
if ($bubbleable_metadata) {
|
||||
// Cached URLs that have been processed by this outbound path
|
||||
// processor must be:
|
||||
$bubbleable_metadata
|
||||
// - varied by the content language query parameter.
|
||||
->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]);
|
||||
}
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
|
||||
$links = [];
|
||||
$query = [];
|
||||
parse_str($request->getQueryString(), $query);
|
||||
|
||||
foreach ($this->languageManager->getNativeLanguages() as $language) {
|
||||
$langcode = $language->getId();
|
||||
$query[static::QUERY_PARAMETER] = $langcode;
|
||||
$links[$langcode] = [
|
||||
'url' => $url,
|
||||
'title' => $language->getName(),
|
||||
'attributes' => ['class' => ['language-link']],
|
||||
'query' => $query,
|
||||
];
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if content entity language negotiator has higher priority.
|
||||
*
|
||||
* The content entity language negotiator having higher priority than the url
|
||||
* language negotiator, is a criteria in
|
||||
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::processOutbound().
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the the content entity language negotiator has higher priority
|
||||
* than the url language negotiator, FALSE otherwise.
|
||||
*/
|
||||
protected function hasLowerLanguageNegotiationWeight() {
|
||||
if (!isset($this->hasLowerLanguageNegotiationWeightResult)) {
|
||||
// Only run if the LanguageNegotiationContentEntity outbound function is
|
||||
// being executed before the outbound function of LanguageNegotiationUrl.
|
||||
$content_method_weights = $this->config->get('language.types')->get('negotiation.language_content.enabled') ?: [];
|
||||
|
||||
// Check if the content language is configured to be dependent on the
|
||||
// url negotiator directly or indirectly over the interface negotiator.
|
||||
if (isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) && ($content_method_weights[static::METHOD_ID] > $content_method_weights[LanguageNegotiationUrl::METHOD_ID])) {
|
||||
$this->hasLowerLanguageNegotiationWeightResult = FALSE;
|
||||
}
|
||||
else {
|
||||
$check_interface_method = FALSE;
|
||||
if (isset($content_method_weights[LanguageNegotiationUI::METHOD_ID])) {
|
||||
$interface_method_weights = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: [];
|
||||
$check_interface_method = isset($interface_method_weights[LanguageNegotiationUrl::METHOD_ID]);
|
||||
}
|
||||
if ($check_interface_method) {
|
||||
$max_weight = $content_method_weights[LanguageNegotiationUI::METHOD_ID];
|
||||
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? max($max_weight, $content_method_weights[LanguageNegotiationUrl::METHOD_ID]) : $max_weight;
|
||||
}
|
||||
else {
|
||||
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? $content_method_weights[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX;
|
||||
}
|
||||
|
||||
$this->hasLowerLanguageNegotiationWeightResult = $content_method_weights[static::METHOD_ID] < $max_weight;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->hasLowerLanguageNegotiationWeightResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if content entity route condition is met.
|
||||
*
|
||||
* Requirements: currently being on an content entity route and processing
|
||||
* outbound url pointing to the same content entity.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $outbound_route
|
||||
* The route object for the current outbound url being processed.
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the content entity route condition is met, FALSE otherwise.
|
||||
*/
|
||||
protected function meetsContentEntityRoutesCondition(Route $outbound_route, Request $request) {
|
||||
$outbound_path_pattern = $outbound_route->getPath();
|
||||
$storage = isset($this->paths[$request]) ? $this->paths[$request] : [];
|
||||
if (!isset($storage[$outbound_path_pattern])) {
|
||||
$storage[$outbound_path_pattern] = FALSE;
|
||||
|
||||
// Check if the outbound route points to the current entity.
|
||||
if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) {
|
||||
if (!empty($this->getContentEntityPaths()[$outbound_path_pattern]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_path_pattern]) {
|
||||
$storage[$outbound_path_pattern] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
$this->paths[$request] = $storage;
|
||||
}
|
||||
|
||||
return $storage[$outbound_path_pattern];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content entity type ID from the current request for the route.
|
||||
*
|
||||
* @param \Symfony\Component\HttpFoundation\Request $request
|
||||
* The HttpRequest object representing the current request.
|
||||
*
|
||||
* @return string
|
||||
* The entity type ID for the route from the request.
|
||||
*/
|
||||
protected function getContentEntityTypeIdForCurrentRequest(Request $request) {
|
||||
$content_entity_type_id_for_current_route = '';
|
||||
|
||||
if ($current_route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
|
||||
$current_route_path = $current_route->getPath();
|
||||
$content_entity_type_id_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : '';
|
||||
}
|
||||
|
||||
return $content_entity_type_id_for_current_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths for the link templates of all content entities.
|
||||
*
|
||||
* @return array
|
||||
* An array of all content entity type IDs, keyed by the corresponding link
|
||||
* template paths.
|
||||
*/
|
||||
protected function getContentEntityPaths() {
|
||||
if (!isset($this->contentEntityPaths)) {
|
||||
$this->contentEntityPaths = [];
|
||||
$entity_types = $this->entityManager->getDefinitions();
|
||||
foreach ($entity_types as $entity_type_id => $entity_type) {
|
||||
if ($entity_type->isSubclassOf(ContentEntityInterface::class)) {
|
||||
$entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id);
|
||||
$this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->contentEntityPaths;
|
||||
}
|
||||
|
||||
}
|
|
@ -7,22 +7,34 @@
|
|||
|
||||
namespace Drupal\language\Tests;
|
||||
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\simpletest\KernelTestBase;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests the language of entity URLs.
|
||||
* @group language
|
||||
*/
|
||||
class EntityUrlLanguageTest extends KernelTestBase {
|
||||
class EntityUrlLanguageTest extends LanguageTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = ['language', 'entity_test', 'user', 'system'];
|
||||
public static $modules = ['entity_test', 'user'];
|
||||
|
||||
/**
|
||||
* The entity being used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
@ -37,33 +49,93 @@ class EntityUrlLanguageTest extends KernelTestBase {
|
|||
ConfigurableLanguage::create(['id' => 'es'])->save();
|
||||
ConfigurableLanguage::create(['id' => 'fr'])->save();
|
||||
|
||||
$this->config('language.types')->setData([
|
||||
'configurable' => ['language_interface'],
|
||||
'negotiation' => ['language_interface' => ['enabled' => ['language-url' => 0]]],
|
||||
])->save();
|
||||
$this->config('language.negotiation')->setData([
|
||||
'url' => [
|
||||
'source' => 'path_prefix',
|
||||
'prefixes' => ['en' => 'en', 'es' => 'es', 'fr' => 'fr']
|
||||
],
|
||||
])->save();
|
||||
$config = $this->config('language.negotiation');
|
||||
$config->set('url.prefixes', ['en' => 'en', 'es' => 'es', 'fr' => 'fr'])
|
||||
->save();
|
||||
$this->kernel->rebuildContainer();
|
||||
$this->container = $this->kernel->getContainer();
|
||||
\Drupal::setContainer($this->container);
|
||||
|
||||
$this->createTranslatableEntity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that entity URLs in a language have the right language prefix.
|
||||
*/
|
||||
public function testEntityUrlLanguage() {
|
||||
$entity = EntityTest::create();
|
||||
$entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$entity->save();
|
||||
$this->assertTrue(strpos($this->entity->urlInfo()->toString(), '/en/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $this->entity->id()) !== FALSE);
|
||||
}
|
||||
|
||||
$this->assertTrue(strpos($entity->urlInfo()->toString(), '/en/entity_test/' . $entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $entity->id()) !== FALSE);
|
||||
$this->assertTrue(strpos($entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $entity->id()) !== FALSE);
|
||||
/**
|
||||
* Ensures correct entity URLs with the method language-content-entity enabled.
|
||||
*
|
||||
* Test case with the method language-content-entity enabled and configured
|
||||
* with higher and also with lower priority than the method language-url.
|
||||
*/
|
||||
public function testEntityUrlLanguageWithLanguageContentEnabled() {
|
||||
// Define the method language-content-entity with a higher priority than
|
||||
// language-url.
|
||||
$config = $this->config('language.types');
|
||||
$config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]);
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 0,
|
||||
LanguageNegotiationUrl::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// Without being on an content entity route the default entity URL tests
|
||||
// should still pass.
|
||||
$this->testEntityUrlLanguage();
|
||||
|
||||
// Now switching to an entity route, so that the URL links are generated
|
||||
// while being on an entity route.
|
||||
$this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical');
|
||||
|
||||
// The method language-content-entity should run before language-url and
|
||||
// append query parameter for the content language and prevent language-url
|
||||
// from overwriting the url.
|
||||
$this->assertTrue(strpos($this->entity->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=en') !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=es') !== FALSE);
|
||||
$this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=fr') !== FALSE);
|
||||
|
||||
// Define the method language-url with a higher priority than
|
||||
// language-content-entity. This configuration should match the default one,
|
||||
// where the language-content-entity is turned off.
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationUrl::METHOD_ID => 0,
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// The default entity URL tests should pass again with the current
|
||||
// configuration.
|
||||
$this->testEntityUrlLanguage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translated entity.
|
||||
*/
|
||||
protected function createTranslatableEntity() {
|
||||
$this->entity = EntityTest::create();
|
||||
$this->entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$this->entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current request to a specific path with the corresponding route.
|
||||
*
|
||||
* @param string $path
|
||||
* The path for which the current request should be created.
|
||||
* @param string $route_name
|
||||
* The route name for which the route object for the request should be
|
||||
* created.
|
||||
*/
|
||||
protected function setCurrentRequestForRoute($path, $route_name) {
|
||||
$request = Request::create($path);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path));
|
||||
$this->container->get('request_stack')->push($request);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\language\Tests\LanguageNegotiationLanguageContentEntityTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\language\Tests;
|
||||
|
||||
use Drupal\Component\Render\FormattableMarkup;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\entity_test\Entity\EntityTest;
|
||||
use Drupal\language\Entity\ConfigurableLanguage;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity;
|
||||
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
|
||||
use Drupal\simpletest\WebTestBase;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Tests language negotiation with the language negotiator content entity.
|
||||
*
|
||||
* @group language
|
||||
*/
|
||||
class LanguageNegotiationContentEntityTest extends WebTestBase {
|
||||
|
||||
/**
|
||||
* Modules to enable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public static $modules = array('language', 'language_test', 'entity_test', 'system');
|
||||
|
||||
/**
|
||||
* The entity being used for testing.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\ContentEntityInterface
|
||||
*/
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
ConfigurableLanguage::create(['id' => 'es'])->save();
|
||||
ConfigurableLanguage::create(['id' => 'fr'])->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
$this->createTranslatableEntity();
|
||||
|
||||
$user = $this->drupalCreateUser(array('view test entity'));
|
||||
$this->drupalLogin($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests default with content language remaining same as interface language.
|
||||
*/
|
||||
public function testDefaultConfiguration() {
|
||||
$translation = $this->entity;
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
|
||||
$translation = $this->entity->getTranslation('es');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
|
||||
$translation = $this->entity->getTranslation('fr');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), new FormattableMarkup('Interface language %interface_language and Content language %content_language are the same as the translation language %translation_language of the entity.', ['%interface_language' => $last_interface_language, '%content_language' => $last_content_language, '%translation_language' => $translation->language()->getId()]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests enabling the language negotiator language_content_entity.
|
||||
*/
|
||||
public function testEnabledLanguageContentNegotiator() {
|
||||
// Define the method language-url with a higher priority than
|
||||
// language-content-entity. This configuration should match the default one,
|
||||
// where the language-content-entity is turned off.
|
||||
$config = $this->config('language.types');
|
||||
$config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]);
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationUrl::METHOD_ID => 0,
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// The tests for the default configuration should still pass.
|
||||
$this->testDefaultConfiguration();
|
||||
|
||||
// Define the method language-content-entity with a higher priority than
|
||||
// language-url.
|
||||
$config->set('negotiation.language_content.enabled', [
|
||||
LanguageNegotiationContentEntity::METHOD_ID => 0,
|
||||
LanguageNegotiationUrl::METHOD_ID => 1
|
||||
]);
|
||||
$config->save();
|
||||
|
||||
// In order to reflect the changes for a multilingual site in the container
|
||||
// we have to rebuild it.
|
||||
$this->rebuildContainer();
|
||||
|
||||
// The method language-content-entity should run before language-url and
|
||||
// append query parameter for the content language and prevent language-url
|
||||
// from overwriting the URL.
|
||||
$default_site_langcode = $this->config('system.site')->get('default_langcode');
|
||||
|
||||
// Now switching to an entity route, so that the URL links are generated
|
||||
// while being on an entity route.
|
||||
$this->setCurrentRequestForRoute('/entity_test/{entity_test}', 'entity.entity_test.canonical');
|
||||
|
||||
$translation = $this->entity;
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue(($last_interface_language == $default_site_langcode) && ($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the default translation language of the entity.');
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
|
||||
$translation = $this->entity->getTranslation('es');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
|
||||
$translation = $this->entity->getTranslation('fr');
|
||||
$this->drupalGet($translation->urlInfo());
|
||||
$last = $this->container->get('state')->get('language_test.language_negotiation_last');
|
||||
$last_content_language = $last[LanguageInterface::TYPE_CONTENT];
|
||||
$last_interface_language = $last[LanguageInterface::TYPE_INTERFACE];
|
||||
$this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.');
|
||||
$this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a translated entity.
|
||||
*/
|
||||
protected function createTranslatableEntity() {
|
||||
$this->entity = EntityTest::create();
|
||||
$this->entity->addTranslation('es', ['name' => 'name spanish']);
|
||||
$this->entity->addTranslation('fr', ['name' => 'name french']);
|
||||
$this->entity->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current request to a specific path with the corresponding route.
|
||||
*
|
||||
* @param string $path
|
||||
* The path for which the current request should be created.
|
||||
* @param string $route_name
|
||||
* The route name for which the route object for the request should be
|
||||
* created.
|
||||
*/
|
||||
protected function setCurrentRequestForRoute($path, $route_name) {
|
||||
$request = Request::create($path);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name);
|
||||
$request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path));
|
||||
$this->container->get('request_stack')->push($request);
|
||||
}
|
||||
|
||||
}
|
|
@ -84,7 +84,7 @@ class LanguageSelectorTranslatableTest extends WebTestBase {
|
|||
$this->drupalGet($path);
|
||||
|
||||
// Get en language from selector.
|
||||
$elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => 'edit-settings-node-node-settings-language-langcode', ':option' => 'en'));
|
||||
$elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => 'edit-settings-user-user-settings-language-langcode', ':option' => 'en'));
|
||||
|
||||
// Check that the language text is translated.
|
||||
$this->assertEqual((string) $elements[0], $name_translation, 'Checking the option string English is translated to Spanish.');
|
||||
|
|
|
@ -221,8 +221,13 @@ class LanguageUILanguageNegotiationTest extends WebTestBase {
|
|||
|
||||
// Unknown language prefix should return 404.
|
||||
$definitions = \Drupal::languageManager()->getNegotiator()->getNegotiationMethods();
|
||||
// Enable only methods, which are either not limited to a specific language
|
||||
// type or are supporting the interface language type.
|
||||
$language_interface_method_definitions = array_filter($definitions, function ($method_definition) {
|
||||
return !isset($method_definition['types']) || (isset($method_definition['types']) && in_array(LanguageInterface::TYPE_INTERFACE, $method_definition['types']));
|
||||
});
|
||||
$this->config('language.types')
|
||||
->set('negotiation.' . LanguageInterface::TYPE_INTERFACE . '.enabled', array_flip(array_keys($definitions)))
|
||||
->set('negotiation.' . LanguageInterface::TYPE_INTERFACE . '.enabled', array_flip(array_keys($language_interface_method_definitions)))
|
||||
->save();
|
||||
$this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback);
|
||||
$this->assertResponse(404, "Unknown language path prefix should return 404");
|
||||
|
|
|
@ -1,24 +1,25 @@
|
|||
{#
|
||||
/**
|
||||
* @file
|
||||
* Default theme implementation for a language negotiation configuration form.
|
||||
*
|
||||
* Available variables:
|
||||
* - language_types: A list of language negotiation types. Each language type
|
||||
* contains the following:
|
||||
* - type: The machine name for the negotiation type.
|
||||
* - title: The language negotiation type name.
|
||||
* - description: A description for how the language negotiation type operates.
|
||||
* - configurable: A radio element to toggle the table.
|
||||
* - table: A draggable table for the language detection methods of this type.
|
||||
* - children: Remaining form items for the group.
|
||||
* - attributes: A list of HTML attributes for the wrapper element.
|
||||
* - children: Remaining form items for all groups.
|
||||
*
|
||||
* @see template_preprocess_language_negotiation_configure_form()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
* @file
|
||||
* Default theme implementation for a language negotiation configuration form.
|
||||
*
|
||||
* Available variables:
|
||||
* - language_types: A list of language negotiation types. Each language type
|
||||
* contains the following:
|
||||
* - type: The machine name for the negotiation type.
|
||||
* - title: The language negotiation type name.
|
||||
* - description: A description for how the language negotiation type
|
||||
* operates.
|
||||
* - configurable: A radio element to toggle the table.
|
||||
* - table: A draggable table for the language detection methods of this type.
|
||||
* - children: Remaining form items for the group.
|
||||
* - attributes: A list of HTML attributes for the wrapper element.
|
||||
* - children: Remaining form items for all groups.
|
||||
*
|
||||
* @see template_preprocess_language_negotiation_configure_form()
|
||||
*
|
||||
* @ingroup themeable
|
||||
*/
|
||||
#}
|
||||
{% for language_type in language_types %}
|
||||
{%
|
||||
|
|
|
@ -121,25 +121,30 @@ class LinkItem extends FieldItemBase implements LinkItemInterface {
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public static function generateSampleValue(FieldDefinitionInterface $field_definition) {
|
||||
// Set of possible top-level domains.
|
||||
$tlds = array('com', 'net', 'gov', 'org', 'edu', 'biz', 'info');
|
||||
// Set random length for the domain name.
|
||||
$domain_length = mt_rand(7, 15);
|
||||
$random = new Random();
|
||||
if ($field_definition->getItemDefinition()->getSetting('link_type') & LinkItemInterface::LINK_EXTERNAL) {
|
||||
// Set of possible top-level domains.
|
||||
$tlds = array('com', 'net', 'gov', 'org', 'edu', 'biz', 'info');
|
||||
// Set random length for the domain name.
|
||||
$domain_length = mt_rand(7, 15);
|
||||
|
||||
switch ($field_definition->getSetting('title')) {
|
||||
case DRUPAL_DISABLED:
|
||||
$values['title'] = '';
|
||||
break;
|
||||
case DRUPAL_REQUIRED:
|
||||
$values['title'] = $random->sentences(4);
|
||||
break;
|
||||
case DRUPAL_OPTIONAL:
|
||||
// In case of optional title, randomize its generation.
|
||||
$values['title'] = mt_rand(0,1) ? $random->sentences(4) : '';
|
||||
break;
|
||||
switch ($field_definition->getSetting('title')) {
|
||||
case DRUPAL_DISABLED:
|
||||
$values['title'] = '';
|
||||
break;
|
||||
case DRUPAL_REQUIRED:
|
||||
$values['title'] = $random->sentences(4);
|
||||
break;
|
||||
case DRUPAL_OPTIONAL:
|
||||
// In case of optional title, randomize its generation.
|
||||
$values['title'] = mt_rand(0, 1) ? $random->sentences(4) : '';
|
||||
break;
|
||||
}
|
||||
$values['uri'] = 'http://www.' . $random->word($domain_length) . '.' . $tlds[mt_rand(0, (sizeof($tlds) - 1))];
|
||||
}
|
||||
else {
|
||||
$values['uri'] = 'base:' . $random->name(mt_rand(1, 64));
|
||||
}
|
||||
$values['uri'] = 'http://www.' . $random->word($domain_length) . '.' . $tlds[mt_rand(0, (sizeof($tlds)-1))];
|
||||
return $values;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace Drupal\link\Plugin\Validation\Constraint;
|
||||
|
||||
use Symfony\Component\Routing\Exception\InvalidParameterException;
|
||||
use Symfony\Component\Routing\Exception\MissingMandatoryParametersException;
|
||||
use Symfony\Component\Routing\Exception\RouteNotFoundException;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidatorInterface;
|
||||
|
@ -50,9 +52,17 @@ class LinkNotExistingInternalConstraintValidator implements ConstraintValidatorI
|
|||
try {
|
||||
$url->toString();
|
||||
}
|
||||
// The following exceptions are all possible during URL generation, and
|
||||
// should be considered as disallowed URLs.
|
||||
catch (RouteNotFoundException $e) {
|
||||
$allowed = FALSE;
|
||||
}
|
||||
catch (InvalidParameterException $e) {
|
||||
$allowed = FALSE;
|
||||
}
|
||||
catch (MissingMandatoryParametersException $e) {
|
||||
$allowed = FALSE;
|
||||
}
|
||||
if (!$allowed) {
|
||||
$this->context->addViolation($constraint->message, array('@uri' => $value->uri));
|
||||
}
|
||||
|
|
|
@ -129,8 +129,6 @@ class LinkFieldTest extends WebTestBase {
|
|||
'entity:user/1' => '- Restricted access - (1)',
|
||||
// URI for an entity that doesn't exist, but with a valid ID.
|
||||
'entity:user/999999' => 'entity:user/999999',
|
||||
// URI for an entity that doesn't exist, with an invalid ID.
|
||||
'entity:user/invalid-parameter' => 'entity:user/invalid-parameter',
|
||||
);
|
||||
|
||||
// Define some invalid URLs.
|
||||
|
@ -146,6 +144,8 @@ class LinkFieldTest extends WebTestBase {
|
|||
$invalid_internal_entries = array(
|
||||
'no-leading-slash' => $validation_error_2,
|
||||
'entity:non_existing_entity_type/yar' => $validation_error_1,
|
||||
// URI for an entity that doesn't exist, with an invalid ID.
|
||||
'entity:user/invalid-parameter' => $validation_error_1,
|
||||
);
|
||||
|
||||
// Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
|
||||
|
|
|
@ -10,7 +10,10 @@ namespace Drupal\link\Tests;
|
|||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Field\FieldItemListInterface;
|
||||
use Drupal\Core\Field\FieldItemInterface;
|
||||
use Drupal\field\Entity\FieldConfig;
|
||||
use Drupal\field\Entity\FieldStorageConfig;
|
||||
use Drupal\field\Tests\FieldUnitTestBase;
|
||||
use Drupal\link\LinkItemInterface;
|
||||
|
||||
/**
|
||||
* Tests the new entity API for the link field type.
|
||||
|
@ -29,17 +32,40 @@ class LinkItemTest extends FieldUnitTestBase {
|
|||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a link field for validation.
|
||||
entity_create('field_storage_config', array(
|
||||
// Create a generic, external, and internal link fields for validation.
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_test',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'link',
|
||||
))->save();
|
||||
entity_create('field_config', array(
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_test',
|
||||
'bundle' => 'entity_test',
|
||||
))->save();
|
||||
'settings' => ['link_type' => LinkItemInterface::LINK_GENERIC],
|
||||
])->save();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_test_external',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'link',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_test_external',
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => ['link_type' => LinkItemInterface::LINK_EXTERNAL],
|
||||
])->save();
|
||||
FieldStorageConfig::create([
|
||||
'field_name' => 'field_test_internal',
|
||||
'entity_type' => 'entity_test',
|
||||
'type' => 'link',
|
||||
])->save();
|
||||
FieldConfig::create([
|
||||
'entity_type' => 'entity_test',
|
||||
'field_name' => 'field_test_internal',
|
||||
'bundle' => 'entity_test',
|
||||
'settings' => ['link_type' => LinkItemInterface::LINK_INTERNAL],
|
||||
])->save();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,9 +156,12 @@ class LinkItemTest extends FieldUnitTestBase {
|
|||
$entity->field_test[0] = NULL;
|
||||
$this->assertNull($entity->field_test[0]->getValue());
|
||||
|
||||
// Test the generateSampleValue() method.
|
||||
// Test the generateSampleValue() method for generic, external, and internal
|
||||
// link types.
|
||||
$entity = entity_create('entity_test');
|
||||
$entity->field_test->generateSampleItems();
|
||||
$entity->field_test_external->generateSampleItems();
|
||||
$entity->field_test_internal->generateSampleItems();
|
||||
$this->entityValidateAndSave($entity);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,10 @@ class LocaleConfigTranslationImportTest extends WebTestBase {
|
|||
$this->container->get('module_installer')->install(['block', 'config_translation']);
|
||||
$this->resetAll();
|
||||
|
||||
// The testing profile overrides locale.settings to disable translation
|
||||
// import. Test that this override is in place.
|
||||
$this->assertFalse($this->config('locale.settings')->get('translation.import_enabled'), 'Translations imports are disabled by default in the Testing profile.');
|
||||
|
||||
$admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'administer permissions', 'translate configuration'));
|
||||
$this->drupalLogin($admin_user);
|
||||
|
||||
|
|
|
@ -144,6 +144,66 @@ class LocalePluralFormatTest extends WebTestBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests plural editing of DateFormatter strings
|
||||
*/
|
||||
public function testPluralEditDateFormatter() {
|
||||
|
||||
// Import some .po files with formulas to set up the environment.
|
||||
// These will also add the languages to the system.
|
||||
$this->importPoFile($this->getPoFileWithSimplePlural(), array(
|
||||
'langcode' => 'fr',
|
||||
));
|
||||
|
||||
// Set French as the site default language.
|
||||
$this->config('system.site')->set('default_langcode', 'fr')->save();
|
||||
|
||||
// Visit User Info page before updating translation strings.
|
||||
$this->drupalGet('user');
|
||||
|
||||
// Member for time should be translated.
|
||||
$this->assertText("seconde", "'Member for' text is translated.");
|
||||
|
||||
$path = 'admin/config/regional/translate/';
|
||||
$search = array(
|
||||
'langcode' => 'fr',
|
||||
// Limit to only translated strings to ensure that database ordering does
|
||||
// not break the test.
|
||||
'translation' => 'translated',
|
||||
);
|
||||
$this->drupalPostForm($path, $search, t('Filter'));
|
||||
// Plural values for the langcode fr.
|
||||
$this->assertText('@count seconde');
|
||||
$this->assertText('@count secondes');
|
||||
|
||||
// Inject a plural source string to the database. We need to use a specific
|
||||
// langcode here because the language will be English by default and will
|
||||
// not save our source string for performance optimization if we do not ask
|
||||
// specifically for a language.
|
||||
\Drupal::translation()->formatPlural(1, '1 second', '@count seconds', array(), array('langcode' => 'fr'))->render();
|
||||
$lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = ''", array(':source' => "1 second" . LOCALE_PLURAL_DELIMITER . "@count seconds"))->fetchField();
|
||||
// Look up editing page for this plural string and check fields.
|
||||
$search = array(
|
||||
'string' => '1 second',
|
||||
'langcode' => 'fr',
|
||||
);
|
||||
$this->drupalPostForm('admin/config/regional/translate', $search, t('Filter'));
|
||||
|
||||
// Save complete translations for the string in langcode fr.
|
||||
$edit = array(
|
||||
"strings[$lid][translations][0]" => '1 seconde updated',
|
||||
"strings[$lid][translations][1]" => '@count secondes updated',
|
||||
);
|
||||
$this->drupalPostForm($path, $edit, t('Save translations'));
|
||||
|
||||
// User interface input for translating seconds should not be duplicated
|
||||
$this->assertUniqueText('@count seconds', 'Interface translation input for @count seconds only appears once.');
|
||||
|
||||
// Member for time should be translated.
|
||||
$this->drupalGet('user');
|
||||
$this->assertText("seconde", "'Member for' text is translated.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests plural editing and export functionality.
|
||||
*/
|
||||
|
@ -308,6 +368,11 @@ msgid_plural "@count hours"
|
|||
msgstr[0] "@count heure"
|
||||
msgstr[1] "@count heures"
|
||||
|
||||
msgid "1 second"
|
||||
msgid_plural "@count seconds"
|
||||
msgstr[0] "@count seconde"
|
||||
msgstr[1] "@count secondes"
|
||||
|
||||
msgid "Monday"
|
||||
msgstr "lundi"
|
||||
EOF;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link_content\Tests\Migrate\MigrateMenuLinkContentStubTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link_content\Tests\Migrate;
|
||||
|
||||
use Drupal\migrate_drupal\Tests\MigrateDrupalTestBase;
|
||||
use Drupal\migrate_drupal\Tests\StubTestTrait;
|
||||
|
||||
/**
|
||||
* Test stub creation for menu link content entities.
|
||||
*
|
||||
* @group menu_link_content
|
||||
*/
|
||||
class MigrateMenuLinkContentStubTest extends MigrateDrupalTestBase {
|
||||
|
||||
use StubTestTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['menu_link_content', 'link'];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->installEntitySchema('menu_link_content');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests creation of menu link content stubs.
|
||||
*/
|
||||
public function testStub() {
|
||||
$this->performStubTest('menu_link_content');
|
||||
}
|
||||
|
||||
}
|
|
@ -401,8 +401,8 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
$multiple = $multiple || $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// No plugins means do not set.
|
||||
if ($plugins) {
|
||||
// No plugins or no value means do not set.
|
||||
if ($plugins && !is_null($value)) {
|
||||
$row->setDestinationProperty($destination, $value);
|
||||
}
|
||||
// Reset the value.
|
||||
|
|
|
@ -44,10 +44,14 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
|||
public function __toString();
|
||||
|
||||
/**
|
||||
* Get the source ids.
|
||||
* Defines the source fields uniquely identifying a source row. None of these
|
||||
* fields should contain a NULL value - if necessary, use prepareRow() or
|
||||
* hook_migrate_prepare_row() to rewrite NULL values to appropriate empty
|
||||
* values (such as '' or 0).
|
||||
*
|
||||
* @return array
|
||||
* The source ids.
|
||||
* Array keyed by source field name, with values being a schema array
|
||||
* describing the field (such as ['type' => 'string]).
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
|
|
|
@ -136,19 +136,6 @@ abstract class Entity extends DestinationBase implements ContainerFactoryPluginI
|
|||
return $row->getDestinationProperty($this->getKey('id'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the stub values.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
|
||||
$row->setDestinationProperty($bundle_key, reset($this->bundles));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific entity key.
|
||||
*
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\Random;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\Core\TypedData\TypedDataInterface;
|
||||
use Drupal\link\LinkItemInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
@ -29,6 +34,13 @@ class EntityContentBase extends Entity {
|
|||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* Field type plugin manager.
|
||||
*
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
/**
|
||||
* Constructs a content entity.
|
||||
*
|
||||
|
@ -46,10 +58,13 @@ class EntityContentBase extends Entity {
|
|||
* The list of bundles this entity type has.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager service.
|
||||
* @param \Drupal\Core\Field\FieldTypePluginManagerInterface $field_type_manager
|
||||
* The field type plugin manager service.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, FieldTypePluginManagerInterface $field_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->entityManager = $entity_manager;
|
||||
$this->fieldTypeManager = $field_type_manager;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +79,8 @@ class EntityContentBase extends Entity {
|
|||
$migration,
|
||||
$container->get('entity.manager')->getStorage($entity_type),
|
||||
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
|
||||
$container->get('entity.manager')
|
||||
$container->get('entity.manager'),
|
||||
$container->get('plugin.manager.field.field_type')
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -74,6 +90,9 @@ class EntityContentBase extends Entity {
|
|||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
if (!$entity) {
|
||||
throw new MigrateException('Unable to get entity');
|
||||
}
|
||||
return $this->save($entity, $old_destination_id_values);
|
||||
}
|
||||
|
||||
|
@ -132,4 +151,47 @@ class EntityContentBase extends Entity {
|
|||
$this->setRollbackAction($row->getIdMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Do as much population of the stub row as we can.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row of data.
|
||||
*/
|
||||
protected function processStubRow(Row $row) {
|
||||
$bundle_key = $this->getKey('bundle');
|
||||
if ($bundle_key && empty($row->getDestinationProperty($bundle_key))) {
|
||||
if (empty($this->bundles)) {
|
||||
throw new MigrateException('Stubbing failed, no bundles available for entity type: ' . $this->storage->getEntityTypeId());
|
||||
}
|
||||
$row->setDestinationProperty($bundle_key, reset($this->bundles));
|
||||
}
|
||||
|
||||
// Populate any required fields not already populated.
|
||||
$fields = $this->entityManager
|
||||
->getFieldDefinitions($this->storage->getEntityTypeId(), $bundle_key);
|
||||
foreach ($fields as $field_name => $field_definition) {
|
||||
if ($field_definition->isRequired() && is_null($row->getDestinationProperty($field_name))) {
|
||||
// Use the configured default value for this specific field, if any.
|
||||
if ($default_value = $field_definition->getDefaultValueLiteral()) {
|
||||
$values[] = $default_value;
|
||||
}
|
||||
else {
|
||||
// Otherwise, ask the field type to generate a sample value.
|
||||
$field_type = $field_definition->getType();
|
||||
/** @var \Drupal\Core\Field\FieldItemInterface $field_type_class */
|
||||
$field_type_class = $this->fieldTypeManager
|
||||
->getPluginClass($field_definition->getType());
|
||||
$values = $field_type_class::generateSampleValue($field_definition);
|
||||
if (is_null($values)) {
|
||||
// Handle failure to generate a sample value.
|
||||
throw new MigrateException('Stubbing failed, unable to generate value for field ' . $field_name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$row->setDestinationProperty($field_name, $values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -287,6 +287,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
|||
foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) {
|
||||
$mapkey = 'sourceid' . $count++;
|
||||
$source_id_schema[$mapkey] = $this->getFieldSchema($id_definition);
|
||||
$source_id_schema[$mapkey]['not null'] = TRUE;
|
||||
|
||||
// With InnoDB, utf8mb4-based primary keys can't be over 191 characters.
|
||||
// Use ASCII-based primary keys instead.
|
||||
|
|
|
@ -136,6 +136,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
|||
$this->skipCount = !empty($configuration['skip_count']);
|
||||
$this->cacheKey = !empty($configuration['cache_key']) ? !empty($configuration['cache_key']) : NULL;
|
||||
$this->trackChanges = !empty($configuration['track_changes']) ? $configuration['track_changes'] : FALSE;
|
||||
$this->idMap = $this->migration->getIdMap();
|
||||
|
||||
// Pull out the current highwater mark if we have a highwater property.
|
||||
if ($this->highWaterProperty = $this->migration->get('highWaterProperty')) {
|
||||
|
@ -256,7 +257,6 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
|||
* source records.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->idMap = $this->migration->getIdMap();
|
||||
$this->getIterator()->rewind();
|
||||
$this->next();
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ class Row {
|
|||
*
|
||||
* @see getRawDestination()
|
||||
*/
|
||||
protected $rawDestination;
|
||||
protected $rawDestination = [];
|
||||
|
||||
/**
|
||||
* TRUE when this row is a stub.
|
||||
|
@ -222,6 +222,17 @@ class Row {
|
|||
NestedArray::setValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property), $value, TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes destination property.
|
||||
*
|
||||
* @param string $property
|
||||
* The name of the destination property.
|
||||
*/
|
||||
public function removeDestinationProperty($property) {
|
||||
unset($this->rawDestination[$property]);
|
||||
NestedArray::unsetValue($this->destination, explode(static::PROPERTY_SEPARATOR, $property));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the whole destination array.
|
||||
*
|
||||
|
|
|
@ -9,8 +9,12 @@ namespace Drupal\Tests\migrate\Unit;
|
|||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
|
||||
|
@ -128,6 +132,7 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
}
|
||||
|
||||
/**
|
||||
* @covers ::__construct
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function testHighwaterTrackChangesIncompatible() {
|
||||
|
@ -138,6 +143,8 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
|
||||
/**
|
||||
* Test that the source count is correct.
|
||||
*
|
||||
* @covers ::count
|
||||
*/
|
||||
public function testCount() {
|
||||
// Mock the cache to validate set() receives appropriate arguments.
|
||||
|
@ -221,6 +228,144 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic row preparation.
|
||||
*
|
||||
* @covers ::prepareRow
|
||||
*/
|
||||
public function testPrepareRow() {
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
|
||||
// Get a new migration with an id.
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourcePlugin([], '', [], $migration);
|
||||
$row = new Row([], []);
|
||||
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$source->setModuleHandler($module_handler->reveal());
|
||||
|
||||
// Ensure we don't log this to the mapping table.
|
||||
$this->idMap->expects($this->never())
|
||||
->method('saveIdMapping');
|
||||
|
||||
$this->assertTrue($source->prepareRow($row));
|
||||
|
||||
// Track_changes...
|
||||
$source = new StubSourcePlugin(['track_changes' => TRUE], '', [], $migration);
|
||||
$row2 = $this->prophesize(Row::class);
|
||||
$row2->rehash()
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_prepare_row', [$row2, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row2, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$source->setModuleHandler($module_handler->reveal());
|
||||
$this->assertTrue($source->prepareRow($row2->reveal()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that global prepare hooks can skip rows.
|
||||
*
|
||||
* @covers ::prepareRow
|
||||
*/
|
||||
public function testPrepareRowGlobalPrepareSkip() {
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourcePlugin([], '', [], $migration);
|
||||
$row = new Row([], []);
|
||||
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
// Return a failure from a prepare row hook.
|
||||
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, FALSE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$source->setModuleHandler($module_handler->reveal());
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
|
||||
$this->assertFalse($source->prepareRow($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that migrate specific prepare hooks can skip rows.
|
||||
*
|
||||
* @covers ::prepareRow
|
||||
*/
|
||||
public function testPrepareRowMigratePrepareSkip() {
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourcePlugin([], '', [], $migration);
|
||||
$row = new Row([], []);
|
||||
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
// Return a failure from a prepare row hook.
|
||||
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, FALSE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$source->setModuleHandler($module_handler->reveal());
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
|
||||
$this->assertFalse($source->prepareRow($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a skip exception during prepare hooks correctly skips.
|
||||
*
|
||||
* @covers ::prepareRow
|
||||
*/
|
||||
public function testPrepareRowPrepareException() {
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourcePlugin([], '', [], $migration);
|
||||
$row = new Row([], []);
|
||||
|
||||
$module_handler = $this->prophesize(ModuleHandlerInterface::class);
|
||||
// Return a failure from a prepare row hook.
|
||||
$module_handler->invokeAll('migrate_prepare_row', [$row, $source, $migration])
|
||||
->willReturn([TRUE, TRUE])
|
||||
->shouldBeCalled();
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
|
||||
->willThrow(new MigrateSkipRowException())
|
||||
->shouldBeCalled();
|
||||
$source->setModuleHandler($module_handler->reveal());
|
||||
|
||||
// This will only be called on the first prepare because the second
|
||||
// explicitly avoids it.
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
->with($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
$this->assertFalse($source->prepareRow($row));
|
||||
|
||||
// Throw an exception the second time that avoids mapping.
|
||||
$e = new MigrateSkipRowException('', FALSE);
|
||||
$module_handler->invokeAll('migrate_' . $migration->id() . '_prepare_row', [$row, $source, $migration])
|
||||
->willThrow($e)
|
||||
->shouldBeCalled();
|
||||
$this->assertFalse($source->prepareRow($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mock executable for the test.
|
||||
*
|
||||
|
@ -239,3 +384,46 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubbed source plugin for testing base class implementations.
|
||||
*/
|
||||
class StubSourcePlugin extends SourcePluginBase {
|
||||
|
||||
/**
|
||||
* Helper for setting internal module handler implementation.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
*/
|
||||
function setModuleHandler(ModuleHandlerInterface $module_handler) {
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __toString() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,11 @@ abstract class MigrateTestCase extends UnitTestCase {
|
|||
|
||||
protected $migrationConfiguration = [];
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $idMap;
|
||||
|
||||
/**
|
||||
* Local store for mocking setStatus()/getStatus().
|
||||
*
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\Plugin\migrate\destination\EntityContentBaseTest
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Core\Entity\ContentEntityInterface;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Field\FieldTypePluginManagerInterface;
|
||||
use Drupal\migrate\Entity\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\destination\EntityContentBase;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* Tests base entity migration destination functionality.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\destination\EntityContentBase
|
||||
* @group migrate
|
||||
*/
|
||||
class EntityContentBaseTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* @var \Drupal\migrate\Entity\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->migration = $this->prophesize(MigrationInterface::class);
|
||||
$this->storage = $this->prophesize(EntityStorageInterface::class);
|
||||
$this->entityManager = $this->prophesize(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test basic entity save.
|
||||
*
|
||||
* @covers ::import
|
||||
*/
|
||||
public function testImport() {
|
||||
$bundles = [];
|
||||
$destination = new EntityTestDestination([], '', [],
|
||||
$this->migration->reveal(),
|
||||
$this->storage->reveal(),
|
||||
$bundles,
|
||||
$this->entityManager->reveal(),
|
||||
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal());
|
||||
$entity = $this->prophesize(ContentEntityInterface::class);
|
||||
// Assert that save is called.
|
||||
$entity->save()
|
||||
->shouldBeCalledTimes(1);
|
||||
// Set an id for the entity
|
||||
$entity->id()
|
||||
->willReturn(5);
|
||||
$destination->setEntity($entity->reveal());
|
||||
// Ensure the id is saved entity id is returned from import.
|
||||
$this->assertEquals([5], $destination->import(new Row([], [])));
|
||||
// Assert that import set the rollback action.
|
||||
$this->assertEquals(MigrateIdMapInterface::ROLLBACK_DELETE, $destination->rollbackAction());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test row skipping when we can't get an entity to save.
|
||||
*
|
||||
* @covers ::import
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
* @expectedExceptionMessage Unable to get entity
|
||||
*/
|
||||
public function testImportEntityLoadFailure() {
|
||||
$bundles = [];
|
||||
$destination = new EntityTestDestination([], '', [],
|
||||
$this->migration->reveal(),
|
||||
$this->storage->reveal(),
|
||||
$bundles,
|
||||
$this->entityManager->reveal(),
|
||||
$this->prophesize(FieldTypePluginManagerInterface::class)->reveal());
|
||||
$destination->setEntity(FALSE);
|
||||
$destination->import(new Row([], []));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub class for testing EntityContentBase methods.
|
||||
*
|
||||
* We want to test things without testing the base class implementations.
|
||||
*/
|
||||
class EntityTestDestination extends EntityContentBase {
|
||||
|
||||
private $entity = NULL;
|
||||
|
||||
public function setEntity($entity) {
|
||||
$this->entity = $entity;
|
||||
}
|
||||
|
||||
protected function getEntity(Row $row, array $old_destination_id_values) {
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
|
@ -36,6 +36,11 @@ class EntityRevisionTest extends UnitTestCase {
|
|||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\Field\FieldTypePluginManagerInterface
|
||||
*/
|
||||
protected $fieldTypeManager;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
|
@ -43,6 +48,7 @@ class EntityRevisionTest extends UnitTestCase {
|
|||
$this->migration = $this->prophesize('\Drupal\migrate\Entity\MigrationInterface');
|
||||
$this->storage = $this->prophesize('\Drupal\Core\Entity\EntityStorageInterface');
|
||||
$this->entityManager = $this->prophesize('\Drupal\Core\Entity\EntityManagerInterface');
|
||||
$this->fieldTypeManager = $this->prophesize('\Drupal\Core\Field\FieldTypePluginManagerInterface');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -183,7 +189,9 @@ class EntityRevisionTest extends UnitTestCase {
|
|||
$this->migration->reveal(),
|
||||
$this->storage->reveal(),
|
||||
[],
|
||||
$this->entityManager->reveal());
|
||||
$this->entityManager->reveal(),
|
||||
$this->fieldTypeManager->reveal()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
80
core/modules/migrate_drupal/src/Tests/StubTestTrait.php
Normal file
80
core/modules/migrate_drupal/src/Tests/StubTestTrait.php
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\migrate_drupal\Tests\StubTestTrait.
|
||||
*/
|
||||
|
||||
namespace Drupal\migrate_drupal\Tests;
|
||||
use Drupal\migrate\Entity\Migration;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Provides common functionality for testing stubbing.
|
||||
*/
|
||||
trait StubTestTrait {
|
||||
|
||||
/**
|
||||
* Test that creating a stub of the given entity type results in a valid
|
||||
* entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type we are stubbing.
|
||||
*/
|
||||
protected function performStubTest($entity_type_id) {
|
||||
$entity_id = $this->createStub($entity_type_id);
|
||||
$this->assertTrue($entity_id, 'Stub successfully created');
|
||||
if ($entity_id) {
|
||||
$violations = $this->validateStub($entity_type_id, $entity_id);
|
||||
if (!$this->assertIdentical(count($violations), 0, 'Stub is a valid entity')) {
|
||||
foreach ($violations as $violation) {
|
||||
$this->fail((string) $violation->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub of the given entity type.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type we are stubbing.
|
||||
*
|
||||
* @return int
|
||||
* ID of the created entity.
|
||||
*/
|
||||
protected function createStub($entity_type_id) {
|
||||
// Create a dummy migration to pass to the destination plugin.
|
||||
$config = [
|
||||
'id' => 'dummy',
|
||||
'migration_tags' => ['Stub test'],
|
||||
'source' => ['plugin' => 'empty'],
|
||||
'process' => [],
|
||||
'destination' => ['plugin' => 'entity:' . $entity_type_id],
|
||||
];
|
||||
$migration = Migration::create($config);
|
||||
$destination_plugin = $migration->getDestinationPlugin(TRUE);
|
||||
$stub_row = new Row([], [], TRUE);
|
||||
$destination_ids = $destination_plugin->import($stub_row);
|
||||
return reset($destination_ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform validation on a stub entity.
|
||||
*
|
||||
* @param string $entity_type_id
|
||||
* The entity type we are stubbing.
|
||||
* @param string $entity_id
|
||||
* ID of the stubbed entity to validate.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityConstraintViolationListInterface
|
||||
* List of constraint violations identified.
|
||||
*/
|
||||
protected function validateStub($entity_type_id, $entity_id) {
|
||||
$controller = \Drupal::entityManager()->getStorage($entity_type_id);
|
||||
/** @var \Drupal\Core\Entity\ContentEntityInterface $stub_entity */
|
||||
$stub_entity = $controller->load($entity_id);
|
||||
return $stub_entity->validate();
|
||||
}
|
||||
|
||||
}
|
|
@ -41539,10 +41539,30 @@ $connection->insert('variable')
|
|||
'name' => 'tracker_batch_size',
|
||||
'value' => 'i:999;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_check_frequency',
|
||||
'value' => 'i:1;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_fetch_url',
|
||||
'value' => 's:23:"http://127.0.0.1/update";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_last_check',
|
||||
'value' => 'i:1444944973;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_max_fetch_attempts',
|
||||
'value' => 'i:3;',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_notification_threshold',
|
||||
'value' => 's:3:"all";',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'update_notify_emails',
|
||||
'value' => 'a:1:{i:0;s:19:"webmaster@127.0.0.1";}',
|
||||
))
|
||||
->values(array(
|
||||
'name' => 'user_admin_role',
|
||||
'value' => 's:1:"3";',
|
||||
|
|
|
@ -1064,6 +1064,14 @@ function node_query_node_access_alter(AlterableInterface $query) {
|
|||
|
||||
// Update the query for the given storage method.
|
||||
\Drupal::service('node.grant_storage')->alterQuery($query, $tables, $op, $account, $base_table);
|
||||
|
||||
// Bubble the 'user.node_grants:$op' cache context to the current render
|
||||
// context.
|
||||
$renderer = \Drupal::service('renderer');
|
||||
if ($renderer->hasRenderContext()) {
|
||||
$build = ['#cache' => ['contexts' => ['user.node_grants:' . $op]]];
|
||||
$renderer->render($build);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -47,6 +47,7 @@ entity.node.version_history:
|
|||
_controller: '\Drupal\node\Controller\NodeController::revisionOverview'
|
||||
requirements:
|
||||
_access_node_revision: 'view'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
|
||||
|
@ -57,6 +58,7 @@ entity.node.revision:
|
|||
_title_callback: '\Drupal\node\Controller\NodeController::revisionPageTitle'
|
||||
requirements:
|
||||
_access_node_revision: 'view'
|
||||
node: \d+
|
||||
|
||||
node.revision_revert_confirm:
|
||||
path: '/node/{node}/revisions/{node_revision}/revert'
|
||||
|
@ -65,6 +67,7 @@ node.revision_revert_confirm:
|
|||
_title: 'Revert to earlier revision'
|
||||
requirements:
|
||||
_access_node_revision: 'update'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
|
||||
|
@ -75,6 +78,7 @@ node.revision_revert_translation_confirm:
|
|||
_title: 'Revert to earlier revision of a translation'
|
||||
requirements:
|
||||
_access_node_revision: 'update'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
|
||||
|
@ -85,6 +89,7 @@ node.revision_delete_confirm:
|
|||
_title: 'Delete earlier revision'
|
||||
requirements:
|
||||
_access_node_revision: 'delete'
|
||||
node: \d+
|
||||
options:
|
||||
_node_operation_route: TRUE
|
||||
|
||||
|
|
|
@ -187,7 +187,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
|
|||
if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) {
|
||||
$username = [
|
||||
'#theme' => 'username',
|
||||
'#account' => $revision->uid->entity,
|
||||
'#account' => $revision->getRevisionAuthor(),
|
||||
];
|
||||
|
||||
// Use revision link to link to revisions that are not active.
|
||||
|
|
|
@ -27,6 +27,7 @@ class NodeRouteProvider implements EntityRouteProviderInterface {
|
|||
'_controller' => '\Drupal\node\Controller\NodeViewController::view',
|
||||
'_title_callback' => '\Drupal\node\Controller\NodeViewController::title',
|
||||
])
|
||||
->setRequirement('node', '\d+')
|
||||
->setRequirement('_entity_access', 'node.view');
|
||||
$route_collection->add('entity.node.canonical', $route);
|
||||
|
||||
|
@ -35,6 +36,7 @@ class NodeRouteProvider implements EntityRouteProviderInterface {
|
|||
'_entity_form' => 'node.delete',
|
||||
'_title' => 'Delete',
|
||||
])
|
||||
->setRequirement('node', '\d+')
|
||||
->setRequirement('_entity_access', 'node.delete')
|
||||
->setOption('_node_operation_route', TRUE);
|
||||
$route_collection->add('entity.node.delete_form', $route);
|
||||
|
@ -42,6 +44,7 @@ class NodeRouteProvider implements EntityRouteProviderInterface {
|
|||
$route = (new Route('/node/{node}/edit'))
|
||||
->setDefault('_entity_form', 'node.edit')
|
||||
->setRequirement('_entity_access', 'node.update')
|
||||
->setRequirement('node', '\d+')
|
||||
->setOption('_node_operation_route', TRUE);
|
||||
$route_collection->add('entity.node.edit_form', $route);
|
||||
|
||||
|
|
|
@ -48,4 +48,32 @@ class NodeSelection extends DefaultSelection {
|
|||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createNewEntity($entity_type_id, $bundle, $label, $uid) {
|
||||
$node = parent::createNewEntity($entity_type_id, $bundle, $label, $uid);
|
||||
|
||||
// In order to create a referenceable node, it needs to published.
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
$node->setPublished(TRUE);
|
||||
|
||||
return $node;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateReferenceableNewEntities(array $entities) {
|
||||
$entities = parent::validateReferenceableNewEntities($entities);
|
||||
// Mirror the conditions checked in buildEntityQuery().
|
||||
if (!$this->currentUser->hasPermission('bypass node access') && !count($this->moduleHandler->getImplementations('node_grants'))) {
|
||||
$entities = array_filter($entities, function ($node) {
|
||||
/** @var \Drupal\node\NodeInterface $node */
|
||||
return $node->isPublished();
|
||||
});
|
||||
}
|
||||
return $entities;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -199,7 +199,9 @@ class Node extends WizardPluginBase {
|
|||
protected function buildFilters(&$form, FormStateInterface $form_state) {
|
||||
parent::buildFilters($form, $form_state);
|
||||
|
||||
$selected_bundle = static::getSelected($form_state, array('show', 'type'), 'all', $form['displays']['show']['type']);
|
||||
if (isset($form['displays']['show']['type'])) {
|
||||
$selected_bundle = static::getSelected($form_state, array('show', 'type'), 'all', $form['displays']['show']['type']);
|
||||
}
|
||||
|
||||
// Add the "tagged with" filter to the view.
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Reference in a new issue