Update to drupal 8.0.0-rc1. For more information, see https://www.drupal.org/node/2582663

This commit is contained in:
Greg Anderson 2015-10-08 11:40:12 -07:00
parent eb34d130a8
commit f32e58e4b1
8476 changed files with 211648 additions and 170042 deletions

View file

@ -102,9 +102,14 @@ class ImageStyleDownloadController extends FileDownloadController {
// generated without a token can set the
// 'image.settings:allow_insecure_derivatives' configuration to TRUE to
// bypass the latter check, but this will increase the site's vulnerability
// to denial-of-service attacks.
// to denial-of-service attacks. To prevent this variable from leaving the
// site vulnerable to the most serious attacks, a token is always required
// when a derivative of a style is requested.
// The $target variable for a derivative of a style has
// styles/<style_name>/... as structure, so we check if the $target variable
// starts with styles/.
$valid = !empty($image_style) && file_stream_wrapper_valid_scheme($scheme);
if (!$this->config('image.settings')->get('allow_insecure_derivatives')) {
if (!$this->config('image.settings')->get('allow_insecure_derivatives') || strpos(ltrim($target, '\/'), 'styles/') === 0) {
$valid &= $request->query->get(IMAGE_DERIVATIVE_TOKEN) === $image_style->getPathToken($image_uri);
}
if (!$valid) {
@ -173,7 +178,11 @@ class ImageStyleDownloadController extends FileDownloadController {
'Content-Type' => $image->getMimeType(),
'Content-Length' => $image->getFileSize(),
);
return new BinaryFileResponse($uri, 200, $headers);
// \Drupal\Core\EventSubscriber\FinishResponseSubscriber::onRespond()
// sets response as not cacheable if the Cache-Control header is not
// already modified. We pass in FALSE for non-private schemes for the
// $public parameter to make sure we don't change the headers.
return new BinaryFileResponse($uri, 200, $headers, $scheme !== 'private');
}
else {
$this->logger->notice('Unable to generate the derived image located at %path.', array('%path' => $derivative_uri));

View file

@ -276,6 +276,13 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
* {@inheritdoc}
*/
public function createDerivative($original_uri, $derivative_uri) {
// If the source file doesn't exist, return FALSE without creating folders.
$image = \Drupal::service('image.factory')->get($original_uri);
if (!$image->isValid()) {
return FALSE;
}
// Get the folder for the final location of this style.
$directory = drupal_dirname($derivative_uri);
@ -285,11 +292,6 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface, Entity
return FALSE;
}
$image = \Drupal::service('image.factory')->get($original_uri);
if (!$image->isValid()) {
return FALSE;
}
foreach ($this->getEffects() as $effect) {
$effect->applyEffect($image);
}

View file

@ -91,8 +91,8 @@ 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(
'!url' => $this->urlGenerator->generateFromPath('admin/config/media/image-styles/add'),
$build['#empty'] = $this->t('There are currently no styles. <a href=":url">Add a new one</a>.', array(
':url' => $this->urlGenerator->generateFromRoute('image.style_add'),
));
return $build;
}

View file

@ -10,6 +10,7 @@ namespace Drupal\image\Plugin\Field\FieldFormatter;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Link;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Routing\UrlGeneratorInterface;
use Drupal\Core\Session\AccountInterface;
@ -39,13 +40,6 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
*/
protected $currentUser;
/**
* The link generator.
*
* @var \Drupal\Core\Utility\LinkGeneratorInterface
*/
protected $linkGenerator;
/**
* The image style entity storage.
*
@ -72,13 +66,10 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
* Any third party settings settings.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
* The link generator service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, LinkGeneratorInterface $link_generator, EntityStorageInterface $image_style_storage) {
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, AccountInterface $current_user, EntityStorageInterface $image_style_storage) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
$this->currentUser = $current_user;
$this->linkGenerator = $link_generator;
$this->imageStyleStorage = $image_style_storage;
}
@ -95,7 +86,6 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
$configuration['view_mode'],
$configuration['third_party_settings'],
$container->get('current_user'),
$container->get('link_generator'),
$container->get('entity.manager')->getStorage('image_style')
);
}
@ -115,17 +105,20 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$image_styles = image_style_options(FALSE);
$element['image_style'] = array(
$description_link = Link::fromTextAndUrl(
$this->t('Configure Image Styles'),
Url::fromRoute('entity.image_style.collection')
);
$element['image_style'] = [
'#title' => t('Image style'),
'#type' => 'select',
'#default_value' => $this->getSetting('image_style'),
'#empty_option' => t('None (original image)'),
'#options' => $image_styles,
'#description' => array(
'#markup' => $this->linkGenerator->generate($this->t('Configure Image Styles'), new Url('entity.image_style.collection')),
'#access' => $this->currentUser->hasPermission('administer image styles'),
),
);
'#description' => $description_link->toRenderable() + [
'#access' => $this->currentUser->hasPermission('administer image styles')
],
];
$link_types = array(
'content' => t('Content'),
'file' => t('File'),
@ -176,9 +169,9 @@ class ImageFormatter extends ImageFormatterBase implements ContainerFactoryPlugi
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items) {
public function viewElements(FieldItemListInterface $items, $langcode) {
$elements = array();
$files = $this->getEntitiesToView($items);
$files = $this->getEntitiesToView($items, $langcode);
// Early opt-out if the field is empty.
if (empty($files)) {

View file

@ -8,6 +8,7 @@
namespace Drupal\image\Plugin\Field\FieldFormatter;
use Drupal\Core\Field\EntityReferenceFieldItemListInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\FieldConfigInterface;
use Drupal\file\Plugin\Field\FieldFormatter\FileFormatterBase;
@ -19,7 +20,7 @@ abstract class ImageFormatterBase extends FileFormatterBase {
/**
* {@inheritdoc}
*/
protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items) {
protected function getEntitiesToView(EntityReferenceFieldItemListInterface $items, $langcode) {
// Add the default image if needed.
if ($items->isEmpty()) {
$default_image = $this->getFieldSetting('default_image');
@ -28,7 +29,6 @@ abstract class ImageFormatterBase extends FileFormatterBase {
if (empty($default_image['uuid']) && $this->fieldDefinition instanceof FieldConfigInterface) {
$default_image = $this->fieldDefinition->getFieldStorageDefinition()->getSetting('default_image');
}
if (!empty($default_image['uuid']) && $file = \Drupal::entityManager()->loadEntityByUuid('file', $default_image['uuid'])) {
// Clone the FieldItemList into a runtime-only object for the formatter,
// so that the fallback image can be rendered without affecting the
@ -48,7 +48,7 @@ abstract class ImageFormatterBase extends FileFormatterBase {
}
}
return parent::getEntitiesToView($items);
return parent::getEntitiesToView($items, $langcode);
}
}

View file

@ -145,6 +145,9 @@ class ImageItem extends FileItem {
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties = parent::propertyDefinitions($field_definition);
unset($properties['display']);
unset($properties['description']);
$properties['alt'] = DataDefinition::create('string')
->setLabel(t('Alternative text'))
->setDescription(t("Alternative image text, for the image's 'alt' attribute."));
@ -209,7 +212,7 @@ class ImageItem extends FileItem {
'#weight' => 4.1,
'#field_prefix' => '<div class="container-inline">',
'#field_suffix' => '</div>',
'#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href="@url">EXIF data</a> in the image.', array('@url' => 'http://en.wikipedia.org/wiki/Exchangeable_image_file_format')),
'#description' => t('The maximum allowed image size expressed as WIDTH×HEIGHT (e.g. 640×480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of <a href=":url">EXIF data</a> in the image.', array(':url' => 'http://en.wikipedia.org/wiki/Exchangeable_image_file_format')),
);
$element['max_resolution']['x'] = array(
'#type' => 'number',
@ -380,7 +383,9 @@ class ImageItem extends FileItem {
if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) {
foreach (array('x', 'y') as $dimension) {
if (!$element[$dimension]['#value']) {
$form_state->setError($element[$dimension], t('Both a height and width value must be specified in the !name field.', array('!name' => $element['#title'])));
// We expect the field name placeholder value to be wrapped in t()
// here, so it won't be escaped again as it's already marked safe.
$form_state->setError($element[$dimension], t('Both a height and width value must be specified in the @name field.', array('@name' => $element['#title'])));
return;
}
}

View file

@ -160,7 +160,6 @@ class ImageWidget extends FileWidget {
$item['fids'] = $element['fids']['#value'];
$element['#theme'] = 'image_widget';
$element['#attached']['library'][] = 'image/form';
// Add the image preview.
if (!empty($element['#files']) && $element['#preview_image_style']) {

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\image\Plugin\migrate\source\d7\ImageStyles.
*/
namespace Drupal\image\Plugin\migrate\source\d7;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
use Drupal\migrate\Row;
/**
* Drupal image styles source from database.
*
* @MigrateSource(
* id = "d7_image_styles",
* source_provider = "image"
* )
*/
class ImageStyles extends DrupalSqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('image_styles', 'ims')
->fields('ims');
}
/**
* {@inheritdoc}
*/
public function fields() {
$fields = [
'isid' => $this->t('The primary identifier for an image style.'),
'name' => $this->t('The style machine name.'),
'label' => $this->t('The style administrative name.'),
];
return $fields;
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['isid']['type'] = 'integer';
return $ids;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$effects = array();
$results = $this->select('image_effects', 'ie')
->fields('ie')
->condition('isid', $row->getSourceProperty('isid'))
->execute();
foreach ($results as $key => $result) {
$result['data'] = unserialize($result['data']);
$effects[$key] = $result;
}
$row->setSourceProperty('effects', $effects);
return parent::prepareRow($row);
}
}

View file

@ -443,11 +443,11 @@ class ImageAdminStylesTest extends ImageFieldTestBase {
$this->drupalGet('node/' . $nid);
$this->assertRaw($style->buildUrl($original_uri), format_string('Image displayed using style @style.', array('@style' => $style_name)));
// Copy config to staging, and delete the image style.
$staging = $this->container->get('config.storage.staging');
// Copy config to sync, and delete the image style.
$sync = $this->container->get('config.storage.sync');
$active = $this->container->get('config.storage');
$this->copyConfig($active, $staging);
$staging->delete('image.style.' . $style_name);
$this->copyConfig($active, $sync);
$sync->delete('image.style.' . $style_name);
$this->configImporter()->import();
$this->assertFalse(ImageStyle::load($style_name), 'Style deleted after config import.');

View file

@ -0,0 +1,36 @@
<?php
/**
* @file
* Contains \Drupal\image\Tests\ImageFieldWidgetTest.
*/
namespace Drupal\image\Tests;
/**
* Tests the image field widget.
*
* @group image
*/
class ImageFieldWidgetTest extends ImageFieldTestBase {
/**
* Tests file widget element.
*/
public function testWidgetElement() {
// Check for image widget in add/node/article page
$field_name = strtolower($this->randomMachineName());
$min_resolution = 50;
$max_resolution = 100;
$field_settings = array(
'max_resolution' => $max_resolution . 'x' . $max_resolution,
'min_resolution' => $min_resolution . 'x' . $min_resolution,
'alt_field' => 0,
);
$this->createImageField($field_name, 'article', array(), $field_settings);
$this->drupalGet('node/add/article');
$this->assertNotEqual(0, count($this->xpath('//div[contains(@class, "field--widget-image-image")]')), 'Image field widget found on add/node page', 'Browser');
}
}

View file

@ -115,6 +115,11 @@ class ImageItemTest extends FieldUnitTestBase {
$entity = entity_create('entity_test', array('mame' => $this->randomMachineName()));
$entity->save();
// Test image item properties.
$expected = array('target_id', 'entity', 'alt', 'title', 'width', 'height');
$properties = $entity->getFieldDefinition('image_test')->getFieldStorageDefinition()->getPropertyDefinitions();
$this->assertEqual(array_keys($properties), $expected);
// Test the generateSampleValue() method.
$entity = entity_create('entity_test');
$entity->image_test->generateSampleItems();

View file

@ -196,10 +196,15 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->assertNoRaw( chr(137) . chr(80) . chr(78) . chr(71) . chr(13) . chr(10) . chr(26) . chr(10), 'No PNG signature found in the response body.');
}
}
elseif ($clean_url) {
// Add some extra chars to the token.
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
$this->assertResponse(200, 'Existing image was accessible at the URL with an invalid token.');
else {
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
if ($clean_url) {
// Add some extra chars to the token.
$this->drupalGet(str_replace(IMAGE_DERIVATIVE_TOKEN . '=', IMAGE_DERIVATIVE_TOKEN . '=Zo', $generate_url));
$this->assertResponse(200, 'Existing image was accessible at the URL with an invalid token.');
}
}
// Allow insecure image derivatives to be created for the remainder of this
@ -224,6 +229,34 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->assertIdentical(strpos($generate_url, IMAGE_DERIVATIVE_TOKEN . '='), FALSE, 'The security token does not appear in the image style URL.');
$this->drupalGet($generate_url);
$this->assertResponse(200, 'Image was accessible at the URL with a missing token.');
// Stop supressing the security token in the URL.
$this->config('image.settings')->set('suppress_itok_output', FALSE)->save();
// Ensure allow_insecure_derivatives is enabled.
$this->assertEqual($this->config('image.settings')->get('allow_insecure_derivatives'), TRUE);
// Check that a security token is still required when generating a second
// image derivative using the first one as a source.
$nested_url = $this->style->buildUrl($generated_uri, $clean_url);
$matches_expected_url_format = (boolean) preg_match('/styles\/' . $this->style->id() . '\/' . $scheme . '\/styles\/' . $this->style->id() . '\/' . $scheme . '/', $nested_url);
$this->assertTrue($matches_expected_url_format, "Url for a derivative of an image style matches expected format.");
$nested_url_with_wrong_token = str_replace(IMAGE_DERIVATIVE_TOKEN . '=', 'wrongparam=', $nested_url);
$this->drupalGet($nested_url_with_wrong_token);
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token.');
// Check that this restriction cannot be bypassed by adding extra slashes
// to the URL.
$this->drupalGet(substr_replace($nested_url_with_wrong_token, '//styles/', strrpos($nested_url_with_wrong_token, '/styles/'), strlen('/styles/')));
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token, even with an extra forward slash in the URL.');
$this->drupalGet(substr_replace($nested_url_with_wrong_token, '////styles/', strrpos($nested_url_with_wrong_token, '/styles/'), strlen('/styles/')));
$this->assertResponse(403, 'Image generated from an earlier derivative was inaccessible at the URL with a missing token, even with multiple forward slashes in the URL.');
// Make sure the image can still be generated if a correct token is used.
$this->drupalGet($nested_url);
$this->assertResponse(200, 'Image was accessible when a correct token was provided in the URL.');
// Check that requesting a nonexistent image does not create any new
// directories in the file system.
$directory = $scheme . '://styles/' . $this->style->id() . '/' . $scheme . '/' . $this->randomMachineName();
$this->drupalGet(file_create_url($directory . '/' . $this->randomString()));
$this->assertFalse(file_exists($directory), 'New directory was not created in the filesystem when requesting an unauthorized image.');
}
}

View file

@ -2,7 +2,7 @@
/**
* @file
* Contains \Drupal\migrate_drupal\Tests\d6\MigrateImageCacheTest.
* Contains \Drupal\image\Tests\Migrate\d6\MigrateImageCacheTest.
*/
namespace Drupal\image\Tests\Migrate\d6;
@ -21,32 +21,14 @@ use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
*/
class MigrateImageCacheTest extends MigrateDrupal6TestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('image');
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
$this->prepareMigrations(array(
'd6_imagecache_presets' => [],
));
$this->installConfig(['image']);
}
/**
* Override parent to setup migration prior to run.
*/
public function testSourcePlugin() {
$this->executeMigration('d6_imagecache_presets');
parent::testSourcePlugin();
}
/**
* Test basic passing migrations.
*/

View file

@ -0,0 +1,78 @@
<?php
/**
* @file
* Contains \Drupal\image\Tests\Migrate\d7\MigrateImageStylesTest.
*/
namespace Drupal\image\Tests\Migrate\d7;
use Drupal\image\Entity\ImageStyle;
use Drupal\image\ImageStyleInterface;
use Drupal\image\ImageEffectBase;
use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
/**
* Test image styles migration to config entities.
*
* @group image
*/
class MigrateImageStylesTest extends MigrateDrupal7TestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('image');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installConfig(static::$modules);
$this->executeMigration('d7_image_styles');
}
/**
* Test the image styles migration.
*/
public function testImageStylesMigration() {
$this->assertEntity('custom_image_style_1', "Custom image style 1", ['image_scale_and_crop', 'image_desaturate'], [['width' => 55, 'height' => 55], []]);
$this->assertEntity('custom_image_style_2', "Custom image style 2", ['image_resize', 'image_rotate'], [['width' => 55, 'height' => 100], ['degrees' => 45, 'bgcolor' => '#FFFFFF', 'random' => false]]);
$this->assertEntity('custom_image_style_3', "Custom image style 3", ['image_scale', 'image_crop'], [['width' => 150, 'height' => NULL, 'upscale' => false], ['width' => 50, 'height' => 50, 'anchor' => 'left-top']]);
}
/**
* Asserts various aspects of an ImageStyle entity.
*
* @param string $id
* The expected image style ID.
* @param string $label
* The expected image style label.
* @param array $expected_effect_plugins
* An array of expected plugins attached to the image style entity
* @param array $expected_effect_config
* An array of expected configuration for each effect in the image style
*/
protected function assertEntity($id, $label, array $expected_effect_plugins, array $expected_effect_config) {
$style = ImageStyle::load($id);
$this->assertTrue($style instanceof ImageStyleInterface);
/** @var \Drupal\image\ImageStyleInterface $style */
$this->assertIdentical($id, $style->id());
$this->assertIdentical($label, $style->label());
// Check the number of effects associated with the style.
$effects = $style->getEffects();
$this->assertIdentical(count($effects), count($expected_effect_plugins));
$index = 0;
foreach ($effects as $effect) {
$this->assertTrue($effect instanceof ImageEffectBase);
$this->assertIdentical($expected_effect_plugins[$index], $effect->getPluginId());
$config = $effect->getConfiguration();
$this->assertIdentical($expected_effect_config[$index], $config['data']);
$index++;
}
}
}

View file

@ -84,7 +84,7 @@ class RelationshipUserImageDataTest extends ViewTestBase {
'user',
],
];
$this->assertIdentical($expected, $view->calculateDependencies());
$this->assertIdentical($expected, $view->getDependencies());
$this->executeView($view);
$expected_result = array(
array(