Update to Drupal 8.0.3. For more information, see https://www.drupal.org/drupal-8.0.3-release-notes

This commit is contained in:
Pantheon Automation 2016-02-03 14:56:31 -08:00 committed by Greg Anderson
parent 10f9f7fbde
commit 9db4fae9a7
202 changed files with 3806 additions and 760 deletions

View file

@ -28,6 +28,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class MessageAction extends ConfigurableActionBase implements ContainerFactoryPluginInterface {
/**
* The token service.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
@ -48,10 +50,8 @@ class MessageAction extends ConfigurableActionBase implements ContainerFactoryPl
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Utility\Token
* The token service.
* @param \Drupal\Core\Utility\Token $token
* The token replacement service.
* The token service.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer.
*/

View file

@ -323,14 +323,22 @@ function book_node_prepare_form(NodeInterface $node, $operation, FormStateInterf
}
/**
* Implements hook_form_FORM_ID_alter() for node_delete_confirm().
* Implements hook_form_BASE_FORM_ID_alter().
*
* Alters the confirm form for a single node deletion.
*
* @see node_delete_confirm()
*/
function book_form_node_delete_confirm_alter(&$form, FormStateInterface $form_state) {
$node = Node::load($form['nid']['#value']);
function book_form_node_confirm_form_alter(&$form, FormStateInterface $form_state) {
// Only need to alter the delete operation form.
if ($form_state->getFormObject()->getOperation() !== 'delete') {
return;
}
/** @var \Drupal\node\NodeInterface $node */
$node = $form_state->getFormObject()->getEntity();
if (!book_type_is_allowed($node->getType())) {
// Not a book node.
return;
}
if (isset($node->book) && $node->book['has_children']) {
$form['book_warning'] = array(

View file

@ -439,9 +439,9 @@ class BookManager implements BookManagerInterface {
if ($nid == $original['bid']) {
// Handle deletion of a top-level post.
$result = $this->bookOutlineStorage->loadBookChildren($nid);
foreach ($result as $child) {
$child['bid'] = $child['nid'];
$children = $this->entityManager->getStorage('node')->loadMultiple(array_keys($result));
foreach ($children as $child) {
$child->book['bid'] = $child->id();
$this->updateOutline($child);
}
}

View file

@ -30,7 +30,7 @@ class BookTest extends WebTestBase {
/**
* A book node.
*
* @var object
* @var \Drupal\node\NodeInterface
*/
protected $book;
@ -77,11 +77,13 @@ class BookTest extends WebTestBase {
$this->bookAuthor = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books'));
$this->webUser = $this->drupalCreateUser(array('access printer-friendly version', 'node test view'));
$this->webUserWithoutNodeAccess = $this->drupalCreateUser(array('access printer-friendly version'));
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit own book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
$this->adminUser = $this->drupalCreateUser(array('create new books', 'create book content', 'edit any book content', 'delete any book content', 'add content to books', 'administer blocks', 'administer permissions', 'administer book outlines', 'node test view', 'administer content types', 'administer site configuration'));
}
/**
* Creates a new book with a page hierarchy.
*
* @return \Drupal\node\NodeInterface[]
*/
function createBook() {
// Create new book.
@ -498,6 +500,25 @@ class BookTest extends WebTestBase {
$node_storage->resetCache(array($this->book->id()));
$node = $node_storage->load($this->book->id());
$this->assertTrue(empty($node->book), 'Deleting childless top-level book node properly allowed.');
// Tests directly deleting a book parent.
$nodes = $this->createBook();
$this->drupalLogin($this->adminUser);
$this->drupalGet($this->book->urlInfo('delete-form'));
$this->assertRaw(t('%title is part of a book outline, and has associated child pages. If you proceed with deletion, the child pages will be relocated automatically.', ['%title' => $this->book->label()]));
// Delete parent, and visit a child page.
$this->drupalPostForm($this->book->urlInfo('delete-form'), [], t('Delete'));
$this->drupalGet($nodes[0]->urlInfo());
$this->assertResponse(200);
$this->assertText($nodes[0]->label());
// The book parents should be updated.
$node_storage = \Drupal::entityTypeManager()->getStorage('node');
$node_storage->resetCache();
$child = $node_storage->load($nodes[0]->id());
$this->assertEqual($child->id(), $child->book['bid'], 'Child node book ID updated when parent is deleted.');
// 3rd-level children should now be 2nd-level.
$second = $node_storage->load($nodes[1]->id());
$this->assertEqual($child->id(), $second->book['bid'], '3rd-level child node is now second level when top-level node is deleted.');
}
/**

View file

@ -35,10 +35,11 @@ function hook_ckeditor_plugin_info_alter(array &$plugins) {
* iframe versions of CKEditor.
*
* Front-end themes (and base themes) can easily specify CSS files to be used in
* iframe instances of CKEditor through an entry in their .info file:
* iframe instances of CKEditor through an entry in their .info.yml file:
*
* @code
* ckeditor_stylesheets[] = css/ckeditor-iframe.css
* ckeditor_stylesheets:
* - css/ckeditor-iframe.css
* @endcode
*
* @param array &$css

View file

@ -19,6 +19,7 @@ use Drupal\Component\Annotation\Plugin;
* @see \Drupal\ckeditor\CKEditorPluginInterface
* @see \Drupal\ckeditor\CKEditorPluginBase
* @see \Drupal\ckeditor\CKEditorPluginManager
* @see hook_ckeditor_plugin_info_alter()
* @see plugin_api
*
* @Annotation

View file

@ -17,11 +17,11 @@ use Drupal\editor\Entity\Editor;
* than the one provided by Drupal core is used. Most CKEditor plugins don't
* need to provide additional settings forms.
*
* This base assumes that your plugin has buttons that you want to be enabled
* through the toolbar builder UI. It is still possible to also implement the
* CKEditorPluginContextualInterface (for contextual enabling) and
* This base class assumes that your plugin has buttons that you want to be
* enabled through the toolbar builder UI. It is still possible to also
* implement the CKEditorPluginContextualInterface (for contextual enabling) and
* CKEditorPluginConfigurableInterface interfaces (for configuring plugin
* settings) though.
* settings).
*
* NOTE: the Drupal plugin ID should correspond to the CKEditor plugin name.
*

View file

@ -298,8 +298,11 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
);
// Finally, set Drupal-specific CKEditor settings.
$root_relative_file_url = function ($uri) {
return file_url_transform_relative(file_create_url($uri));
};
$settings += array(
'drupalExternalPlugins' => array_map('file_create_url', $external_plugin_files),
'drupalExternalPlugins' => array_map($root_relative_file_url, $external_plugin_files),
);
// Parse all CKEditor plugin JavaScript files for translations.
@ -421,6 +424,7 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
$this->moduleHandler->alter('ckeditor_css', $css, $editor);
$css = array_merge($css, _ckeditor_theme_css());
$css = array_map('file_create_url', $css);
$css = array_map('file_url_transform_relative', $css);
return array_values($css);
}

View file

@ -90,8 +90,8 @@ class CKEditorTest extends KernelTestBase {
'language' => 'en',
'stylesSet' => FALSE,
'drupalExternalPlugins' => array(
'drupalimage' => file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js'),
'drupallink' => file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js'),
'drupalimage' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/plugin.js')),
'drupallink' => file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupallink/plugin.js')),
),
);
$expected_config = $this->castSafeStrings($expected_config);
@ -114,9 +114,9 @@ class CKEditorTest extends KernelTestBase {
$expected_config['toolbar'][0]['items'][] = 'Format';
$expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6';
$expected_config['extraPlugins'] .= ',llama_contextual,llama_contextual_and_button';
$expected_config['drupalExternalPlugins']['llama_contextual'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js');
$expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js');
$expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
$expected_config['drupalExternalPlugins']['llama_contextual'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js'));
$expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js'));
$expected_config['contentsCss'][] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css'));
ksort($expected_config);
$this->assertIdentical($expected_config, $this->castSafeStrings($this->ckeditor->getJSSettings($editor)), 'Generated JS settings are correct for customized configuration.');
@ -261,15 +261,15 @@ class CKEditorTest extends KernelTestBase {
// Enable the editor_test module, which implements hook_ckeditor_css_alter().
$this->enableModules(array('ckeditor_test'));
$expected[] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
$expected[] = file_url_transform_relative(file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css'));
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a hook_ckeditor_css_alter() implementation exists.');
// Enable the Bartik theme, which specifies a CKEditor stylesheet.
\Drupal::service('theme_handler')->install(['bartik']);
$this->config('system.theme')->set('default', 'bartik')->save();
$expected[] = file_create_url('core/themes/bartik/css/base/elements.css');
$expected[] = file_create_url('core/themes/bartik/css/components/captions.css');
$expected[] = file_create_url('core/themes/bartik/css/components/table.css');
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/base/elements.css'));
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/captions.css'));
$expected[] = file_url_transform_relative(file_create_url('core/themes/bartik/css/components/table.css'));
$this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a theme providing a CKEditor stylesheet exists.');
}
@ -478,8 +478,8 @@ class CKEditorTest extends KernelTestBase {
protected function getDefaultContentsCssConfig() {
return array(
file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css'),
file_create_url('core/modules/system/css/components/align.module.css'),
file_url_transform_relative(file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css')),
file_url_transform_relative(file_create_url('core/modules/system/css/components/align.module.css')),
);
}

View file

@ -57,7 +57,6 @@ class CKEditorToolbarButtonTest extends WebTestBase {
* Method tests CKEditor image buttons.
*/
public function testImageButtonDisplay() {
global $base_url;
$this->drupalLogin($this->admin_user);
// Install the Arabic language (which is RTL) and configure as the default.
@ -75,7 +74,7 @@ class CKEditorToolbarButtonTest extends WebTestBase {
$json_encode = function($html) {
return trim(Json::encode($html), '"');
};
$markup = $json_encode($base_url . '/core/modules/ckeditor/js/plugins/drupalimage/image.png');
$markup = $json_encode(file_url_transform_relative(file_create_url('core/modules/ckeditor/js/plugins/drupalimage/image.png')));
$this->assertRaw($markup);
}

View file

@ -126,7 +126,7 @@ function color_block_view_pre_render(array $build) {
// Override logo.
$logo = $config->get('logo');
if ($logo && $build['content']['site_logo'] && preg_match('!' . $theme_key . '/logo.svg$!', $build['content']['site_logo']['#uri'])) {
$build['content']['site_logo']['#uri'] = file_create_url($logo);
$build['content']['site_logo']['#uri'] = file_url_transform_relative(file_create_url($logo));
}
return $build;

View file

@ -121,7 +121,7 @@ class ColorTest extends WebTestBase {
$this->drupalGet('<front>');
$stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets');
foreach ($stylesheets as $stylesheet) {
$this->assertPattern('|' . file_create_url($stylesheet) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')');
$this->assertPattern('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')');
$stylesheet_content = join("\n", file($stylesheet));
$this->assertTrue(strpos($stylesheet_content, 'color: #123456') !== FALSE, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')');
}
@ -191,7 +191,7 @@ class ColorTest extends WebTestBase {
// Ensure that the overridden logo is present in Bartik, which is colorable.
$this->drupalGet('admin/appearance/settings/bartik');
$this->assertIdentical($GLOBALS['base_url'] . '/' . 'core/misc/druplicon.png', $this->getDrupalSettings()['color']['logo']);
$this->assertIdentical($GLOBALS['base_path'] . 'core/misc/druplicon.png', $this->getDrupalSettings()['color']['logo']);
}
/**

View file

@ -1,14 +0,0 @@
<?php
/**
* @file
* Provide test color module.
*/
/**
* Implements hook_system_theme_info().
*/
function color_test_system_theme_info() {
$themes['color_test_theme'] = drupal_get_path('module', 'color_test') . '/themes/color_test_theme/color_test_theme.info.yml';
return $themes;
}

View file

@ -563,7 +563,7 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta
if (!$form_state->getErrors()) {
$comment->in_preview = TRUE;
$comment_build = comment_view($comment);
$comment_build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($comment);
$comment_build['#weight'] = -100;
$preview_build['comment_preview'] = $comment_build;
@ -573,7 +573,7 @@ function comment_preview(CommentInterface $comment, FormStateInterface $form_sta
$build = array();
$parent = $comment->getParentComment();
if ($parent && $parent->isPublished()) {
$build = comment_view($parent);
$build = \Drupal::entityTypeManager()->getViewBuilder('comment')->view($parent);
}
}
else {

View file

@ -104,7 +104,7 @@ class Rss extends RssPluginBase {
// The comment gets built and modules add to or modify
// $comment->rss_elements and $comment->rss_namespaces.
$build = comment_view($comment, 'rss');
$build = $this->entityManager->getViewBuilder('comment')->view($comment, 'rss');
unset($build['#theme']);
if (!empty($comment->rss_namespaces)) {

View file

@ -164,7 +164,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
'created' => REQUEST_TIME - mt_rand(0, 1000),
);
$edit = array(
'uid' => $user->getUsername() . '(' . $user->id() . ')',
'uid' => $user->getUsername() . ' (' . $user->id() . ')',
'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
);

View file

@ -33,7 +33,6 @@ class ConfigEventsTest extends KernelTestBase {
$config = new Config($name, \Drupal::service('config.storage'), \Drupal::service('event_dispatcher'), \Drupal::service('config.typed'));
$config->set('key', 'initial');
\Drupal::state()->get('config_events_test.event', FALSE);
$this->assertIdentical(\Drupal::state()->get('config_events_test.event', array()), array(), 'No events fired by creating a new configuration object');
$config->save();

View file

@ -506,4 +506,70 @@ class ConfigSchemaTest extends KernelTestBase {
$this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info');
}
/**
* Tests saving config when the type is wrapped by a dynamic type.
*/
public function testConfigSaveWithWrappingSchema() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'plugin_id' => 'wrapper:foo',
'internal_value' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.plugin_types')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.plugin_types')
->get(), $typed_values);
}
/**
* Tests dynamic config schema type with multiple sub-key references.
*/
public function testConfigSaveWithWrappingSchemaDoubleBrackets() {
$untyped_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => 100,
],
],
];
$typed_values = [
'tests' => [
[
'wrapper_value' => 'foo',
'foo' => 'cat',
'bar' => 'dog',
'another_key' => '100',
],
],
];
// Save config which has a schema that enforces types.
\Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
->setData($untyped_values)
->save();
$this->assertIdentical(\Drupal::config('wrapping.config_schema_test.double_brackets')
->get(), $typed_values);
}
}

View file

@ -9,6 +9,7 @@ namespace Drupal\config\Tests\Storage;
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Config\FileStorage;
use Drupal\Core\Config\UnsupportedDataTypeConfigException;
/**
* Tests FileStorage operations.
@ -76,4 +77,19 @@ class FileStorageTest extends ConfigStorageTestBase {
$this->assertIdentical($config_files, $expected_files, 'Absolute path, two config files found.');
}
/**
* Test UnsupportedDataTypeConfigException displays path of
* erroneous file during read.
*/
public function testReadUnsupportedDataTypeConfigException() {
file_put_contents($this->storage->getFilePath('core.extension'), PHP_EOL . 'foo : [bar}', FILE_APPEND);
try {
$config_parsed = $this->storage->read('core.extension');
}
catch (UnsupportedDataTypeConfigException $e) {
$this->pass('Exception thrown when trying to read a field containing invalid data type.');
$this->assertTrue((strpos($e->getMessage(), $this->storage->getFilePath('core.extension')) !== FALSE), 'Erroneous file path is displayed.');
}
}
}

View file

@ -32,10 +32,10 @@ class EventSubscriber implements EventSubscriberInterface {
}
/**
* Reacts to the ConfigEvents::COLLECTION_NAMES event.
* Reacts to the ConfigEvents::COLLECTION_INFO event.
*
* @param \Drupal\Core\Config\ConfigCollectionInfo $collection_info
* The configuration collection names event.
* The configuration collection info event.
*/
public function addCollections(ConfigCollectionInfo $collection_info) {
$collections = $this->state->get('config_collection_install_test.collection_names', array());

View file

@ -213,3 +213,50 @@ config_test.dynamic.*.third_party.config_schema_test:
type: integer
string:
type: string
wrapping.config_schema_test.plugin_types:
type: config_object
mapping:
tests:
type: sequence
sequence:
- type: wrapping.test.plugin_types.[plugin_id]
wrapping.test.plugin_types.*:
type: test.plugin_types.[plugin_id]
mapping:
wrapper_value:
type: string
test.plugin_types.wrapper:*:
type: test.plugin_types
mapping:
internal_value:
type: string
wrapping.config_schema_test.double_brackets:
type: config_object
mapping:
tests:
type: sequence
sequence:
- type: wrapping.test.double_brackets.[another_key]
wrapping.test.double_brackets.*:
type: test.double_brackets.[foo].[bar]
mapping:
wrapper_value:
type: string
test.double_brackets.cat.dog:
type: test.double_brackets
mapping:
another_key:
type: string
foo:
type: string
bar:
type: string
test.double_brackets.*:
type: mapping

View file

@ -35,9 +35,6 @@ function dblog_views_data() {
'sort' => array(
'id' => 'standard',
),
'search' => array(
'id' => 'standard',
),
);
$data['watchdog']['uid'] = array(
@ -52,9 +49,6 @@ function dblog_views_data() {
'argument' => array(
'id' => 'numeric',
),
'search' => array(
'id' => 'standard',
),
'relationship' => array(
'title' => t('User'),
'help' => t('The user on which the log entry as written.'),

View file

@ -308,7 +308,7 @@ class EntityReferenceAdminTest extends WebTestBase {
$edit = array(
'title[0][value]' => 'Test',
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . '(' . $node1->id() . ')'
'field_test_entity_ref_field[0][target_id]' => $node1->getTitle() . ' (' . $node1->id() . ')'
);
$this->drupalPostForm('node/add/' . $this->type, $edit, t('Save'));
$this->assertLink($node1->getTitle());

View file

@ -12,6 +12,7 @@ use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTest;
@ -76,6 +77,7 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
$this->installEntitySchema('file');
$this->installSchema('comment', ['comment_entity_statistics']);
$this->installSchema('node', ['node_access']);
$this->vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => $this->randomMachineName(),
@ -100,7 +102,7 @@ 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_node', 'Test node entity reference', 'node', 'default', [], FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
$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');
@ -406,6 +408,64 @@ class EntityReferenceItemTest extends FieldUnitTestBase {
$errors = $entity->validate();
$this->assertEqual(0, count($errors));
// Test with a mix of valid and invalid nodes.
$unsaved_unpublished_node_title = $this->randomString();
$unsaved_unpublished_node = Node::create([
'title' => $unsaved_unpublished_node_title,
'type' => 'node',
'status' => NODE_NOT_PUBLISHED,
]);
$saved_unpublished_node_title = $this->randomString();
$saved_unpublished_node = Node::create([
'title' => $saved_unpublished_node_title,
'type' => 'node',
'status' => NODE_NOT_PUBLISHED,
]);
$saved_unpublished_node->save();
$saved_published_node_title = $this->randomString();
$saved_published_node = Node::create([
'title' => $saved_published_node_title,
'type' => 'node',
'status' => NODE_PUBLISHED,
]);
$saved_published_node->save();
$entity = EntityTest::create([
'field_test_node' => [
[
'entity' => $unsaved_unpublished_node,
],
[
'target_id' => $saved_unpublished_node->id(),
],
[
'target_id' => $saved_published_node->id(),
],
],
]);
$errors = $entity->validate();
$this->assertEqual(2, count($errors));
$this->assertEqual($errors[0]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $unsaved_unpublished_node_title]));
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
$this->assertEqual($errors[1]->getMessage(), new FormattableMarkup('This entity (%type: %label) cannot be referenced.', ['%type' => 'node', '%label' => $saved_unpublished_node->id()]));
$this->assertEqual($errors[1]->getPropertyPath(), 'field_test_node.1.target_id');
// Publish one of the nodes and try again.
$saved_unpublished_node->setPublished(TRUE);
$saved_unpublished_node->save();
$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' => $unsaved_unpublished_node_title]));
$this->assertEqual($errors[0]->getPropertyPath(), 'field_test_node.0.entity');
// Publish the last invalid node and try again.
$unsaved_unpublished_node->setPublished(TRUE);
$errors = $entity->validate();
$this->assertEqual(0, count($errors));
// Test with an unpublished and unsaved comment.
$title = $this->randomString();
$comment = Comment::create([

View file

@ -977,7 +977,12 @@ function file_tokens($type, $tokens, array $data, array $options, BubbleableMeta
break;
case 'url':
// Ideally, this would use file_url_transform_relative(), but because
// tokens are also often used in e-mails, it's better to keep absolute
// file URLs. The 'url.site' cache context is associated to ensure the
// correct absolute URL is used in case of a multisite setup.
$replacements[$original] = file_create_url($file->getFileUri());
$bubbleable_metadata->addCacheContexts(['url.site']);
break;
// These tokens are default variations on the chained tokens handled below.
@ -1228,7 +1233,13 @@ function template_preprocess_file_link(&$variables) {
$options = array();
$file_entity = ($file instanceof File) ? $file : File::load($file->fid);
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we currently add the 'url.site' cache context
// to ensure different file URLs are generated for different sites in a
// multisite setup, including HTTP and HTTPS versions of the same site.
// Fix in https://www.drupal.org/node/2646744.
$url = file_create_url($file_entity->getFileUri());
$variables['#cache']['contexts'][] = 'url.site';
$mime_type = $file->getMimeType();
// Set options as per anchor format described at

View file

@ -70,6 +70,8 @@ class File extends ContentEntityBase implements FileInterface {
/**
* {@inheritdoc}
*
* @see file_url_transform_relative()
*/
public function url($rel = 'canonical', $options = array()) {
return file_create_url($this->getFileUri());

View file

@ -52,6 +52,8 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase {
$url = NULL;
// Add support to link to the entity itself.
if ($this->getSetting('link_to_file')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See below.
$url = file_create_url($items->getEntity()->uri->value);
}
@ -63,6 +65,16 @@ abstract class BaseFieldFileFormatterBase extends FormatterBase {
'#type' => 'link',
'#title' => $view_value,
'#url' => Url::fromUri($url),
// @todo Remove the 'url.site' cache context by using a relative file
// URL (file_url_transform_relative()). This is currently impossible
// because #type => link requires a Url object, and Url objects do not
// support relative URLs: they require fully qualified URLs. Fix in
// https://www.drupal.org/node/2646744.
'#cache' => [
'contexts' => [
'url.site',
],
],
];
}
else {

View file

@ -55,6 +55,9 @@ class FileUriFormatter extends BaseFieldFileFormatterBase {
protected function viewValue(FieldItemInterface $item) {
$value = $item->value;
if ($this->getSetting('file_download_path')) {
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. See BaseFieldFileFormatterBase::viewElements(). Fix in
// https://www.drupal.org/node/2646744.
$value = file_create_url($value);
}
return $value;

View file

@ -33,6 +33,9 @@ class RSSEnclosureFormatter extends FileFormatterBase {
$entity->rss_elements[] = array(
'key' => 'enclosure',
'attributes' => array(
// In RSS feeds, it is necessary to use absolute URLs. The 'url.site'
// cache context is already associated with RSS feed responses, so it
// does not need to be specified here.
'url' => file_create_url($file->getFileUri()),
'length' => $file->getSize(),
'type' => $file->getMimeType(),

View file

@ -30,7 +30,7 @@ class UrlPlainFormatter extends FileFormatterBase {
foreach ($this->getEntitiesToView($items, $langcode) as $delta => $file) {
$elements[$delta] = array(
'#markup' => file_create_url($file->getFileUri()),
'#markup' => file_url_transform_relative(file_create_url($file->getFileUri())),
'#cache' => array(
'tags' => $file->getCacheTags(),
),

View file

@ -56,9 +56,11 @@ class FileFieldItemList extends EntityReferenceFieldItemList {
$original_ids = array();
$langcode = $this->getLangcode();
$original = $entity->original;
$original_items = $original->hasTranslation($langcode) ? $original->getTranslation($langcode)->{$field_name} : $original->{$field_name};
foreach ($original_items as $item) {
$original_ids[] = $item->target_id;
if ($original->hasTranslation($langcode)) {
$original_items = $original->getTranslation($langcode)->{$field_name};
foreach ($original_items as $item) {
$original_ids[] = $item->target_id;
}
}
// Decrement file usage by 1 for files that were removed from the field.

View file

@ -186,7 +186,6 @@ class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
$elements['#description'] = $description;
$elements['#field_name'] = $field_name;
$elements['#language'] = $items->getLangcode();
$elements['#display_field'] = (bool) $this->getFieldSetting('display_field');
// The field settings include defaults for the field type. However, this
// widget is a base class for other widgets (e.g., ImageWidget) that may
// act on field types without these expected settings.

View file

@ -2,10 +2,10 @@
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\FileField.
* Contains \Drupal\file\Plugin\migrate\cckfield\d6\FileField.
*/
namespace Drupal\file\Plugin\migrate\cckfield;
namespace Drupal\file\Plugin\migrate\cckfield\d6;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;

View file

@ -0,0 +1,67 @@
<?php
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\d7\FileField.
*/
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "file",
* )
*/
class FileField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldWidgetMap() {
return [
'filefield_widget' => 'file_generic',
];
}
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return [
'default' => 'file_default',
'url_plain' => 'file_url_plain',
'path_plain' => 'file_url_plain',
'image_plain' => 'image',
'image_nodelink' => 'image',
'image_imagelink' => 'image',
];
}
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'iterator',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'display' => 'display',
'description' => 'description',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
/**
* {@inheritdoc}
*/
public function getFieldType(Row $row) {
return $row->getSourceProperty('widget_type') == 'imagefield_widget' ? 'image' : 'file';
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\file\Plugin\migrate\cckfield\d7\ImageField.
*/
namespace Drupal\file\Plugin\migrate\cckfield\d7;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\cckfield\CckFieldPluginBase;
/**
* @MigrateCckField(
* id = "image"
* )
*/
class ImageField extends CckFieldPluginBase {
/**
* {@inheritdoc}
*/
public function getFieldFormatterMap() {
return array();
}
/**
* {@inheritdoc}
*/
public function processCckFieldValues(MigrationInterface $migration, $field_name, $data) {
$process = [
'plugin' => 'iterator',
'source' => $field_name,
'process' => [
'target_id' => 'fid',
'alt' => 'alt',
'title' => 'title',
'width' => 'width',
'height' => 'height',
],
];
$migration->mergeProcessOfProperty($field_name, $process);
}
}

View file

@ -69,6 +69,12 @@ class File extends FieldPluginBase {
protected function renderLink($data, ResultRow $values) {
if (!empty($this->options['link_to_file']) && $data !== NULL && $data !== '') {
$this->options['alter']['make_link'] = TRUE;
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we could add the 'url.site' cache context
// to ensure different file URLs are generated for different sites in a
// multisite setup, including HTTP and HTTPS versions of the same site.
// But unfortunately it's impossible to bubble a cache context here.
// Fix in https://www.drupal.org/node/2646744.
$this->options['alter']['path'] = file_create_url($this->getValue($values, 'uri'));
}

View file

@ -0,0 +1,208 @@
<?php
/**
* @file
* Contains \Drupal\file\Tests\FileOnTranslatedEntityTest.
*/
namespace Drupal\file\Tests;
use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
/**
* Uploads files to translated nodes.
*
* @group file
*/
class FileOnTranslatedEntityTest extends FileFieldTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('language', 'content_translation');
/**
* The name of the file field used in the test.
*
* @var string
*/
protected $fieldName;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create the "Basic page" node type.
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
// Create a file field on the "Basic page" node type.
$this->fieldName = strtolower($this->randomMachineName());
$this->createFileField($this->fieldName, 'node', 'page');
// Create and login user.
$permissions = array(
'access administration pages',
'administer content translation',
'administer content types',
'administer languages',
'create content translations',
'create page content',
'edit any page content',
'translate any entity',
'delete any page content',
);
$admin_user = $this->drupalCreateUser($permissions);
$this->drupalLogin($admin_user);
// Add a second and third language.
$edit = array();
$edit['predefined_langcode'] = 'fr';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$edit = array();
$edit['predefined_langcode'] = 'nl';
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
// Enable translation for "Basic page" nodes.
$edit = array(
'entity_types[node]' => 1,
'settings[node][page][translatable]' => 1,
"settings[node][page][fields][$this->fieldName]" => 1,
);
$this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
\Drupal::entityManager()->clearCachedDefinitions();
}
/**
* Tests synced file fields on translated nodes.
*/
public function testSyncedFiles() {
// Verify that the file field on the "Basic page" node type is translatable.
$definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
$this->assertTrue($definitions[$this->fieldName]->isTranslatable(), 'Node file field is translatable.');
// Create a default language node.
$default_language_node = $this->drupalCreateNode(array('type' => 'page', 'title' => 'Lost in translation'));
// Edit the node to upload a file.
$edit = array();
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[0]->uri);
$this->drupalPostForm('node/' . $default_language_node->id() . '/edit', $edit, t('Save'));
$first_fid = $this->getLastFileId();
// Translate the node into French: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/fr', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'Bill Murray';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[1]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
$second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
/* @var $file \Drupal\file\FileInterface */
// Ensure the file status of the first file permanent.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Translate the node into dutch: remove the existing file.
$this->drupalPostForm('node/' . $default_language_node->id() . '/translations/add/en/nl', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'Scarlett Johansson';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[2]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$third_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first file is untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
// This inspects the HTML after the post of the translation, the file
// should be displayed on the original node.
$this->assertRaw('file--mime-text-plain');
// Ensure the file status of the second file is permanent.
$file = File::load($second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is permanent.
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Edit the second translation: remove the existing file.
$this->drupalPostForm('fr/node/' . $default_language_node->id() . '/edit', array(), t('Remove'));
// Upload a different file.
$edit = array();
$edit['title[0][value]'] = 'David Bowie';
$name = 'files[' . $this->fieldName . '_0]';
$edit[$name] = drupal_realpath($this->drupalGetTestFiles('text')[3]->uri);
$this->drupalPostForm(NULL, $edit, t('Save (this translation)'));
$replaced_second_fid = $this->getLastFileId();
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and third files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($third_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the replaced second file is permanent.
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the old second file is now temporary.
$file = File::load($second_fid);
$this->assertTrue($file->isTemporary());
// Delete the third translation.
$this->drupalPostForm('nl/node/' . $default_language_node->id() . '/delete', array(), t('Delete Dutch translation'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the first and replaced second files are untouched.
$file = File::load($first_fid);
$this->assertTrue($file->isPermanent(), 'First file still exists and is permanent.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isPermanent());
// Ensure the file status of the third file is now temporary.
$file = File::load($third_fid);
$this->assertTrue($file->isTemporary());
// Delete the all translations.
$this->drupalPostForm('node/' . $default_language_node->id() . '/delete', array(), t('Delete all translations'));
\Drupal::entityTypeManager()->getStorage('file')->resetCache();
// Ensure the file status of the all files are now temporary.
$file = File::load($first_fid);
$this->assertTrue($file->isTemporary(), 'First file still exists and is temporary.');
$file = File::load($replaced_second_fid);
$this->assertTrue($file->isTemporary());
}
}

View file

@ -66,7 +66,8 @@ class FileTokenReplaceTest extends FileFieldTestBase {
$metadata_tests['[file:path]'] = $base_bubbleable_metadata;
$metadata_tests['[file:mime]'] = $base_bubbleable_metadata;
$metadata_tests['[file:size]'] = $base_bubbleable_metadata;
$metadata_tests['[file:url]'] = $base_bubbleable_metadata;
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:url]'] = $bubbleable_metadata->addCacheContexts(['url.site']);
$bubbleable_metadata = clone $base_bubbleable_metadata;
$metadata_tests['[file:created]'] = $bubbleable_metadata->addCacheTags(['rendered']);
$metadata_tests['[file:created:short]'] = $bubbleable_metadata;

View file

@ -72,16 +72,18 @@ class FilterCaption extends FilterBase {
$altered_html = drupal_render($filter_caption);
// Load the altered HTML into a new DOMDocument and retrieve the element.
$updated_node = Html::load($altered_html)->getElementsByTagName('body')
$updated_nodes = Html::load($altered_html)->getElementsByTagName('body')
->item(0)
->childNodes
->item(0);
->childNodes;
// Import the updated node from the new DOMDocument into the original
// one, importing also the child nodes of the updated node.
$updated_node = $dom->importNode($updated_node, TRUE);
// Finally, replace the original node with the new node.
$node->parentNode->replaceChild($updated_node, $node);
foreach ($updated_nodes as $updated_node) {
// Import the updated node from the new DOMDocument into the original
// one, importing also the child nodes of the updated node.
$updated_node = $dom->importNode($updated_node, TRUE);
$node->parentNode->insertBefore($updated_node, $node);
}
// Finally, remove the original data-caption node.
$node->parentNode->removeChild($node);
}
$result->setProcessedText(Html::serialize($dom))

View file

@ -49,7 +49,7 @@ class FilterHtml extends FilterBase {
'#type' => 'textfield',
'#title' => $this->t('Allowed HTML tags'),
'#default_value' => $this->settings['allowed_html'],
'#maxlength' => 1024,
'#maxlength' => 2048,
'#description' => $this->t('A list of HTML tags that can be used. By default only the <em>lang</em> and <em>dir</em> attributes are allowed for all HTML tags. Each HTML tag may have attributes which are treated as allowed attribute names for that HTML tag. Each attribute may allow all values, or only allow specific values. Attribute names or values may be written as a prefix and wildcard like <em>jump-*</em>. JavaScript event attributes, JavaScript URLs, and CSS are always stripped.'),
'#size' => 250,
'#attached' => array(

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\filter\Tests\FilterCaptionTwigDebugTest.
*/
namespace Drupal\filter\Tests;
use Drupal\Core\Render\RenderContext;
use Drupal\simpletest\WebTestBase;
use Drupal\filter\FilterPluginCollection;
/**
* Tests the caption filter with Twig debugging on.
*
* @group filter
*/
class FilterCaptionTwigDebugTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['system', 'filter'];
/**
* @var \Drupal\filter\Plugin\FilterInterface[]
*/
protected $filters;
/**
* Enables Twig debugging.
*/
protected function debugOn() {
// Enable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
if (!$parameters['debug']) {
$parameters['debug'] = TRUE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
}
}
/**
* Disables Twig debugging.
*/
protected function debugOff() {
// Disable debug, rebuild the service container, and clear all caches.
$parameters = $this->container->getParameter('twig.config');
if ($parameters['debug']) {
$parameters['debug'] = FALSE;
$this->setContainerParameter('twig.config', $parameters);
$this->rebuildContainer();
$this->resetAll();
}
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->debugOn();
$manager = $this->container->get('plugin.manager.filter');
$bag = new FilterPluginCollection($manager, []);
$this->filters = $bag->getAll();
}
/**
* {@inheritdoc}
*/
protected function tearDown() {
$this->debugOff();
}
/**
* Test the caption filter with Twig debugging on.
*/
function testCaptionFilter() {
/** @var \Drupal\Core\Render\RendererInterface $renderer */
$renderer = \Drupal::service('renderer');
$filter = $this->filters['filter_caption'];
$test = function ($input) use ($filter, $renderer) {
return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter) {
return $filter->process($input, 'und');
});
};
// No data-caption attribute.
$input = '<img src="llama.jpg" />';
$expected = $input;
$this->assertIdentical($expected, $test($input)->getProcessedText());
// Data-caption attribute.
$input = '<img src="llama.jpg" data-caption="Loquacious llama!" />';
$expected = '<img src="llama.jpg" /><figcaption>Loquacious llama!</figcaption>';
$output = $test($input);
$output = $output->getProcessedText();
$this->assertTrue(strpos($output, $expected) !== FALSE, "\"$output\" contains \"$expected\"");
$this->assertTrue(strpos($output, '<!-- THEME HOOK: \'filter_caption\' -->') !== FALSE, 'filter_caption theme hook debug comment is present.');
}
}

View file

@ -501,7 +501,12 @@ function template_preprocess_forums(&$variables) {
}
$row[] = array(
'data' => $topic->comment_count . $new_replies,
'data' => [
[
'#prefix' => $topic->comment_count,
'#markup' => $new_replies,
],
],
'class' => array('forum__replies'),
);
$row[] = array(

View file

@ -514,6 +514,9 @@ class ForumTest extends WebTestBase {
// Check that forum renders properly.
$this->drupalGet("forum/{$this->forum['tid']}");
$this->assertResponse(200);
// Verify there is no unintentional HTML tag escaping.
$this->assertNoEscaped('<', '');
}
/**

View file

@ -34,7 +34,7 @@ function template_preprocess_image_style_preview(&$variables) {
$original_path = \Drupal::config('image.settings')->get('preview_image');
$original_image = $image_factory->get($original_path);
$variables['original'] = array(
'url' => file_create_url($original_path),
'url' => file_url_transform_relative(file_create_url($original_path)),
'width' => $original_image->getWidth(),
'height' => $original_image->getHeight(),
);
@ -55,7 +55,7 @@ function template_preprocess_image_style_preview(&$variables) {
}
$preview_image = $image_factory->get($preview_file);
$variables['derivative'] = array(
'url' => file_create_url($preview_file),
'url' => file_url_transform_relative(file_create_url($preview_file)),
'width' => $preview_image->getWidth(),
'height' => $preview_image->getHeight(),
);

View file

@ -0,0 +1,22 @@
<?php
/**
* @file
* Post-update functions for Image.
*/
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
/**
* Saves the image style dependencies into form and view display entities.
*/
function image_post_update_image_style_dependencies() {
// Merge view and form displays. Use array_values() to avoid key collisions.
$displays = array_merge(array_values(EntityViewDisplay::loadMultiple()), array_values(EntityFormDisplay::loadMultiple()));
/** @var \Drupal\Core\Entity\Display\EntityDisplayInterface[] $displays */
foreach ($displays as $display) {
// Re-save each config entity to add missed dependencies.
$display->save();
}
}

View file

@ -36,6 +36,7 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
* "flush" = "Drupal\image\Form\ImageStyleFlushForm"
* },
* "list_builder" = "Drupal\image\ImageStyleListBuilder",
* "storage" = "Drupal\image\ImageStyleStorage",
* },
* admin_permission = "administer image styles",
* config_prefix = "style",
@ -58,13 +59,6 @@ use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
*/
class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, EntityWithPluginCollectionInterface {
/**
* The name of the image style to use as replacement upon delete.
*
* @var string
*/
protected $replacementID;
/**
* The name of the image style.
*
@ -128,17 +122,13 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
public static function postDelete(EntityStorageInterface $storage, array $entities) {
parent::postDelete($storage, $entities);
/** @var \Drupal\image\ImageStyleInterface[] $entities */
foreach ($entities as $style) {
// Flush cached media for the deleted style.
$style->flush();
// Check whether field settings need to be updated.
// In case no replacement style was specified, all image fields that are
// using the deleted style are left in a broken state.
if (!$style->isSyncing() && $new_id = $style->getReplacementID()) {
// The deleted ID is still set as originalID.
$style->setName($new_id);
static::replaceImageStyle($style);
}
// Clear the replacement ID, if one has been previously stored.
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage->clearReplacementId($style->id());
}
}
@ -380,7 +370,9 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
* {@inheritdoc}
*/
public function getReplacementID() {
return $this->get('replacementID');
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = $this->entityTypeManager()->getStorage($this->getEntityTypeId());
return $storage->getReplacementId($this->id());
}
/**

View file

@ -15,6 +15,13 @@ use Drupal\Core\Form\FormStateInterface;
*/
class ImageStyleDeleteForm extends EntityDeleteForm {
/**
* Replacement options.
*
* @var array
*/
protected $replacementOptions;
/**
* {@inheritdoc}
*/
@ -25,20 +32,28 @@ class ImageStyleDeleteForm extends EntityDeleteForm {
* {@inheritdoc}
*/
public function getDescription() {
return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted.');
if (count($this->getReplacementOptions()) > 1) {
return $this->t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.');
}
return $this->t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.');
}
/**
* {@inheritdoc}
*/
public function form(array $form, FormStateInterface $form_state) {
$replacement_styles = array_diff_key(image_style_options(), array($this->entity->id() => ''));
$form['replacement'] = array(
'#title' => $this->t('Replacement style'),
'#type' => 'select',
'#options' => $replacement_styles,
'#empty_option' => $this->t('No replacement, just delete'),
);
$replacement_styles = $this->getReplacementOptions();
// If there are non-empty options in the list, allow the user to optionally
// pick up a replacement.
if (count($replacement_styles) > 1) {
$form['replacement'] = [
'#type' => 'select',
'#title' => $this->t('Replacement style'),
'#options' => $replacement_styles,
'#empty_option' => $this->t('- No replacement -'),
'#weight' => -5,
];
}
return parent::form($form, $form_state);
}
@ -47,9 +62,27 @@ class ImageStyleDeleteForm extends EntityDeleteForm {
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->entity->set('replacementID', $form_state->getValue('replacement'));
// Save a selected replacement in the image style storage. It will be used
// later, in the same request, when resolving dependencies.
if ($replacement = $form_state->getValue('replacement')) {
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = $this->entityTypeManager->getStorage($this->entity->getEntityTypeId());
$storage->setReplacementId($this->entity->id(), $replacement);
}
parent::submitForm($form, $form_state);
}
/**
* Returns a list of image style replacement options.
*
* @return array
* An option list suitable for the form select '#options'.
*/
protected function getReplacementOptions() {
if (!isset($this->replacementOptions)) {
$this->replacementOptions = array_diff_key(image_style_options(), [$this->getEntity()->id() => '']);
}
return $this->replacementOptions;
}
}

View file

@ -17,8 +17,14 @@ interface ImageStyleInterface extends ConfigEntityInterface {
/**
* Returns the replacement ID.
*
* @return string
* The name of the image style to use as replacement upon delete.
* @return string|null
* The replacement image style ID or NULL if no replacement has been
* selected.
*
* @deprecated in Drupal 8.0.x, will be removed before Drupal 9.0.x. Use
* \Drupal\image\ImageStyleStorageInterface::getReplacementId() instead.
*
* @see \Drupal\image\ImageStyleStorageInterface::getReplacementId()
*/
public function getReplacementID();
@ -70,6 +76,7 @@ interface ImageStyleInterface extends ConfigEntityInterface {
* in an <img> tag. Requesting the URL will cause the image to be created.
*
* @see \Drupal\image\Controller\ImageStyleDownloadController::deliver()
* @see file_url_transform_relative()
*/
public function buildUrl($path, $clean_urls = NULL);

View file

@ -91,9 +91,9 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
*/
public function render() {
$build = parent::render();
$build['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', array(
$build['table']['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', [
':url' => $this->urlGenerator->generateFromRoute('image.style_add'),
));
]);
return $build;
}

View file

@ -0,0 +1,51 @@
<?php
/**
* @file
* Contains \Drupal\image\ImageStyleStorage.
*/
namespace Drupal\image;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
/**
* Storage controller class for "image style" configuration entities.
*/
class ImageStyleStorage extends ConfigEntityStorage implements ImageStyleStorageInterface {
/**
* Image style replacement memory storage.
*
* This value is not stored in the backend. It's used during the deletion of
* an image style to save the replacement image style in the same request. The
* value is used later, when resolving dependencies.
*
* @var string[]
*
* @see \Drupal\image\Form\ImageStyleDeleteForm::submitForm()
*/
protected $replacement = [];
/**
* {@inheritdoc}
*/
public function setReplacementId($name, $replacement) {
$this->replacement[$name] = $replacement;
}
/**
* {@inheritdoc}
*/
public function getReplacementId($name) {
return isset($this->replacement[$name]) ? $this->replacement[$name] : NULL;
}
/**
* {@inheritdoc}
*/
public function clearReplacementId($name) {
unset($this->replacement[$name]);
}
}

View file

@ -0,0 +1,57 @@
<?php
/**
* @file
* Contains \Drupal\image\ImageStyleStorageInterface.
*/
namespace Drupal\image;
/**
* Interface for storage controller for "image style" configuration entities.
*/
interface ImageStyleStorageInterface {
/**
* Stores a replacement ID for an image style being deleted.
*
* The method stores a replacement style to be used by the configuration
* dependency system when a image style is deleted. The replacement style is
* replacing the deleted style in other configuration entities that are
* depending on the image style being deleted.
*
* @param string $name
* The ID of the image style to be deleted.
* @param string $replacement
* The ID of the image style used as replacement.
*/
public function setReplacementId($name, $replacement);
/**
* Retrieves the replacement ID of a deleted image style.
*
* The method is retrieving the value stored by ::setReplacementId().
*
* @param string $name
* The ID of the image style to be replaced.
*
* @return string|null
* The ID of the image style used as replacement, if there's any, or NULL.
*
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
*/
public function getReplacementId($name);
/**
* Clears a replacement ID from the storage.
*
* The method clears the value previously stored with ::setReplacementId().
*
* @param string $name
* The ID of the image style to be replaced.
*
* @see \Drupal\image\ImageStyleStorageInterface::setReplacementId()
*/
public function clearReplacementId($name);
}

View file

@ -14,6 +14,7 @@ use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Drupal\image\Entity\ImageStyle;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Cache\Cache;
@ -41,7 +42,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
/**
* The image style entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
* @var \Drupal\image\ImageStyleStorageInterface
*/
protected $imageStyleStorage;
@ -199,9 +200,16 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
}
foreach ($files as $delta => $file) {
$cache_contexts = array();
if (isset($link_file)) {
$image_uri = $file->getFileUri();
// @todo Wrap in file_url_transform_relative(). This is currently
// impossible. As a work-around, we currently add the 'url.site' cache
// context to ensure different file URLs are generated for different
// sites in a multisite setup, including HTTP and HTTPS versions of the
// same site. Fix in https://www.drupal.org/node/2646744.
$url = Url::fromUri(file_create_url($image_uri));
$cache_contexts[] = 'url.site';
}
$cache_tags = Cache::mergeTags($cache_tags, $file->getCacheTags());
@ -219,6 +227,7 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
'#url' => $url,
'#cache' => array(
'tags' => $cache_tags,
'contexts' => $cache_contexts,
),
);
}
@ -226,4 +235,41 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
return $elements;
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$style_id = $this->getSetting('image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
// If this formatter uses a valid image style to display the image, add
// the image style configuration entity as dependency of this formatter.
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
$style_id = $this->getSetting('image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
$replacement_id = $this->imageStyleStorage->getReplacementId($style_id);
// If a valid replacement has been provided in the storage, replace the
// image style with the replacement and signal that the formatter plugin
// settings were updated.
if ($replacement_id && ImageStyle::load($replacement_id)) {
$this->setSetting('image_style', $replacement_id);
$changed = TRUE;
}
}
}
return $changed;
}
}

View file

@ -12,6 +12,7 @@ use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Form\FormStateInterface;
use Drupal\file\Entity\File;
use Drupal\file\Plugin\Field\FieldWidget\FileWidget;
use Drupal\image\Entity\ImageStyle;
/**
* Plugin implementation of the 'image_image' widget.
@ -273,4 +274,49 @@ class ImageWidget extends FileWidget {
}
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$dependencies = parent::calculateDependencies();
$style_id = $this->getSetting('preview_image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
// If this widget uses a valid image style to display the preview of the
// uploaded image, add that image style configuration entity as dependency
// of this widget.
$dependencies[$style->getConfigDependencyKey()][] = $style->getConfigDependencyName();
}
return $dependencies;
}
/**
* {@inheritdoc}
*/
public function onDependencyRemoval(array $dependencies) {
$changed = parent::onDependencyRemoval($dependencies);
$style_id = $this->getSetting('preview_image_style');
/** @var \Drupal\image\ImageStyleInterface $style */
if ($style_id && $style = ImageStyle::load($style_id)) {
if (!empty($dependencies[$style->getConfigDependencyKey()][$style->getConfigDependencyName()])) {
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = \Drupal::entityManager()->getStorage($style->getEntityTypeId());
$replacement_id = $storage->getReplacementId($style_id);
// If a valid replacement has been provided in the storage, replace the
// preview image style with the replacement.
if ($replacement_id && ImageStyle::load($replacement_id)) {
$this->setSetting('preview_image_style', $replacement_id);
}
// If there's no replacement or the replacement is invalid, disable the
// image preview.
else {
$this->setSetting('preview_image_style', '');
}
// Signal that the formatter plugin settings were updated.
$changed = TRUE;
}
}
return $changed;
}
}

View file

@ -8,6 +8,7 @@
namespace Drupal\image\Tests;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Drupal\node\Entity\Node;
@ -273,6 +274,19 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$this->assertFalse(ImageStyle::load($style_name), format_string('Image style %style successfully deleted.', array('%style' => $style->label())));
// Test empty text when there are no image styles.
// Delete all image styles.
foreach (ImageStyle::loadMultiple() as $image_style) {
$image_style->delete();
}
// Confirm that the empty text is correct on the image styles page.
$this->drupalGet($admin_path);
$this->assertRaw(t('There are currently no styles. <a href=":url">Add a new one</a>.', [
':url' => \Drupal::url('image.style_add'),
]));
}
/**
@ -307,7 +321,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Test that image is displayed using newly created style.
$this->drupalGet('node/' . $nid);
$this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name)));
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', array('@style' => $style_name)));
// Rename the style and make sure the image field is updated.
$new_style_name = strtolower($this->randomMachineName(10));
@ -322,7 +336,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Reload the image style using the new name.
$style = ImageStyle::load($new_style_name);
$this->assertRaw($style->buildUrl($original_uri), 'Image displayed using style replacement style.');
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
// Delete the style and choose a replacement style.
$edit = array(
@ -334,7 +348,7 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$replacement_style = ImageStyle::load('thumbnail');
$this->drupalGet('node/' . $nid);
$this->assertRaw($replacement_style->buildUrl($original_uri), 'Image displayed using style replacement style.');
$this->assertRaw(file_url_transform_relative($replacement_style->buildUrl($original_uri)), 'Image displayed using style replacement style.');
}
/**
@ -441,11 +455,16 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
// Test that image is displayed using newly created style.
$this->drupalGet('node/' . $nid);
$this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name)));
$this->assertRaw(file_url_transform_relative($style->buildUrl($original_uri)), format_string('Image displayed using style @style.', array('@style' => $style_name)));
// Copy config to sync, and delete the image style.
$sync = $this->container->get('config.storage.sync');
$active = $this->container->get('config.storage');
// Remove the image field from the display, to avoid a dependency error
// during import.
EntityViewDisplay::load('node.article.default')
->removeComponent($field_name)
->save();
$this->copyConfig($active, $sync);
$sync->delete('image.style.' . $style_name);
$this->configImporter()->import();

View file

@ -41,7 +41,7 @@ class ImageDimensionsTest extends WebTestBase {
$style = entity_create('image_style', array('name' => 'test', 'label' => 'Test'));
$style->save();
$generated_uri = 'public://styles/test/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$variables = array(
'#theme' => 'image_style',
@ -70,7 +70,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="120" height="60" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -91,7 +91,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="60" height="120" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -113,7 +113,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -135,7 +135,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -153,7 +153,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="45" height="90" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -174,7 +174,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
@ -194,7 +194,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="30" height="30" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -215,7 +215,7 @@ class ImageDimensionsTest extends WebTestBase {
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
@ -251,10 +251,10 @@ class ImageDimensionsTest extends WebTestBase {
];
// PNG original image. Should be resized to 100x100.
$generated_uri = 'public://styles/test_uri/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="100" height="100" alt="" class="image-style-test-uri" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
@ -264,11 +264,11 @@ class ImageDimensionsTest extends WebTestBase {
$file = $files[1];
$original_uri = file_unmanaged_copy($file->uri, 'public://', FILE_EXISTS_RENAME);
$generated_uri = 'public://styles/test_uri/public/'. \Drupal::service('file_system')->basename($original_uri);
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
$variables['#uri'] = $original_uri;
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="50" height="50" alt="" class="image-style-test-uri" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->drupalGet($this->getAbsoluteUrl($url));
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);

View file

@ -127,6 +127,8 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$default_output = '<a href="' . file_create_url($image_uri) . '">' . $renderer->renderRoot($image) . '</a>';
$this->drupalGet('node/' . $nid);
$this->assertCacheTag($file->getCacheTags()[0]);
// @todo Remove in https://www.drupal.org/node/2646744.
$this->assertCacheContext('url.site');
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
$this->assertRaw($default_output, 'Image linked to file formatter displaying correctly on full node view.');
@ -165,7 +167,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
'//a[@href=:path]/img[@src=:url and @alt=:alt and @width=:width and @height=:height]',
array(
':path' => $node->url(),
':url' => file_create_url($image['#uri']),
':url' => file_url_transform_relative(file_create_url($image['#uri'])),
':width' => $image['#width'],
':height' => $image['#height'],
':alt' => $alt,
@ -256,7 +258,7 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
$node = $node_storage->load($nid);
$file = $node->{$field_name}->entity;
$url = file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri()));
$url = file_url_transform_relative(file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri())));
$this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'));
// Add alt/title fields to the image and verify that they are displayed.

View file

@ -66,9 +66,11 @@ abstract class ImageFieldTestBase extends WebTestBase {
* @param array $field_settings
* A list of instance settings that will be added to the instance defaults.
* @param array $widget_settings
* A list of widget settings that will be added to the widget defaults.
* Widget settings to be added to the widget defaults.
* @param array $formatter_settings
* Formatter settings to be added to the formatter defaults.
*/
function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array()) {
function createImageField($name, $type_name, $storage_settings = array(), $field_settings = array(), $widget_settings = array(), $formatter_settings = array()) {
entity_create('field_storage_config', array(
'field_name' => $name,
'entity_type' => 'node',
@ -95,7 +97,10 @@ abstract class ImageFieldTestBase extends WebTestBase {
->save();
entity_get_display('node', $type_name, 'default')
->setComponent($name)
->setComponent($name, array(
'type' => 'image',
'settings' => $formatter_settings,
))
->save();
return $field_config;

View file

@ -0,0 +1,86 @@
<?php
/**
* @file
* Contains \Drupal\image\Tests\ImageStyleDeleteTest.
*/
namespace Drupal\image\Tests;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
/**
* Tests image style deletion using the UI.
*
* @group image
*/
class ImageStyleDeleteTest extends ImageFieldTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Create an image field 'foo' having the image style 'medium' as widget
// preview and as formatter.
$this->createImageField('foo', 'page', [], [], ['preview_image_style' => 'medium'], ['image_style' => 'medium']);
}
/**
* Tests image style deletion.
*/
public function testDelete() {
$this->drupalGet('admin/config/media/image-styles/manage/medium/delete');
// Checks that the 'replacement' select element is displayed.
$this->assertFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
// Delete 'medium' image style but replace it with 'thumbnail'. This style
// is involved in 'node.page.default' display view and form.
$this->drupalPostForm(NULL, ['replacement' => 'thumbnail'], t('Delete'));
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
$view_display = EntityViewDisplay::load('node.page.default');
// Checks that the formatter setting is replaced.
if ($this->assertNotNull($component = $view_display->getComponent('foo'))) {
$this->assertIdentical($component['settings']['image_style'], 'thumbnail');
}
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
$form_display = EntityFormDisplay::load('node.page.default');
// Check that the widget setting is replaced.
if ($this->assertNotNull($component = $form_display->getComponent('foo'))) {
$this->assertIdentical($component['settings']['preview_image_style'], 'thumbnail');
}
$this->drupalGet('admin/config/media/image-styles/manage/thumbnail/delete');
// Checks that the 'replacement' select element is displayed.
$this->assertFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertNoRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
// Delete 'thumbnail' image style. Provide no replacement.
$this->drupalPostForm(NULL, [], t('Delete'));
$view_display = EntityViewDisplay::load('node.page.default');
// Checks that the formatter setting is disabled.
$this->assertNull($view_display->getComponent('foo'));
$this->assertNotNull($view_display->get('hidden')['foo']);
// Checks that widget setting is preserved with the image preview disabled.
$form_display = EntityFormDisplay::load('node.page.default');
$this->assertNotNull($widget = $form_display->getComponent('foo'));
$this->assertIdentical($widget['settings']['preview_image_style'], '');
// Now, there's only one image style configured on the system: 'large'.
$this->drupalGet('admin/config/media/image-styles/manage/large/delete');
// Checks that the 'replacement' select element is not displayed.
$this->assertNoFieldByName('replacement');
// Checks that UI messages are correct.
$this->assertNoRaw(t('If this style is in use on the site, you may select another style to replace it. All images that have been generated for this style will be permanently deleted. If no replacement style is selected, the dependent configurations might need manual reconfiguration.'));
$this->assertRaw(t('All images that have been generated for this style will be permanently deleted. The dependent configurations might need manual reconfiguration.'));
}
}

View file

@ -74,7 +74,7 @@ class ImageThemeFunctionTest extends WebTestBase {
// Create a style.
$style = entity_create('image_style', array('name' => 'test', 'label' => 'Test'));
$style->save();
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
// Create a test entity with the image field set.
$entity = entity_create('entity_test');
@ -136,7 +136,7 @@ class ImageThemeFunctionTest extends WebTestBase {
// Create a style.
$style = entity_create('image_style', array('name' => 'image_test', 'label' => 'Test'));
$style->save();
$url = $style->buildUrl($original_uri);
$url = file_url_transform_relative($style->buildUrl($original_uri));
// Create the base element that we'll use in the tests below.
$base_element = array(

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Contains \Drupal\image\Tests\Update\ImageUpdateTest.
*/
namespace Drupal\image\Tests\Update;
use Drupal\system\Tests\Update\UpdatePathTestBase;
/**
* Tests Image update path.
*
* @group image
*/
class ImageUpdateTest extends UpdatePathTestBase {
/**
* {@inheritdoc}
*/
protected function setDatabaseDumpFiles() {
$this->databaseDumpFiles = [
__DIR__ . '/../../../../system/tests/fixtures/update/drupal-8-rc1.bare.standard.php.gz',
];
}
/**
* Tests image_post_update_image_style_dependencies().
*
* @see image_post_update_image_style_dependencies()
*/
public function testPostUpdateImageStylesDependencies() {
$view = 'core.entity_view_display.node.article.default';
$form = 'core.entity_form_display.node.article.default';
// Check that view display 'node.article.default' doesn't depend on image
// style 'image.style.large'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertFalse(in_array('image.style.large', $dependencies));
// Check that form display 'node.article.default' doesn't depend on image
// style 'image.style.thumbnail'.
$dependencies = $this->config($form)->get('dependencies.config');
$this->assertFalse(in_array('image.style.thumbnail', $dependencies));
// Run updates.
$this->runUpdates();
// Check that view display 'node.article.default' depend on image style
// 'image.style.large'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertTrue(in_array('image.style.large', $dependencies));
// Check that form display 'node.article.default' depend on image style
// 'image.style.thumbnail'.
$dependencies = $this->config($view)->get('dependencies.config');
$this->assertTrue(in_array('image.style.large', $dependencies));
}
}

View file

@ -0,0 +1,117 @@
<?php
/**
* @file
* Contains \Drupal\Tests\image\Kernel\ImageStyleIntegrationTest.
*/
namespace Drupal\Tests\image\Kernel;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\image\Entity\ImageStyle;
use Drupal\KernelTests\KernelTestBase;
use Drupal\node\Entity\NodeType;
/**
* Tests the integration of ImageStyle with the core.
*
* @group image
*/
class ImageStyleIntegrationTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['image', 'file', 'field', 'system', 'user', 'node'];
/**
* Tests the dependency between ImageStyle and entity display components.
*/
public function testEntityDisplayDependency() {
// Create two image styles.
/** @var \Drupal\image\ImageStyleInterface $style */
$style = ImageStyle::create(['name' => 'main_style']);
$style->save();
/** @var \Drupal\image\ImageStyleInterface $replacement */
$replacement = ImageStyle::create(['name' => 'replacement_style']);
$replacement->save();
// Create a node-type, named 'note'.
$node_type = NodeType::create(['type' => 'note']);
$node_type->save();
// Create an image field and attach it to the 'note' node-type.
FieldStorageConfig::create([
'entity_type' => 'node',
'field_name' => 'sticker',
'type' => 'image',
])->save();
FieldConfig::create([
'entity_type' => 'node',
'field_name' => 'sticker',
'bundle' => 'note',
])->save();
// Create the default entity view display and set the 'sticker' field to use
// the 'main_style' images style in formatter.
/** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
$view_display = EntityViewDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'note',
'mode' => 'default',
'status' => TRUE,
])->setComponent('sticker', ['settings' => ['image_style' => 'main_style']]);
$view_display->save();
// Create the default entity form display and set the 'sticker' field to use
// the 'main_style' images style in the widget.
/** @var \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display */
$form_display = EntityFormDisplay::create([
'targetEntityType' => 'node',
'bundle' => 'note',
'mode' => 'default',
'status' => TRUE,
])->setComponent('sticker', ['settings' => ['preview_image_style' => 'main_style']]);
$form_display->save();
// Check that the entity displays exists before dependency removal.
$this->assertNotNull(EntityViewDisplay::load($view_display->id()));
$this->assertNotNull(EntityFormDisplay::load($form_display->id()));
// Delete the 'main_style' image style. Before that, emulate the UI process
// of selecting a replacement style by setting the replacement image style
// ID in the image style storage.
/** @var \Drupal\image\ImageStyleStorageInterface $storage */
$storage = $this->container->get('entity.manager')->getStorage($style->getEntityTypeId());
$storage->setReplacementId('main_style', 'replacement_style');
$style->delete();
// Check that the entity displays exists after dependency removal.
$this->assertNotNull($view_display = EntityViewDisplay::load($view_display->id()));
$this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id()));
// Check that the 'sticker' formatter component exists in both displays.
$this->assertNotNull($formatter = $view_display->getComponent('sticker'));
$this->assertNotNull($widget = $form_display->getComponent('sticker'));
// Check that both displays are using now 'replacement_style' for images.
$this->assertSame('replacement_style', $formatter['settings']['image_style']);
$this->assertSame('replacement_style', $widget['settings']['preview_image_style']);
// Delete the 'replacement_style' without setting a replacement image style.
$replacement->delete();
// The entity view and form displays exists after dependency removal.
$this->assertNotNull($view_display = EntityViewDisplay::load($view_display->id()));
$this->assertNotNull($form_display = EntityFormDisplay::load($form_display->id()));
// The 'sticker' formatter component should be hidden in view display.
$this->assertNull($view_display->getComponent('sticker'));
$this->assertTrue($view_display->get('hidden')['sticker']);
// The 'sticker' widget component should be active in form displays, but the
// image preview should be disabled.
$this->assertNotNull($widget = $form_display->getComponent('sticker'));
$this->assertSame('', $widget['settings']['preview_image_style']);
}
}

View file

@ -101,6 +101,10 @@ class LinkFieldTest extends WebTestBase {
// strings displayed to the user).
$valid_external_entries = array(
'http://www.example.com/' => 'http://www.example.com/',
// Strings within parenthesis without leading space char.
'http://www.example.com/strings_(string_within_parenthesis)' => 'http://www.example.com/strings_(string_within_parenthesis)',
// Numbers within parenthesis without leading space char.
'http://www.example.com/numbers_(9999)' => 'http://www.example.com/numbers_(9999)',
);
$valid_internal_entries = array(
'/entity_test/add' => '/entity_test/add',

View file

@ -0,0 +1,102 @@
<?php
/**
* @file
* Contains \Drupal\link\Tests\Views\LinkViewsTokensTest.
*/
namespace Drupal\link\Tests\Views;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Tests\ViewTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the views integration for link tokens.
*
* @group link
*/
class LinkViewsTokensTest extends ViewTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = ['link_test_views'];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_link_tokens'];
/**
* The field name used for the link field.
*
* @var string
*/
protected $fieldName = 'field_link';
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
ViewTestData::createTestViews(get_class($this), array('link_test_views'));
// Create Basic page node type.
$this->drupalCreateContentType(array(
'type' => 'page',
'name' => 'Basic page'
));
// Create a field.
FieldStorageConfig::create(array(
'field_name' => $this->fieldName,
'type' => 'link',
'entity_type' => 'node',
'cardinality' => 1,
))->save();
FieldConfig::create(array(
'field_name' => $this->fieldName,
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'link field',
))->save();
}
public function testLinkViewsTokens() {
// Array of URI's to test.
$uris = [
'http://www.drupal.org' => 'Drupal.org',
];
// Add nodes with the URI's and titles.
foreach ($uris as $uri => $title) {
$values = array('type' => 'page');
$values[$this->fieldName][] = ['uri' => $uri, 'title' => $title, 'options' => ['attributes' => ['class' => 'test-link-class']]];
$this->drupalCreateNode($values);
}
$this->drupalGet('test_link_tokens');
foreach ($uris as $uri => $title) {
// Formatted link: {{ field_link }}<br />
$this->assertRaw("Formated: <a href=\"$uri\" class=\"test-link-class\">$title</a>");
// Raw uri: {{ field_link__uri }}<br />
$this->assertRaw("Raw uri: $uri");
// Raw title: {{ field_link__title }}<br />
$this->assertRaw("Raw title: $title");
// Raw options: {{ field_link__options }}<br />
// Options is an array and should return empty after token replace.
$this->assertRaw("Raw options: .");
}
}
}

View file

@ -0,0 +1,10 @@
name: 'Link test views'
type: module
description: 'Provides default views for views link tests.'
package: Testing
version: VERSION
core: 8.x
dependencies:
- node
- views
- link

View file

@ -0,0 +1,206 @@
langcode: en
status: true
dependencies:
config:
- field.storage.node.field_link
module:
- link
- node
- user
id: test_link_tokens
label: link
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: 8.x
display:
default:
display_plugin: default
id: default
display_title: Master
position: 0
display_options:
access:
type: perm
options:
perm: 'access content'
cache:
type: tag
options: { }
query:
type: views_query
options:
disable_sql_rewrite: false
distinct: false
replica: false
query_comment: ''
query_tags: { }
exposed_form:
type: basic
options:
submit_button: Apply
reset_button: false
reset_button_label: Reset
exposed_sorts_label: 'Sort by'
expose_sort_order: true
sort_asc_label: Asc
sort_desc_label: Desc
pager:
type: full
options:
items_per_page: 10
offset: 0
id: 0
total_pages: null
expose:
items_per_page: false
items_per_page_label: 'Items per page'
items_per_page_options: '5, 10, 25, 50'
items_per_page_options_all: false
items_per_page_options_all_label: '- All -'
offset: false
offset_label: Offset
tags:
previous: ' previous'
next: 'next '
first: '« first'
last: 'last »'
quantity: 9
style:
type: default
row:
type: fields
options:
default_field_elements: true
inline: { }
separator: ''
hide_empty: false
fields:
field_link:
id: field_link
table: node__field_link
field: field_link
relationship: none
group_type: group
admin_label: ''
label: ''
exclude: false
alter:
alter_text: true
text: "Formated: {{ field_link }}<br />\nRaw uri: {{ field_link__uri }}<br />\nRaw title: {{ field_link__title }}<br />\nRaw options: {{ field_link__options }}."
make_link: false
path: '{{ field_link__uri }}'
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: false
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: uri
type: link
settings:
trim_length: 80
url_only: false
url_plain: false
rel: '0'
target: '0'
group_column: ''
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
plugin_id: field
filters:
status:
value: true
table: node_field_data
field: status
plugin_id: boolean
entity_type: node
entity_field: status
id: status
expose:
operator: ''
group: 1
sorts:
created:
id: created
table: node_field_data
field: created
order: DESC
entity_type: node
entity_field: created
plugin_id: date
relationship: none
group_type: group
admin_label: ''
exposed: false
expose:
label: ''
granularity: second
title: link
header: { }
footer: { }
empty: { }
relationships: { }
arguments: { }
display_extenders: { }
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
cacheable: false
page_1:
display_plugin: page
id: page_1
display_title: Page
position: 1
display_options:
display_extenders: { }
path: test_link_tokens
cache_metadata:
contexts:
- 'languages:language_content'
- 'languages:language_interface'
- url.query_args
- 'user.node_grants:view'
- user.permissions
cacheable: false

View file

@ -292,8 +292,6 @@ class MenuForm extends EntityForm {
$form['links'][$id]['#attributes'] = $element['#attributes'];
$form['links'][$id]['#attributes']['class'][] = 'draggable';
$form['links'][$id]['#item'] = $element['#item'];
// TableDrag: Sort the table row according to its existing/configured weight.
$form['links'][$id]['#weight'] = $element['#item']->link->getWeight();

View file

@ -34054,7 +34054,7 @@ $connection->insert('vocabulary')
'hierarchy' => '1',
'multiple' => '1',
'required' => '0',
'tags' => '0',
'tags' => '1',
'module' => 'taxonomy',
'weight' => '5',
))
@ -34079,7 +34079,7 @@ $connection->insert('vocabulary')
'relations' => '1',
'hierarchy' => '0',
'multiple' => '0',
'required' => '0',
'required' => '1',
'tags' => '0',
'module' => 'taxonomy',
'weight' => '0',

View file

@ -4330,6 +4330,33 @@ $connection->schema()->createTable('field_data_field_file', array(
'mysql_character_set' => 'utf8',
));
$connection->insert('field_data_field_file')
->fields(array(
'entity_type',
'bundle',
'deleted',
'entity_id',
'revision_id',
'language',
'delta',
'field_file_fid',
'field_file_display',
'field_file_description',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'field_file_fid' => '2',
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->execute();
$connection->schema()->createTable('field_data_field_float', array(
'fields' => array(
'entity_type' => array(
@ -6065,6 +6092,33 @@ $connection->schema()->createTable('field_revision_field_file', array(
'mysql_character_set' => 'utf8',
));
$connection->insert('field_revision_field_file')
->fields(array(
'entity_type',
'bundle',
'deleted',
'entity_id',
'revision_id',
'language',
'delta',
'field_file_fid',
'field_file_display',
'field_file_description',
))
->values(array(
'entity_type' => 'node',
'bundle' => 'test_content_type',
'deleted' => '0',
'entity_id' => '1',
'revision_id' => '1',
'language' => 'und',
'delta' => '0',
'field_file_fid' => '2',
'field_file_display' => '1',
'field_file_description' => 'file desc',
))
->execute();
$connection->schema()->createTable('field_revision_field_float', array(
'fields' => array(
'entity_type' => array(
@ -7337,6 +7391,13 @@ $connection->insert('file_usage')
'module' => 'file',
'type' => 'node',
'id' => '1',
'count' => '2',
))
->values(array(
'fid' => '2',
'module' => 'file',
'type' => 'node',
'id' => '1',
'count' => '1',
))
->execute();

View file

@ -883,7 +883,9 @@ function node_form_system_themes_admin_form_submit($form, FormStateInterface $fo
* with node access to ensure only nodes to which the user has access are
* retrieved, through the use of hook_query_TAG_alter(). See the
* @link entity_api Entity API topic @endlink for more information on entity
* queries.
* queries. Tagging a query with "node_access" does not check the
* published/unpublished status of nodes, so the base query is responsible
* for ensuring that unpublished nodes are not displayed to inappropriate users.
*
* Note: Even a single module returning an AccessResultInterface object from
* hook_node_access() whose isForbidden() method equals TRUE will block access

View file

@ -65,6 +65,11 @@ class NodeGrantDatabaseStorage implements NodeGrantDatabaseStorageInterface {
* {@inheritdoc}
*/
public function access(NodeInterface $node, $operation, AccountInterface $account) {
// Grants only support these operations.
if (!in_array($operation, ['view', 'update', 'delete'])) {
return AccessResult::neutral();
}
// If no module implements the hook or the node does not have an id there is
// no point in querying the database for access grants.
if (!$this->moduleHandler->getImplementations('node_grants') || !$node->id()) {

View file

@ -39,6 +39,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
$this->installEntitySchema('node');
$this->installEntitySchema('comment');
$this->installEntitySchema('taxonomy_term');
$this->installEntitySchema('file');
$this->installConfig(static::$modules);
$this->installSchema('node', ['node_access']);
$this->installSchema('system', ['sequences']);
@ -51,6 +52,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
'd7_field',
'd7_field_instance',
'd7_node__test_content_type',
'd7_node__article',
]);
}
@ -134,6 +136,17 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
$this->assertIdentical('Some more text', $node->field_text_list[0]->value);
$this->assertIdentical('7', $node->field_integer_list[0]->value);
$this->assertIdentical('qwerty', $node->field_text->value);
$this->assertIdentical('2', $node->field_file->target_id);
$this->assertIdentical('file desc', $node->field_file->description);
$this->assertTrue($node->field_file->display);
$this->assertIdentical('1', $node->field_images->target_id);
$this->assertIdentical('alt text', $node->field_images->alt);
$this->assertIdentical('title text', $node->field_images->title);
$this->assertIdentical('93', $node->field_images->width);
$this->assertIdentical('93', $node->field_images->height);
$node = Node::load(2);
$this->assertIdentical("...is that it's the absolute best show ever. Trust me, I would know.", $node->body->value);
}
}

View file

@ -26,4 +26,13 @@ class NodeAccessGrantsTest extends NodeAccessTest {
*/
public static $modules = array('node_access_test_empty');
/**
* Test operations not supported by node grants.
*/
function testUnsupportedOperation() {
$web_user = $this->drupalCreateUser(['access content']);
$node = $this->drupalCreateNode();
$this->assertNodeAccess(['random_operation' => FALSE], $node, $web_user);
}
}

View file

@ -80,17 +80,17 @@ class NodeAttributesTest extends NodeTestBase {
'lang' => 'en',
);
$this->assertTrue($graph->hasProperty($node_uri, 'http://purl.org/dc/terms/title', $expected_value), 'Node title found in RDF output (dc:title).');
// Node date.
// Node date (date format must be UTC).
$expected_value = array(
'type' => 'literal',
'value' => date('c', $node->getCreatedTime()),
'value' => \Drupal::service('date.formatter')->format($node->getCreatedTime(), 'custom', 'c', 'UTC'),
'datatype' => 'http://www.w3.org/2001/XMLSchema#dateTime',
);
$this->assertTrue($graph->hasProperty($node_uri, 'http://purl.org/dc/terms/date', $expected_value), 'Node date found in RDF output (dc:date).');
// Node date.
// Node date (date format must be UTC).
$expected_value = array(
'type' => 'literal',
'value' => date('c', $node->getCreatedTime()),
'value' => \Drupal::service('date.formatter')->format($node->getCreatedTime(), 'custom', 'c', 'UTC'),
'datatype' => 'http://www.w3.org/2001/XMLSchema#dateTime',
);
$this->assertTrue($graph->hasProperty($node_uri, 'http://purl.org/dc/terms/created', $expected_value), 'Node date found in RDF output (dc:created).');

View file

@ -391,7 +391,7 @@ function responsive_image_build_source_attributes(ImageInterface $image, array $
// Use the image width as key so we can sort the array later on.
// Images within a srcset should be sorted from small to large, since
// the first matching source will be used.
$srcset[intval($dimensions['width'])] = file_create_url(_responsive_image_image_style_url($image_style_name, $image->getSource())) . ' ' . $dimensions['width'] . 'w';
$srcset[intval($dimensions['width'])] = _responsive_image_image_style_url($image_style_name, $image->getSource()) . ' ' . $dimensions['width'] . 'w';
$sizes = array_merge(explode(',', $image_style_mapping['image_mapping']['sizes']), $sizes);
}
break;
@ -405,7 +405,7 @@ function responsive_image_build_source_attributes(ImageInterface $image, array $
// be sorted from small to large, since the first matching source will
// be used. We multiply it by 100 so multipliers with up to two decimals
// can be used.
$srcset[intval(Unicode::substr($multiplier, 0, -1) * 100)] = file_create_url(_responsive_image_image_style_url($image_style_mapping['image_mapping'], $image->getSource())) . ' ' . $multiplier;
$srcset[intval(Unicode::substr($multiplier, 0, -1) * 100)] = _responsive_image_image_style_url($image_style_mapping['image_mapping'], $image->getSource()) . ' ' . $multiplier;
break;
}
}
@ -495,9 +495,9 @@ function _responsive_image_image_style_url($style_name, $path) {
}
$entity = ImageStyle::load($style_name);
if ($entity instanceof Drupal\image\Entity\ImageStyle) {
return $entity->buildUrl($path);
return file_url_transform_relative($entity->buildUrl($path));
}
return file_create_url($path);
return file_url_transform_relative(file_create_url($path));
}
/**

View file

@ -230,7 +230,7 @@ class ResponsiveImageFormatter extends ImageFormatterBase implements ContainerFa
foreach ($files as $delta => $file) {
// Link the <picture> element to the original file.
if (isset($link_file)) {
$url = Url::fromUri(file_create_url($file->getFileUri()));
$url = file_url_transform_relative(file_create_url($file->getFileUri()));
}
// Extract field item attributes for the theme function, and unset them
// from the $item so that the field template does not re-render them.

View file

@ -244,7 +244,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
$display->setComponent($field_name, $display_options)
->save();
$default_output = '<a href="' . file_create_url($image_uri) . '"><picture';
$default_output = '<a href="' . file_url_transform_relative(file_create_url($image_uri)) . '"><picture';
$this->drupalGet('node/' . $nid);
$cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
$this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
@ -289,10 +289,10 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
$this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==');
$thumbnail_style = ImageStyle::load('thumbnail');
// Assert the output of the 'srcset' attribute (small multipliers first).
$this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . $thumbnail_style->buildUrl($image_uri) . ' 1.5x');
$this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . file_url_transform_relative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x');
$this->assertRaw('/styles/medium/');
// Assert the output of the original image.
$this->assertRaw(file_create_url($image_uri) . ' 3x');
$this->assertRaw(file_url_transform_relative(file_create_url($image_uri)) . ' 3x');
// Assert the output of the breakpoints.
$this->assertRaw('media="(min-width: 0px)"');
$this->assertRaw('media="(min-width: 560px)"');
@ -301,7 +301,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
$this->assertPattern('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/');
// Assert the output of the 'srcset' attribute (small images first).
$medium_style = ImageStyle::load('medium');
$this->assertRaw($medium_style->buildUrl($image_uri) . ' 220w, ' . $large_style->buildUrl($image_uri) . ' 360w');
$this->assertRaw(file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 220w, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 360w');
$this->assertRaw('media="(min-width: 851px)"');
}
$this->assertRaw('/styles/large/');
@ -321,7 +321,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
'#alt' => $alt,
'#srcset' => array(
array(
'uri' => $large_style->buildUrl($image->getSource()),
'uri' => file_url_transform_relative($large_style->buildUrl($image->getSource())),
),
),
);
@ -403,7 +403,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
$thumbnail_style = ImageStyle::load('thumbnail');
$node = $node_storage->load($nid);
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$this->assertPattern('/srcset="' . preg_quote($thumbnail_style->buildUrl($image_uri), '/') . ' 1x".+?media="\(min-width: 0px\)"/');
$this->assertPattern('/srcset="' . preg_quote(file_url_transform_relative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/');
}
/**
@ -449,7 +449,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
$medium_style = ImageStyle::load('medium');
$node = $node_storage->load($nid);
$image_uri = File::load($node->{$field_name}->target_id)->getFileUri();
$this->assertRaw('<img srcset="' . $medium_style->buildUrl($image_uri) . ' 1x, ' . $large_style->buildUrl($image_uri) . ' 2x"');
$this->assertRaw('<img srcset="' . file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 1x, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 2x"');
}
/**
@ -509,7 +509,7 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
switch ($link_type) {
case 'file':
// Make sure the link to the file is present.
$this->assertPattern('/<a(.*?)href="' . preg_quote(file_create_url($image_uri), '/') . '"(.*?)><picture/');
$this->assertPattern('/<a(.*?)href="' . preg_quote(file_url_transform_relative(file_create_url($image_uri)), '/') . '"(.*?)><picture/');
break;
case 'content':

View file

@ -33,7 +33,7 @@ class EntityDeriver implements ContainerDeriverInterface {
protected $entityManager;
/**
* Constructs an EntityDerivative object.
* Constructs an EntityDeriver object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
@ -86,7 +86,7 @@ class EntityDeriver implements ContainerDeriverInterface {
// Check if there are link templates defined for the entity type and
// use the path from the route instead of the default.
if ($link_template = $entity_type->getLinkTemplate($link_relation)) {
$this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = '/' . $link_template;
$this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $link_template;
}
else {
$this->derivatives[$entity_type_id]['uri_paths'][$link_relation] = $default_uri;

View file

@ -29,7 +29,7 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
* }
* )
*
* @see \Drupal\rest\Plugin\Derivative\EntityDerivative
* @see \Drupal\rest\Plugin\Deriver\EntityDeriver
*/
class EntityResource extends ResourceBase {

View file

@ -88,6 +88,10 @@ class DataFieldRow extends RowPluginBase {
if ($fields = $this->view->display_handler->getOption('fields')) {
foreach ($fields as $id => $field) {
// Don't show the field if it has been excluded.
if (!empty($field['exclude'])) {
continue;
}
$form['field_options'][$id]['field'] = array(
'#markup' => $id,
);
@ -138,6 +142,10 @@ class DataFieldRow extends RowPluginBase {
$output = array();
foreach ($this->view->field as $id => $field) {
// Don't render anything if this field is excluded.
if (!empty($field->options['exclude'])) {
continue;
}
// If the raw output option has been set, just get the raw value.
if (!empty($this->rawOutputOptions[$id])) {
$value = $field->getValue($row);

View file

@ -106,4 +106,19 @@ class ResourceTest extends RESTTestBase {
$this->curlClose();
}
/**
* Tests that resource URI paths are formatted properly.
*/
public function testUriPaths() {
$this->enableService('entity:entity_test');
/** @var \Drupal\rest\Plugin\Type\ResourcePluginManager $manager */
$manager = \Drupal::service('plugin.manager.rest');
foreach ($manager->getDefinitions() as $resource => $definition) {
foreach ($definition['uri_paths'] as $key => $uri_path) {
$this->assertFalse(strpos($uri_path, '//'), 'The resource URI path does not have duplicate slashes.');
}
}
}
}

View file

@ -470,6 +470,32 @@ class StyleSerializerTest extends PluginTestBase {
$this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
$this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
}
// Test result with an excluded field.
$view->setDisplay('rest_export_1');
$view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
'name' => [
'id' => 'name',
'table' => 'views_test_data',
'field' => 'name',
'relationship' => 'none',
],
'created' => [
'id' => 'created',
'exclude' => TRUE,
'table' => 'views_test_data',
'field' => 'created',
'relationship' => 'none',
],
]);
$view->save();
$this->executeView($view);
foreach ($this->drupalGetJSON('test/serialize/field') as $index => $values) {
$this->assertTrue(!isset($values['created']), 'Excluded value not found.');
}
// Test that the excluded field is not shown in the row options.
$this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
$this->assertNoText('created');
}
/**

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\BlockCreationTrait.
*/
namespace Drupal\simpletest;
use Drupal\block\Entity\Block;
/**
* Provides methods to create and place block with default settings.
*
* This trait is meant to be used only by test classes.
*/
trait BlockCreationTrait {
/**
* Creates a block instance based on default settings.
*
* @param string $plugin_id
* The plugin ID of the block type for this block instance.
* @param array $settings
* (optional) An associative array of settings for the block entity.
* Override the defaults by specifying the key and value in the array, for
* example:
* @code
* $this->drupalPlaceBlock('system_powered_by_block', array(
* 'label' => t('Hello, world!'),
* ));
* @endcode
* The following defaults are provided:
* - label: Random string.
* - ID: Random string.
* - region: 'sidebar_first'.
* - theme: The default theme.
* - visibility: Empty array.
*
* @return \Drupal\block\Entity\Block
* The block entity.
*
* @todo
* Add support for creating custom block instances.
*/
protected function placeBlock($plugin_id, array $settings = array()) {
$config = \Drupal::configFactory();
$settings += array(
'plugin' => $plugin_id,
'region' => 'sidebar_first',
'id' => strtolower($this->randomMachineName(8)),
'theme' => $config->get('system.theme')->get('default'),
'label' => $this->randomMachineName(8),
'visibility' => array(),
'weight' => 0,
);
$values = [];
foreach (array('region', 'id', 'theme', 'plugin', 'weight', 'visibility') as $key) {
$values[$key] = $settings[$key];
// Remove extra values that do not belong in the settings array.
unset($settings[$key]);
}
foreach ($values['visibility'] as $id => $visibility) {
$values['visibility'][$id]['id'] = $id;
}
$values['settings'] = $settings;
$block = Block::create($values);
$block->save();
return $block;
}
}

View file

@ -0,0 +1,58 @@
<?php
/**
* @file
* Contains Drupal\simpletest\ContentTypeCreationTrait.
*/
namespace Drupal\simpletest;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\node\Entity\NodeType;
/**
* Provides methods to create content type from given values.
*
* This trait is meant to be used only by test classes.
*/
trait ContentTypeCreationTrait {
/**
* Creates a custom content type based on default settings.
*
* @param array $values
* An array of settings to change from the defaults.
* Example: 'type' => 'foo'.
*
* @return \Drupal\node\Entity\NodeType
* Created content type.
*/
protected function createContentType(array $values = array()) {
// Find a non-existent random type name.
if (!isset($values['type'])) {
do {
$id = strtolower($this->randomMachineName(8));
} while (NodeType::load($id));
}
else {
$id = $values['type'];
}
$values += array(
'type' => $id,
'name' => $id,
);
$type = NodeType::create($values);
$status = $type->save();
node_add_body_field($type);
\Drupal::service('router.builder')->rebuild();
if ($this instanceof \PHPUnit_Framework_TestCase) {
$this->assertSame($status, SAVED_NEW, (new FormattableMarkup('Created content type %type.', array('%type' => $type->id())))->__toString());
}
else {
$this->assertEqual($status, SAVED_NEW, (new FormattableMarkup('Created content type %type.', array('%type' => $type->id())))->__toString());
}
return $type;
}
}

View file

@ -0,0 +1,88 @@
<?php
/**
* @file
* Contains Drupal\simpletest\NodeCreationTrait
*/
namespace Drupal\simpletest;
use Drupal\node\Entity\Node;
/**
* Provides methods to create node based on default settings.
*
* This trait is meant to be used only by test classes.
*/
trait NodeCreationTrait {
/**
* Get a node from the database based on its title.
*
* @param string|\Drupal\Component\Render\MarkupInterface $title
* A node title, usually generated by $this->randomMachineName().
* @param $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\node\NodeInterface
* A node entity matching $title.
*/
function getNodeByTitle($title, $reset = FALSE) {
if ($reset) {
\Drupal::entityTypeManager()->getStorage('node')->resetCache();
}
// Cast MarkupInterface objects to string.
$title = (string) $title;
$nodes = \Drupal::entityTypeManager()
->getStorage('node')
->loadByProperties(['title' => $title]);
// Load the first node returned from the database.
$returned_node = reset($nodes);
return $returned_node;
}
/**
* Creates a node based on default settings.
*
* @param array $settings
* (optional) An associative array of settings for the node, as used in
* entity_create(). Override the defaults by specifying the key and value
* in the array, for example:
* @code
* $this->drupalCreateNode(array(
* 'title' => t('Hello, world!'),
* 'type' => 'article',
* ));
* @endcode
* The following defaults are provided:
* - body: Random string using the default filter format:
* @code
* $settings['body'][0] = array(
* 'value' => $this->randomMachineName(32),
* 'format' => filter_default_format(),
* );
* @endcode
* - title: Random string.
* - type: 'page'.
* - uid: The currently logged in user, or anonymous.
*
* @return \Drupal\node\NodeInterface
* The created node entity.
*/
protected function createNode(array $settings = array()) {
// Populate defaults array.
$settings += array(
'body' => array(array(
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
)),
'title' => $this->randomMachineName(8),
'type' => 'page',
'uid' => \Drupal::currentUser()->id(),
);
$node = Node::create($settings);
$node->save();
return $node;
}
}

View file

@ -333,7 +333,7 @@ EOD;
$assertion['file'] = $this->asText($row->td[2]);
$assertion['line'] = $this->asText($row->td[3]);
$assertion['function'] = $this->asText($row->td[4]);
$ok_url = file_create_url('core/misc/icons/73b355/check.svg');
$ok_url = file_url_transform_relative(file_create_url('core/misc/icons/73b355/check.svg'));
$assertion['status'] = ($row->td[5]->img['src'] == $ok_url) ? 'Pass' : 'Fail';
$results['assertions'][] = $assertion;
}

View file

@ -0,0 +1,42 @@
<?php
/**
* @file
* Contains \Drupal\simpletest\Tests\TimeZoneTest.
*/
namespace Drupal\simpletest\Tests;
use Drupal\simpletest\WebTestBase;
/**
* This test will check SimpleTest's default time zone handling.
*
* @group simpletest
*/
class TimeZoneTest extends WebTestBase {
/**
* A user with administrative privileges.
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->adminUser = $this->drupalCreateUser(['administer site configuration']);
}
/**
* Tests that user accounts have the default time zone set.
*/
function testAccountTimeZones() {
$expected = 'Australia/Sydney';
$this->assertEqual($this->rootUser->getTimeZone(), $expected, 'Root user has correct time zone.');
$this->assertEqual($this->adminUser->getTimeZone(), $expected, 'Admin user has correct time zone.');
}
}

View file

@ -29,7 +29,6 @@ use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Url;
use Drupal\node\Entity\NodeType;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Zend\Diactoros\Uri;
@ -42,7 +41,16 @@ use Zend\Diactoros\Uri;
abstract class WebTestBase extends TestBase {
use AssertContentTrait;
use BlockCreationTrait {
placeBlock as drupalPlaceBlock;
}
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
}
use UserCreationTrait {
createUser as drupalCreateUser;
createRole as drupalCreateRole;
@ -230,108 +238,6 @@ abstract class WebTestBase extends TestBase {
$this->classLoader = require DRUPAL_ROOT . '/autoload.php';
}
/**
* Get a node from the database based on its title.
*
* @param string|\Drupal\Component\Render\MarkupInterface $title
* A node title, usually generated by $this->randomMachineName().
* @param $reset
* (optional) Whether to reset the entity cache.
*
* @return \Drupal\node\NodeInterface
* A node entity matching $title.
*/
function drupalGetNodeByTitle($title, $reset = FALSE) {
if ($reset) {
\Drupal::entityManager()->getStorage('node')->resetCache();
}
// Cast MarkupInterface objects to string.
$title = (string) $title;
$nodes = entity_load_multiple_by_properties('node', array('title' => $title));
// Load the first node returned from the database.
$returned_node = reset($nodes);
return $returned_node;
}
/**
* Creates a node based on default settings.
*
* @param array $settings
* (optional) An associative array of settings for the node, as used in
* entity_create(). Override the defaults by specifying the key and value
* in the array, for example:
* @code
* $this->drupalCreateNode(array(
* 'title' => t('Hello, world!'),
* 'type' => 'article',
* ));
* @endcode
* The following defaults are provided:
* - body: Random string using the default filter format:
* @code
* $settings['body'][0] = array(
* 'value' => $this->randomMachineName(32),
* 'format' => filter_default_format(),
* );
* @endcode
* - title: Random string.
* - type: 'page'.
* - uid: The currently logged in user, or anonymous.
*
* @return \Drupal\node\NodeInterface
* The created node entity.
*/
protected function drupalCreateNode(array $settings = array()) {
// Populate defaults array.
$settings += array(
'body' => array(array(
'value' => $this->randomMachineName(32),
'format' => filter_default_format(),
)),
'title' => $this->randomMachineName(8),
'type' => 'page',
'uid' => \Drupal::currentUser()->id(),
);
$node = entity_create('node', $settings);
$node->save();
return $node;
}
/**
* Creates a custom content type based on default settings.
*
* @param array $values
* An array of settings to change from the defaults.
* Example: 'type' => 'foo'.
*
* @return \Drupal\node\Entity\NodeType
* Created content type.
*/
protected function drupalCreateContentType(array $values = array()) {
// Find a non-existent random type name.
if (!isset($values['type'])) {
do {
$id = strtolower($this->randomMachineName(8));
} while (NodeType::load($id));
}
else {
$id = $values['type'];
}
$values += array(
'type' => $id,
'name' => $id,
);
$type = entity_create('node_type', $values);
$status = $type->save();
node_add_body_field($type);
\Drupal::service('router.builder')->rebuild();
$this->assertEqual($status, SAVED_NEW, SafeMarkup::format('Created content type %type.', array('%type' => $type->id())));
return $type;
}
/**
* Builds the renderable view of an entity.
*
@ -390,58 +296,6 @@ abstract class WebTestBase extends TestBase {
return $build;
}
/**
* Creates a block instance based on default settings.
*
* @param string $plugin_id
* The plugin ID of the block type for this block instance.
* @param array $settings
* (optional) An associative array of settings for the block entity.
* Override the defaults by specifying the key and value in the array, for
* example:
* @code
* $this->drupalPlaceBlock('system_powered_by_block', array(
* 'label' => t('Hello, world!'),
* ));
* @endcode
* The following defaults are provided:
* - label: Random string.
* - ID: Random string.
* - region: 'sidebar_first'.
* - theme: The default theme.
* - visibility: Empty array.
*
* @return \Drupal\block\Entity\Block
* The block entity.
*
* @todo
* Add support for creating custom block instances.
*/
protected function drupalPlaceBlock($plugin_id, array $settings = array()) {
$settings += array(
'plugin' => $plugin_id,
'region' => 'sidebar_first',
'id' => strtolower($this->randomMachineName(8)),
'theme' => $this->config('system.theme')->get('default'),
'label' => $this->randomMachineName(8),
'visibility' => array(),
'weight' => 0,
);
$values = [];
foreach (array('region', 'id', 'theme', 'plugin', 'weight', 'visibility') as $key) {
$values[$key] = $settings[$key];
// Remove extra values that do not belong in the settings array.
unset($settings[$key]);
}
foreach ($values['visibility'] as $id => $visibility) {
$values['visibility'][$id]['id'] = $id;
}
$values['settings'] = $settings;
$block = entity_create('block', $values);
$block->save();
return $block;
}
/**
* Checks to see whether a block appears on the page.
*
@ -674,6 +528,14 @@ abstract class WebTestBase extends TestBase {
* being executed.
*/
protected function setUp() {
// Set an explicit time zone to not rely on the system one, which may vary
// from setup to setup. The Australia/Sydney time zone is chosen so all
// tests are run using an edge case scenario (UTC+10 and DST). This choice
// is made to prevent time zone related regressions and reduce the
// fragility of the testing system in general. This is also set in config in
// \Drupal\simpletest\WebTestBase::initConfig().
date_default_timezone_set('Australia/Sydney');
// Preserve original batch for later restoration.
$this->setBatch();
@ -983,6 +845,7 @@ abstract class WebTestBase extends TestBase {
'name' => 'admin',
'mail' => 'admin@example.com',
'pass_raw' => $this->randomMachineName(),
'timezone' => date_default_timezone_get(),
));
// The child site derives its session name from the database prefix when
@ -2005,8 +1868,7 @@ abstract class WebTestBase extends TestBase {
}
// @todo Ajax commands can target any jQuery selector, but these are
// hard to fully emulate with XPath. For now, just handle 'head'
// and 'body', since these are used by
// \Drupal\Core\Ajax\AjaxResponse::ajaxRender().
// and 'body', since these are used by the Ajax renderer.
elseif (in_array($command['selector'], array('head', 'body'))) {
$wrapperNode = $xpath->query('//' . $command['selector'])->item(0);
}

View file

@ -133,7 +133,6 @@ class CronForm extends FormBase {
drupal_set_message(t('Cron run failed.'), 'error');
}
return new RedirectResponse($this->url('system.cron_settings', array(), array('absolute' => TRUE)));
}
}

View file

@ -87,7 +87,7 @@ class SystemConfigSubscriber implements EventSubscriberInterface {
*/
public static function getSubscribedEvents() {
$events[ConfigEvents::SAVE][] = array('onConfigSave', 0);
// The empty check has a high priority so that is can stop propagation if
// The empty check has a high priority so that it can stop propagation if
// there is no configuration to import.
$events[ConfigEvents::IMPORT_VALIDATE][] = array('onConfigImporterValidateNotEmpty', 512);
$events[ConfigEvents::IMPORT_VALIDATE][] = array('onConfigImporterValidateSiteUUID', 256);

View file

@ -22,13 +22,13 @@ use Drupal\Core\Asset\AttachedAssets;
*/
class FrameworkTest extends AjaxTestBase {
/**
* Ensures \Drupal\Core\Ajax\AjaxResponse::ajaxRender() returns JavaScript settings from the page request.
* Verifies the Ajax rendering of a command in the settings.
*/
public function testAJAXRender() {
// Verify that settings command is generated if JavaScript settings exist.
$commands = $this->drupalGetAjax('ajax-test/render');
$expected = new SettingsCommand(array('ajax' => 'test'), TRUE);
$this->assertCommand($commands, $expected->render(), '\Drupal\Core\Ajax\AjaxResponse::ajaxRender() loads JavaScript settings.');
$this->assertCommand($commands, $expected->render(), 'JavaScript settings command is present.');
}
/**
@ -59,8 +59,8 @@ class FrameworkTest extends AjaxTestBase {
// Load any page with at least one CSS file, at least one JavaScript file
// and at least one #ajax-powered element. The latter is an assumption of
// drupalPostAjaxForm(), the two former are assumptions of
// AjaxResponse::ajaxRender().
// drupalPostAjaxForm(), the two former are assumptions of the Ajax
// renderer.
// @todo refactor AJAX Framework + tests to make less assumptions.
$this->drupalGet('ajax_forms_test_lazy_load_form');

View file

@ -96,8 +96,8 @@ class AttachedAssetsTest extends KernelTestBase {
$rendered_css = $this->renderer->renderPlain($css_render_array);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . file_create_url('core/modules/system/tests/modules/common_test/bar.css') . '?' . $query_string . '" media="all" />'), FALSE, 'Rendering an external CSS file.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/foo.js') . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/bar.css')) . '?' . $query_string . '" media="all" />'), FALSE, 'Rendering an external CSS file.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/foo.js')) . '?' . $query_string . '"></script>'), FALSE, 'Rendering an external JavaScript file.');
}
/**
@ -150,7 +150,7 @@ class AttachedAssetsTest extends KernelTestBase {
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
$expected_2 = '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
}
@ -166,7 +166,7 @@ class AttachedAssetsTest extends KernelTestBase {
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
$expected_2 = '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js') . '?v=1" defer bar="foo"></script>';
$expected_2 = '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/deferred-internal.js')) . '?v=1" defer bar="foo"></script>';
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered external JavaScript with correct defer and random attributes.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered internal JavaScript with correct defer and random attributes.');
}
@ -186,7 +186,7 @@ class AttachedAssetsTest extends KernelTestBase {
$rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
$this->assertEqual(2, count($rendered_footer_js), 'There are 2 JavaScript assets in the footer.');
$this->assertEqual('drupal-settings-json', $rendered_footer_js[0]['#attributes']['data-drupal-selector'], 'The first of the two JavaScript assets in the footer has drupal settings.');
$this->assertEqual('http://', substr($rendered_footer_js[1]['#attributes']['src'], 0, 7), 'The second of the two JavaScript assets in the footer has the sole aggregated JavaScript asset.');
$this->assertEqual(0, strpos($rendered_footer_js[1]['#attributes']['src'], base_path()), 'The second of the two JavaScript assets in the footer has the sole aggregated JavaScript asset.');
}
/**
@ -237,9 +237,9 @@ class AttachedAssetsTest extends KernelTestBase {
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/header.js') . '?' . $query_string . '"></script>'), FALSE, 'The JS asset in common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/misc/drupal.js')), FALSE, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_create_url('core/assets/vendor/domready/ready.min.js')), FALSE, 'The JS asset of the indirect dependency (core/domready) of common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/header.js')) . '?' . $query_string . '"></script>'), FALSE, 'The JS asset in common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/misc/drupal.js'))), FALSE, 'The JS asset of the direct dependency (core/drupal) of common_test/js-header appears in the header.');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . file_url_transform_relative(file_create_url('core/assets/vendor/domready/ready.min.js'))), FALSE, 'The JS asset of the indirect dependency (core/domready) of common_test/js-header appears in the header.');
}
/**
@ -267,8 +267,8 @@ class AttachedAssetsTest extends KernelTestBase {
$js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/old-ie.js') . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
$expected_2 = "<!--[if !IE]><!-->\n" . '<script src="' . file_create_url('core/modules/system/tests/modules/common_test/no-ie.js') . '?' . $default_query_string . '"></script>' . "\n<!--<![endif]-->";
$expected_1 = "<!--[if lte IE 8]>\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/old-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<![endif]-->";
$expected_2 = "<!--[if !IE]><!-->\n" . '<script src="' . file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/no-ie.js')) . '?' . $default_query_string . '"></script>' . "\n<!--<![endif]-->";
$this->assertNotIdentical(strpos($rendered_js, $expected_1), FALSE, 'Rendered JavaScript within downlevel-hidden conditional comments.');
$this->assertNotIdentical(strpos($rendered_js, $expected_2), FALSE, 'Rendered JavaScript within downlevel-revealed conditional comments.');
@ -476,8 +476,8 @@ class AttachedAssetsTest extends KernelTestBase {
$js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
$rendered_js = $this->renderer->renderPlain($js_render_array);
$query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&amp;', file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2')) . '&amp;' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&amp;', file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2')) . '&amp;' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
$this->assertNotIdentical(strpos($rendered_css, '<link rel="stylesheet" href="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '" media="all" />'), FALSE, 'CSS file with query string gets version query string correctly appended..');
$this->assertNotIdentical(strpos($rendered_js, '<script src="' . str_replace('&', '&amp;', file_url_transform_relative(file_create_url('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2'))) . '&amp;' . $query_string . '"></script>'), FALSE, 'JavaScript file with query string gets version query string correctly appended.');
}
}

View file

@ -10,6 +10,8 @@ namespace Drupal\system\Tests\Entity;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTestMulRev;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\language\Entity\ConfigurableLanguage;
/**
@ -810,4 +812,97 @@ class EntityTranslationTest extends EntityLanguageTestBase {
}
}
/**
* Tests if entity translation statuses are correct after removing two
* translation.
*/
public function testDeleteEntityTranslation() {
$entity_type = 'entity_test_mul';
$controller = $this->entityManager->getStorage($entity_type);
// Create a translatable test field.
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => 'translatable_test_field',
'type' => 'field_test',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'label' => $this->randomMachineName(),
'bundle' => $entity_type,
]);
$field->save();
// Create an untranslatable test field.
$field_storage = FieldStorageConfig::create([
'entity_type' => $entity_type,
'field_name' => 'untranslatable_test_field',
'type' => 'field_test',
'translatable' => FALSE,
]);
$field_storage->save();
$field = FieldConfig::create([
'field_storage' => $field_storage,
'label' => $this->randomMachineName(),
'bundle' => $entity_type,
]);
$field->save();
// Create an entity with both translatable and untranslatable test fields.
$values = array(
'name' => $this->randomString(),
'translatable_test_field' => $this->randomString(),
'untranslatable_test_field' => $this->randomString(),
);
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity = $controller->create($values);
foreach ($this->langcodes as $langcode) {
$entity->addTranslation($langcode, $values);
}
$entity->save();
// Assert there are no deleted languages in the lists yet.
$this->assertNull(\Drupal::state()->get('entity_test.delete.translatable_test_field'));
$this->assertNull(\Drupal::state()->get('entity_test.delete.untranslatable_test_field'));
// Remove the second and third langcodes from the entity.
$entity->removeTranslation('l1');
$entity->removeTranslation('l2');
$entity->save();
// Ensure that for the translatable test field the second and third
// langcodes are in the deleted languages list.
$actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
$expected_translatable = ['l1', 'l2'];
sort($actual);
sort($expected_translatable);
$this->assertEqual($actual, $expected_translatable);
// Ensure that the untranslatable test field is untouched.
$this->assertNull(\Drupal::state()->get('entity_test.delete.untranslatable_test_field'));
// Delete the entity, which removes all remaining translations.
$entity->delete();
// All languages have been deleted now.
$actual = \Drupal::state()->get('entity_test.delete.translatable_test_field');
$expected_translatable[] = 'en';
$expected_translatable[] = 'l0';
sort($actual);
sort($expected_translatable);
$this->assertEqual($actual, $expected_translatable);
// The untranslatable field is shared and only deleted once, for the
// default langcode.
$actual = \Drupal::state()->get('entity_test.delete.untranslatable_test_field');
$expected_untranslatable = ['en'];
sort($actual);
sort($expected_untranslatable);
$this->assertEqual($actual, $expected_untranslatable);
}
}

View file

@ -48,6 +48,22 @@ class LocalActionTest extends WebTestBase {
[Url::fromRoute('menu_test.local_action3'), 'My YAML discovery action'],
[Url::fromRoute('menu_test.local_action5'), 'Title override'],
]);
// Test a local action title that changes based on a config value.
$this->drupalGet(Url::fromRoute('menu_test.local_action6'));
$this->assertLocalAction([
[Url::fromRoute('menu_test.local_action5'), 'Original title'],
]);
// Verify the expected cache tag in the response headers.
$header_values = explode(' ', $this->drupalGetHeader('x-drupal-cache-tags'));
$this->assertTrue(in_array('config:menu_test.links.action', $header_values), "Found 'config:menu_test.links.action' cache tag in header");
/** @var \Drupal\Core\Config\Config $config */
$config = $this->container->get('config.factory')->getEditable('menu_test.links.action');
$config->set('title', 'New title');
$config->save();
$this->drupalGet(Url::fromRoute('menu_test.local_action6'));
$this->assertLocalAction([
[Url::fromRoute('menu_test.local_action5'), 'New title'],
]);
}
/**

View file

@ -48,7 +48,7 @@ class AjaxPageStateTest extends WebTestBase {
);
$this->assertRaw(
'/core/misc/drupalSettingsLoader.js',
'The Dupalsettings library from core should be loaded.'
'The drupalSettings library from core should be loaded.'
);
}
@ -78,7 +78,7 @@ class AjaxPageStateTest extends WebTestBase {
$this->assertRaw(
'/core/misc/drupalSettingsLoader.js',
'The Dupalsettings library from core should be loaded.'
'The drupalSettings library from core should be loaded.'
);
}
@ -107,7 +107,7 @@ class AjaxPageStateTest extends WebTestBase {
$this->assertNoRaw(
'/core/misc/drupalSettingsLoader.js',
'The Dupalsettings library from core should be excluded from loading.'
'The drupalSettings library from core should be excluded from loading.'
);
}
}

View file

@ -109,6 +109,10 @@ class CronRunTest extends WebTestBase {
// fail randomly. Look for the word 'years', because without a timestamp,
// the time will start at 1 January 1970.
$this->assertNoText('years');
$this->drupalPostForm(NULL, [], t('Save configuration'));
$this->assertText(t('The configuration options have been saved.'));
$this->assertUrl('admin/config/system/cron');
}
/**

View file

@ -86,6 +86,11 @@ class HtaccessTest extends WebTestBase {
foreach ($file_exts_to_allow as $file_ext) {
$file_paths["$path/access_test.$file_ext"] = 200;
}
// Ensure composer.json and composer.lock cannot be accessed.
$file_paths["$path/composer.json"] = 403;
$file_paths["$path/composer.lock"] = 403;
return $file_paths;
}

View file

@ -65,27 +65,27 @@ class ThemeTest extends WebTestBase {
// Raw stream wrapper URI.
$file->uri => array(
'form' => file_uri_target($file->uri),
'src' => file_create_url($file->uri),
'src' => file_url_transform_relative(file_create_url($file->uri)),
),
// Relative path within the public filesystem.
file_uri_target($file->uri) => array(
'form' => file_uri_target($file->uri),
'src' => file_create_url($file->uri),
'src' => file_url_transform_relative(file_create_url($file->uri)),
),
// Relative path to a public file.
$file_relative => array(
'form' => $file_relative,
'src' => file_create_url($file->uri),
'src' => file_url_transform_relative(file_create_url($file->uri)),
),
// Relative path to an arbitrary file.
'core/misc/druplicon.png' => array(
'form' => 'core/misc/druplicon.png',
'src' => $GLOBALS['base_url'] . '/' . 'core/misc/druplicon.png',
'src' => base_path() . 'core/misc/druplicon.png',
),
// Relative path to a file in a theme.
$default_theme_path . '/logo.svg' => array(
'form' => $default_theme_path . '/logo.svg',
'src' => $GLOBALS['base_url'] . '/' . $default_theme_path . '/logo.svg',
'src' => base_path() . $default_theme_path . '/logo.svg',
),
);
foreach ($supported_paths as $input => $expected) {
@ -186,7 +186,7 @@ class ThemeTest extends WebTestBase {
':rel' => 'home',
)
);
$this->assertEqual($elements[0]['src'], file_create_url($uploaded_filename));
$this->assertEqual($elements[0]['src'], file_url_transform_relative(file_create_url($uploaded_filename)));
$this->container->get('theme_handler')->install(array('bartik'));

View file

@ -125,7 +125,7 @@ class EngineTwigTest extends WebTestBase {
*/
public function testTwigFileUrls() {
$this->drupalGet('/twig-theme-test/file-url');
$filepath = file_create_url('core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js');
$filepath = file_url_transform_relative(file_create_url('core/modules/system/tests/modules/twig_theme_test/twig_theme_test.js'));
$this->assertRaw('<div>file_url: ' . $filepath . '</div>');
}

View file

@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Theme;
use Drupal\simpletest\KernelTestBase;
use Symfony\Component\HttpFoundation\Request;
/**
* Tests built-in image theme functions.
@ -32,9 +33,17 @@ class ImageTest extends KernelTestBase {
protected function setUp() {
parent::setUp();
// The code under test uses file_url_transform_relative(), which relies on
// the Request containing the correct hostname. KernelTestBase doesn't set
// it, so push another request onto the stack to ensure it's correct.
$request = Request::create('/', 'GET', [], [], [], $_SERVER);
$this->container = $this->kernel->getContainer();
$this->container->get('request_stack')->push($request);
$this->testImages = array(
'/core/misc/druplicon.png',
'/core/misc/loading.gif',
'core/misc/druplicon.png',
'core/misc/loading.gif',
);
}
@ -75,7 +84,7 @@ class ImageTest extends KernelTestBase {
$this->render($image);
// Make sure the src attribute has the correct value.
$this->assertRaw(file_create_url($image['#uri']), 'Correct output for an image with the src attribute.');
$this->assertRaw(file_url_transform_relative(file_create_url($image['#uri'])), 'Correct output for an image with the src attribute.');
}
/**
@ -103,7 +112,7 @@ class ImageTest extends KernelTestBase {
$this->render($image);
// Make sure the srcset attribute has the correct value.
$this->assertRaw(file_create_url($this->testImages[0]) . ' 1x, ' . file_create_url($this->testImages[1]) . ' 2x', 'Correct output for image with srcset attribute and multipliers.');
$this->assertRaw(file_url_transform_relative(file_create_url($this->testImages[0])) . ' 1x, ' . file_url_transform_relative(file_create_url($this->testImages[1])) . ' 2x', 'Correct output for image with srcset attribute and multipliers.');
}
/**
@ -135,7 +144,7 @@ class ImageTest extends KernelTestBase {
$this->render($image);
// Make sure the srcset attribute has the correct value.
$this->assertRaw(file_create_url($this->testImages[0]) . ' ' . $widths[0] . ', ' . file_create_url($this->testImages[1]) . ' ' . $widths[1], 'Correct output for image with srcset attribute and width descriptors.');
$this->assertRaw(file_url_transform_relative(file_create_url($this->testImages[0])) . ' ' . $widths[0] . ', ' . file_url_transform_relative(file_create_url($this->testImages[1])) . ' ' . $widths[1], 'Correct output for image with srcset attribute and width descriptors.');
}
}

View file

@ -43,7 +43,9 @@ class UpdatePathTestJavaScriptTest extends UpdatePathTestBase {
if (!isset($script['src'])) {
continue;
}
$src = (string) $script['src'];
// Source is a root-relative URL. Transform it to an absolute URL to allow
// file_get_contents() to access the file.
$src = preg_replace('#^' . $GLOBALS['base_path'] . '(.*)#i', $GLOBALS['base_url'] . '/' . '${1}', (string) $script['src']);
$file_content = file_get_contents($src);
if (strpos($file_content, 'window.drupalSettings =') !== FALSE) {

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