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

This commit is contained in:
Pantheon Automation 2016-04-07 11:19:57 -07:00 committed by Greg Anderson
parent 4297c64508
commit b11a755ba8
159 changed files with 2340 additions and 543 deletions

10
composer.lock generated
View file

@ -1980,16 +1980,16 @@
},
{
"name": "wikimedia/composer-merge-plugin",
"version": "v1.3.0",
"version": "v1.3.1",
"source": {
"type": "git",
"url": "https://github.com/wikimedia/composer-merge-plugin.git",
"reference": "bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9"
"reference": "0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9",
"reference": "bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9",
"url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4",
"reference": "0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4",
"shasum": ""
},
"require": {
@ -2025,7 +2025,7 @@
}
],
"description": "Composer plugin to merge multiple composer.json files",
"time": "2015-11-06 20:31:16"
"time": "2016-03-08 17:11:37"
},
{
"name": "zendframework/zend-diactoros",

View file

@ -533,7 +533,7 @@ participate in mentoring.
- Lucas Hedding 'heddn' https://www.drupal.org/u/heddn
- Valery Lourie 'valthebald' https://www.drupal.org/u/valthebald
- Alina Mackenzie 'alimac' https://www.drupal.org/u/alimac
- Chris McCaferty 'cilefen' https://www.drupal.org/u/cilefen
- Chris McCafferty 'cilefen' https://www.drupal.org/u/cilefen
- Jess Myrbo 'xjm' https://www.drupal.org/u/xjm
- Andrea Soper 'ZenDoodles' https://www.drupal.org/u/zendoodles
- Cathy Theys 'YesCT' https://www.drupal.org/u/yesct

View file

@ -605,10 +605,10 @@ field.field_settings.boolean:
type: mapping
mapping:
on_label:
type: string
type: label
label: 'On label'
off_label:
type: string
type: label
label: 'Off label'
field.value.boolean:

View file

@ -283,16 +283,16 @@
*
* The first task in using the simple configuration API is to define the
* configuration file structure, file name, and schema of your settings (see
* @ref sec_yaml above). Once you have done that, you can retrieve the
* active configuration object that corresponds to configuration file
* mymodule.foo.yml with a call to:
* @ref sec_yaml above). Once you have done that, you can retrieve the active
* configuration object that corresponds to configuration file mymodule.foo.yml
* with a call to:
* @code
* $config = \Drupal::config('mymodule.foo');
* @endcode
*
* This will be an object of class \Drupal\Core\Config\Config, which has methods
* for getting and setting configuration information. For instance, if your
* YAML file structure looks like this:
* for getting configuration information. For instance, if your YAML file
* structure looks like this:
* @code
* enabled: '0'
* bar:
@ -307,12 +307,34 @@
* $bar = $config->get('bar');
* // Get one element of the array.
* $bar_baz = $config->get('bar.baz');
* // Update a value. Nesting works the same as get().
* $config->set('bar.baz', 'string2');
* // Nothing actually happens with set() until you call save().
* @endcode
*
* The Config object that was obtained and used in the previous examples does
* not allow you to change configuration. If you want to change configuration,
* you will instead need to get the Config object by making a call to
* getEditable() on the config factory:
* @code
* $config =\Drupal::service('config.factory')->getEditable('mymodule.foo');
* @endcode
*
* Individual configuration values can be changed or added using the set()
* method and saved using the save() method:
* @code
* // Set a scalar value.
* $config->set('enabled', 1);
* // Save the configuration.
* $config->save();
* @endcode
*
* Configuration values can also be unset using the clear() method, which is
* also chainable:
* @code
* $config->clear('bar.boo')->save();
* $config_data = $config->get('bar');
* @endcode
* In this example $config_data would return an array with one key - 'baz' -
* because 'boo' was unset.
*
* @section sec_entity Configuration entities
* In contrast to the simple configuration settings described in the previous
* section, if your module allows users to create zero or more items (where
@ -709,7 +731,8 @@
* top-level core directory). Some Drupal Core modules and contributed modules
* also define services in modulename.services.yml files. API reference sites
* (such as https://api.drupal.org) generate lists of all existing services from
* these files, or you can look through the individual files manually.
* these files. Look for the Services link in the API Navigation block.
* Alternatively you can look through the individual files manually.
*
* A typical service definition in a *.services.yml file looks like this:
* @code

View file

@ -280,7 +280,8 @@ function drupal_get_path($type, $name) {
* - 'langcode' (defaults to the current language): A language code, to
* translate to a language other than what is used to display the page.
* - 'context' (defaults to the empty context): The context the source string
* belongs to.
* belongs to. See the @link i18n Internationalization topic @endlink for
* more information about string contexts.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* An object that, when cast to a string, returns the translated string.
@ -298,12 +299,13 @@ function t($string, array $args = array(), array $options = array()) {
/**
* Formats a string for HTML display by replacing variable placeholders.
*
* @see \Drupal\Component\Utility\SafeMarkup::format()
* @see \Drupal\Component\Render\FormattableMarkup::placeholderFormat()
* @see \Drupal\Component\Render\FormattableMarkup
* @see t()
* @ingroup sanitization
*
* @deprecated in Drupal 8.0.0, will be removed before Drupal 9.0.0.
* Use \Drupal\Component\Utility\SafeMarkup::format().
* Use \Drupal\Component\Render\FormattableMarkup.
*/
function format_string($string, array $args) {
return SafeMarkup::format($string, $args);

View file

@ -133,7 +133,7 @@ const JS_THEME = 100;
const LOCALE_PLURAL_DELIMITER = PluralTranslatableMarkup::DELIMITER;
/**
* Prepares a 'destination' URL query parameter for use with url().
* Prepares a 'destination' URL query parameter.
*
* Used to direct the user back to the referring page after completing a form.
* By default the current URL is returned. If a destination exists in the

View file

@ -143,16 +143,15 @@ function entity_revision_delete($entity_type, $revision_id) {
* database access if loaded again during the same page request.
*
* The actual loading is done through a class that has to implement the
* Drupal\Core\Entity\EntityStorageInterface interface. By default,
* Drupal\Core\Entity\Sql\SqlContentEntityStorage is used for content entities
* \Drupal\Core\Entity\EntityStorageInterface interface. By default,
* \Drupal\Core\Entity\Sql\SqlContentEntityStorage is used for content entities
* and Drupal\Core\Config\Entity\ConfigEntityStorage for config entities. Entity
* types can specify that a different class should be used by setting the
* "controllers['storage']" key in the entity plugin annotation. These classes
* can either implement the Drupal\Core\Entity\EntityStorageInterface
* "handlers['storage']" key in the entity plugin annotation. These classes
* can either implement the \Drupal\Core\Entity\EntityStorageInterface
* interface, or, most commonly, extend the
* Drupal\Core\Entity\Sql\SqlContentEntityStorage class.
* See Drupal\node\Entity\Node and Drupal\node\NodeStorage
* for an example.
* \Drupal\Core\Entity\Sql\SqlContentEntityStorage class. See
* \Drupal\node\Entity\Node and \Drupal\node\NodeStorage for an example.
*
* @param string $entity_type
* The entity type to load, e.g. node or user.

View file

@ -687,8 +687,8 @@ function template_preprocess_form_element_label(&$variables) {
* .module file. The path should be relative to base_path(), and thus should
* be built using drupal_get_path().
* - css: Array of paths to CSS files to be used on the progress page.
* - url_options: options passed to url() when constructing redirect URLs for
* the batch.
* - url_options: options passed to the \Drupal\Core\Url object when
* constructing redirect URLs for the batch.
* - progressive: A Boolean that indicates whether or not the batch needs to
* run progressively. TRUE indicates that the batch will run in more than
* one run. FALSE (default) indicates that the batch will finish in a single

View file

@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
const VERSION = '8.0.5';
const VERSION = '8.0.6';
/**
* Core API compatibility.

View file

@ -349,4 +349,26 @@ class NestedArray {
return $result;
}
/**
* Filters a nested array recursively.
*
* @param array $array
* The filtered nested array.
* @param callable|NULL $callable
* The callable to apply for filtering.
*
* @return array
* The filtered array.
*/
public static function filter(array $array, callable $callable = NULL) {
$array = is_callable($callable) ? array_filter($array, $callable) : array_filter($array);
foreach ($array as &$element) {
if (is_array($element)) {
$element = static::filter($element, $callable);
}
}
return $array;
}
}

View file

@ -10,11 +10,10 @@ namespace Drupal\Component\Uuid;
use Drupal\Component\Utility\Crypt;
/**
* Generates a UUID v4 using PHP code.
* Generates a UUID v4 (RFC 4122 section 4.4) using PHP code.
*
* Loosely based on Ruby's UUIDTools generate_random logic.
*
* @see http://uuidtools.rubyforge.org/api/classes/UUIDTools/UUID.html
* @see http://www.rfc-editor.org/rfc/rfc4122.txt
* @see http://www.rfc-editor.org/errata_search.php?rfc=4122&eid=3546
*/
class Php implements UuidInterface {
@ -22,27 +21,39 @@ class Php implements UuidInterface {
* {@inheritdoc}
*/
public function generate() {
$hex = substr(hash('sha256', Crypt::randomBytes(16)), 0, 32);
// Obtain a random string of 32 hex characters.
$hex = bin2hex(Crypt::randomBytes(16));
// The field names refer to RFC 4122 section 4.1.2.
// The variable names $time_low, $time_mid, $time_hi_and_version,
// $clock_seq_hi_and_reserved, $clock_seq_low, and $node correlate to
// the fields defined in RFC 4122 section 4.1.2.
//
// Use characters 0-11 to generate 32-bit $time_low and 16-bit $time_mid.
$time_low = substr($hex, 0, 8);
$time_mid = substr($hex, 8, 4);
$time_hi_and_version = base_convert(substr($hex, 12, 4), 16, 10);
$time_hi_and_version &= 0x0FFF;
$time_hi_and_version |= (4 << 12);
// Use characters 12-15 to generate 16-bit $time_hi_and_version.
// The 4 most significant bits are the version number (0100 == 0x4).
// We simply skip character 12 from $hex, and concatenate the strings.
$time_hi_and_version = '4' . substr($hex, 13, 3);
$clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 4), 16, 10);
$clock_seq_hi_and_reserved &= 0x3F;
$clock_seq_hi_and_reserved |= 0x80;
// Use characters 16-17 to generate 8-bit $clock_seq_hi_and_reserved.
// The 2 most significant bits are set to one and zero respectively.
$clock_seq_hi_and_reserved = base_convert(substr($hex, 16, 2), 16, 10);
$clock_seq_hi_and_reserved &= 0b00111111;
$clock_seq_hi_and_reserved |= 0b10000000;
$clock_seq_low = substr($hex, 20, 2);
$nodes = substr($hex, 20);
// Use characters 18-19 to generate 8-bit $clock_seq_low.
$clock_seq_low = substr($hex, 18, 2);
// Use characters 20-31 to generate 48-bit $node.
$node = substr($hex, 20);
$uuid = sprintf('%s-%s-%04x-%02x%02x-%s',
$time_low, $time_mid,
$time_hi_and_version, $clock_seq_hi_and_reserved,
$clock_seq_low, $nodes);
// Re-combine as a UUID. $clock_seq_hi_and_reserved is still an integer.
$uuid = sprintf('%s-%s-%s-%02x%s-%s',
$time_low, $time_mid, $time_hi_and_version,
$clock_seq_hi_and_reserved, $clock_seq_low,
$node
);
return $uuid;
}

View file

@ -142,7 +142,9 @@ EOT;
/**
* Remove possibly problematic test files from vendored projects.
*
* @param \Composer\Script\Event $event
* @param \Composer\Installer\PackageEvent $event
* A PackageEvent object to get the configured composer vendor directories
* from.
*/
public static function vendorTestCodeCleanup(PackageEvent $event) {
$vendor_dir = $event->getComposer()->getConfig()->get('vendor-dir');
@ -162,9 +164,6 @@ EOT;
throw new \RuntimeException(sprintf("Failure removing directory '%s' in package '%s'.", $path, $package->getPrettyName()));
}
}
else {
throw new \RuntimeException(sprintf("The directory '%s' in package '%s' does not exist.", $path, $package->getPrettyName()));
}
}
}
}

View file

@ -190,24 +190,35 @@ class ConfigInstaller implements ConfigInstallerInterface {
});
$all_config = array_merge($existing_config, $list);
$all_config = array_combine($all_config, $all_config);
$config_to_create = $storage->readMultiple($list);
// Check to see if the corresponding override storage has any overrides or
// new configuration that can be installed.
if ($profile_storage) {
$config_to_create = $profile_storage->readMultiple($list) + $config_to_create;
}
// Sort $config_to_create in the order of the least dependent first.
$dependency_manager = new ConfigDependencyManager();
$dependency_manager->setData($config_to_create);
$config_to_create = array_merge(array_flip($dependency_manager->sortAll()), $config_to_create);
foreach ($config_to_create as $config_name => $data) {
// Exclude configuration where its dependencies cannot be met.
if (!$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config)) {
unset($config_to_create[$config_name]);
}
// Exclude configuration that does not have a matching dependency.
elseif (!empty($dependency)) {
// Remove configuration where its dependencies cannot be met.
$remove = !$this->validateDependencies($config_name, $data, $enabled_extensions, $all_config);
// If $dependency is defined, remove configuration that does not have a
// matching dependency.
if (!$remove && !empty($dependency)) {
// Create a light weight dependency object to check dependencies.
$config_entity = new ConfigEntityDependency($config_name, $data);
if (!$config_entity->hasDependency(key($dependency), reset($dependency))) {
unset($config_to_create[$config_name]);
}
$remove = !$config_entity->hasDependency(key($dependency), reset($dependency));
}
if ($remove) {
// Remove from the list of configuration to create.
unset($config_to_create[$config_name]);
// Remove from the list of all configuration. This ensures that any
// configuration that depends on this configuration is also removed.
unset($all_config[$config_name]);
}
}
if (!empty($config_to_create)) {

View file

@ -18,6 +18,8 @@ use Drupal\Core\StringTranslation\StringTranslationTrait;
* Implement the container injection pattern of
* \Drupal\Core\Entity\EntityHandlerInterface::createInstance() to obtain the
* module handler service for your class.
*
* @ingroup entity_api
*/
abstract class EntityHandlerBase {
use StringTranslationTrait;

View file

@ -14,6 +14,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*
* This interface can be implemented by entity handlers that require
* dependency injection.
*
* @ingroup entity_api
*/
interface EntityHandlerInterface {

View file

@ -277,8 +277,8 @@ use Drupal\node\Entity\NodeType;
* content entity type that uses bundles, the 'bundle_label' annotation gives
* the human-readable name to use for a bundle of this entity type (for
* example, "Content type" for the Node entity).
* - The annotation will refer to several controller classes, which you will
* also need to define:
* - The annotation will refer to several handler classes, which you will also
* need to define:
* - list_builder: Define a class that extends
* \Drupal\Core\Config\Entity\ConfigEntityListBuilder (for configuration
* entities) or \Drupal\Core\Entity\EntityListBuilder (for content
@ -298,7 +298,7 @@ use Drupal\node\Entity\NodeType;
* annotation has value TRUE), define a class that extends
* \Drupal\content_translation\ContentTranslationHandler, to translate
* the content. Configuration translation is handled automatically by the
* Configuration Translation module, without the need of a controller class.
* Configuration Translation module, without the need of a handler class.
* - access: If your configuration entity has complex permissions, you might
* need an access control handling, implementing
* \Drupal\Core\Entity\EntityAccessControlHandlerInterface, but most entities
@ -381,10 +381,10 @@ use Drupal\node\Entity\NodeType;
* an object to the controller for the route.
* - defaults: For entity form routes, use _entity_form rather than the generic
* _controller or _form. The value is composed of the entity type machine name
* and a form controller type from the entity annotation (see @ref define
* above more more on controllers and annotation). So, in this example,
* block.default refers to the 'default' form controller on the block entity
* type, whose annotation contains:
* and a form handler type from the entity annotation (see @ref define above
* more more on handlers and annotation). So, in this example, block.default
* refers to the 'default' form handler on the block entity type, whose
* annotation contains:
* @code
* handlers = {
* "form" = {

View file

@ -30,11 +30,6 @@ trait ConfigFormBaseTrait {
/**
* Retrieves a configuration object.
*
* Objects that use the trait need to implement the
* \Drupal\Core\Form\ConfigFormBaseTrait::getEditableConfigNames() to declare
* which configuration objects this method returns override free and mutable.
* This ensures that overrides do not pollute saved configuration.
*
* @param string $name
* The name of the configuration object to retrieve. The name corresponds to
* a configuration file. For @code \Drupal::config('book.admin') @endcode,
@ -42,7 +37,9 @@ trait ConfigFormBaseTrait {
* configuration file.
*
* @return \Drupal\Core\Config\Config|\Drupal\Core\Config\ImmutableConfig
* A configuration object.
* An editable configuration object if the given name is listed in the
* getEditableConfigNames() method or an immutable configuration object if
* not.
*/
protected function config($name) {
/** @var \Drupal\Core\Config\ConfigFactoryInterface $config_factory */

View file

@ -18,7 +18,7 @@
* @param $MULTIPLE_PARAMS
* Additional parameters specific to the batch. These are specified in the
* array passed to batch_set().
* @param $context
* @param array|\ArrayAccess $context.
* The batch context array, passed by reference. This contains the following
* properties:
* - 'finished': A float number between 0 and 1 informing the processing
@ -51,6 +51,8 @@
* all operations have finished, this is passed to callback_batch_finished()
* where results may be referenced to display information to the end-user,
* such as how many total items were processed.
* It is discouraged to typehint this parameter as an array, to allow an
* object implement \ArrayAccess to be passed.
*/
function callback_batch_operation($MULTIPLE_PARAMS, &$context) {
$node_storage = \Drupal::entityTypeManager()->getStorage('node');

View file

@ -111,8 +111,8 @@ use Drupal\Core\Language\LanguageInterface;
* @code
* // PHP code
* t('May', array(), array('context' => 'Long month name');
* format_plural($count, '1 something', '@count somethings',
* array(), array('context' => 'My context'));
* \Drupal::translation()->formatPlural($count, '1 something',
* '@count somethings', array(), array('context' => 'My context'));
*
* // JavaScript code
* Drupal.t('May', {}, {'context': 'Long month name'});
@ -147,7 +147,6 @@ use Drupal\Core\Language\LanguageInterface;
*
* @see transliteration
* @see t()
* @see format_plural()
* @}
*/

View file

@ -34,6 +34,7 @@ class PhpStorageFactory {
* An instantiated storage for the specified name.
*/
static function get($name) {
$configuration = array();
$overrides = Settings::get('php_storage');
if (isset($overrides[$name])) {
$configuration = $overrides[$name];
@ -41,13 +42,11 @@ class PhpStorageFactory {
elseif (isset($overrides['default'])) {
$configuration = $overrides['default'];
}
else {
$configuration = array(
'class' => 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage',
'secret' => Settings::getHashSalt(),
);
}
// Make sure all the necessary configuration values are set.
$class = isset($configuration['class']) ? $configuration['class'] : 'Drupal\Component\PhpStorage\MTimeProtectedFileStorage';
if (!isset($configuration['secret'])) {
$configuration['secret'] = Settings::getHashSalt();
}
if (!isset($configuration['bin'])) {
$configuration['bin'] = $name;
}

View file

@ -28,7 +28,7 @@ use Drupal\Core\Template\Attribute;
* Usage example:
* @code
* $build['hello'] = [
* '#type' => 'html_tag'
* '#type' => 'html_tag',
* '#tag' => 'p',
* '#value' => $this->t('Hello World'),
* ];

View file

@ -259,6 +259,7 @@ class Tableselect extends Table {
'#return_value' => $key,
'#default_value' => isset($value[$key]) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
);
}
else {

View file

@ -136,7 +136,7 @@ class VerticalTabs extends RenderElement {
$element[$name . '__active_tab'] = array(
'#type' => 'hidden',
'#default_value' => $element['#default_tab'],
'#attributes' => array('class' => array('vertical-tabs-active-tab')),
'#attributes' => array('class' => array('vertical-tabs__active-tab')),
);
// Clean up the active tab value so it's not accidentally stored in
// settings forms.

View file

@ -324,8 +324,11 @@
* namespace Element, and generally extend the
* \Drupal\Core\Render\Element\FormElement base class.
* See the @link plugin_api Plugin API topic @endlink for general information
* on plugins, and look for classes with the RenderElement or FormElement
* annotation to discover what render elements are available.
* on plugins. You can search for classes with the RenderElement or FormElement
* annotation to discover what render elements are available. API reference
* sites (such as https://api.drupal.org) generate lists of all existing
* elements from these classes. Look for the Elements link in the API Navigation
* block.
*
* Modules can define render elements by defining an element plugin.
*

View file

@ -35,13 +35,6 @@ class PluralTranslatableMarkup extends TranslatableMarkup {
*/
protected $translatedString;
/**
* A bool that statically caches whether locale_get_plural() exists.
*
* @var bool
*/
protected static $localeEnabled;
/**
* Constructs a new PluralTranslatableMarkup object.
*
@ -157,10 +150,13 @@ class PluralTranslatableMarkup extends TranslatableMarkup {
* @return int
*/
protected function getPluralIndex() {
if (!isset(static::$localeEnabled)) {
static::$localeEnabled = function_exists('locale_get_plural');
}
if (function_exists('locale_get_plural')) {
// We have to test both if the function and the service exist since in
// certain situations it is possible that locale code might be loaded but
// the service does not exist. For example, where the parent test site has
// locale installed but the child site does not.
// @todo Refactor in https://www.drupal.org/node/2660338 so this code does
// not depend on knowing that the Locale module exists.
if (function_exists('locale_get_plural') && \Drupal::hasService('locale.plural.formula')) {
return locale_get_plural($this->count, $this->getOption('langcode'));
}
return -1;

View file

@ -60,7 +60,9 @@ trait StringTranslationTrait {
* - 'langcode' (defaults to the current language): A language code, to
* translate to a language other than what is used to display the page.
* - 'context' (defaults to the empty context): The context the source
* string belongs to.
* string belongs to. See the
* @link i18n Internationalization topic @endlink for more information
* about string contexts.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* An object that, when cast to a string, returns the translated string.

View file

@ -38,7 +38,9 @@ interface TranslationInterface {
* - 'langcode' (defaults to the current language): A language code, to
* translate to a language other than what is used to display the page.
* - 'context' (defaults to the empty context): The context the source
* string belongs to.
* string belongs to. See the
* @link i18n Internationalization topic @endlink for more information
* about string contexts.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* An object that, when cast to a string, returns the translated string.

View file

@ -0,0 +1,161 @@
<?php
/**
* @file
* Contains \Drupal\Core\Test\AssertMailTrait.
*/
namespace Drupal\Core\Test;
/**
* Provides methods for testing emails sent during test runs.
*/
trait AssertMailTrait {
/**
* Gets an array containing all emails sent during this test case.
*
* @param array $filter
* An array containing key/value pairs used to filter the emails that are
* returned.
*
* @return array
* An array containing email messages captured during the current test.
*/
protected function getMails(array $filter = []) {
$captured_emails = $this->container->get('state')->get('system.test_mail_collector', []);
$filtered_emails = [];
foreach ($captured_emails as $message) {
foreach ($filter as $key => $value) {
if (!isset($message[$key]) || $message[$key] != $value) {
continue 2;
}
}
$filtered_emails[] = $message;
}
return $filtered_emails;
}
/**
* Asserts that the most recently sent email message has the given value.
*
* The field in $name must have the content described in $value.
*
* @param string $name
* Name of field or message property to assert. Examples: subject, body,
* id, ...
* @param string $value
* Value of the field to assert.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Email'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMail($name, $value = '', $message = '', $group = 'Email') {
$captured_emails = $this->container->get('state')->get('system.test_mail_collector') ?: [];
$email = end($captured_emails);
return $this->assertTrue($email && isset($email[$name]) && $email[$name] == $value, $message, $group);
}
/**
* Asserts that the most recently sent email message has the string in it.
*
* @param string $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param string $string
* String to search for.
* @param int $email_depth
* Number of emails to search for string, starting with most recent.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') {
$mails = $this->getMails();
$string_found = FALSE;
// Cast MarkupInterface objects to string.
$string = (string) $string;
for ($i = count($mails) - 1; $i >= count($mails) - $email_depth && $i >= 0; $i--) {
$mail = $mails[$i];
// Normalize whitespace, as we don't know what the mail system might have
// done. Any run of whitespace becomes a single space.
$normalized_mail = preg_replace('/\s+/', ' ', $mail[$field_name]);
$normalized_string = preg_replace('/\s+/', ' ', $string);
$string_found = (FALSE !== strpos($normalized_mail, $normalized_string));
if ($string_found) {
break;
}
}
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', ['@field' => $field_name, '@expected' => $string]);
}
return $this->assertTrue($string_found, $message, $group);
}
/**
* Asserts that the most recently sent email message has the pattern in it.
*
* @param string $field_name
* Name of field or message property to assert: subject, body, id, ...
* @param string $regex
* Pattern to search for.
* @param string $message
* (optional) A message to display with the assertion. Do not translate
* messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
* variables in the message text, not t(). If left blank, a default message
* will be displayed.
* @param string $group
* (optional) The group this message is in, which is displayed in a column
* in test output. Use 'Debug' to indicate this is debugging output. Do not
* translate this string. Defaults to 'Other'; most tests do not override
* this default.
*
* @return bool
* TRUE on pass, FALSE on fail.
*/
protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') {
$mails = $this->getMails();
$mail = end($mails);
$regex_found = preg_match("/$regex/", $mail[$field_name]);
if (!$message) {
$message = format_string('Expected text found in @field of email message: "@expected".', ['@field' => $field_name, '@expected' => $regex]);
}
return $this->assertTrue($regex_found, $message, $group);
}
/**
* Outputs to verbose the most recent $count emails sent.
*
* @param int $count
* Optional number of emails to output.
*/
protected function verboseEmail($count = 1) {
$mails = $this->getMails();
for ($i = count($mails) - 1; $i >= count($mails) - $count && $i >= 0; $i--) {
$mail = $mails[$i];
$this->verbose('Email:<pre>' . print_r($mail, TRUE) . '</pre>');
}
}
}

View file

@ -7,7 +7,9 @@
namespace Drupal\Core\Test\HttpClientMiddleware;
use Drupal\Core\Utility\Error;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Overrides the User-Agent HTTP header for outbound HTTP requests.
@ -31,7 +33,29 @@ class TestHttpClientMiddleware {
if ($test_prefix = drupal_valid_test_ua()) {
$request = $request->withHeader('User-Agent', drupal_generate_test_ua($test_prefix));
}
return $handler($request, $options);
return $handler($request, $options)
->then(function (ResponseInterface $response) use ($request) {
if (!drupal_valid_test_ua()) {
return $response;
}
$headers = $response->getHeaders();
foreach ($headers as $header_name => $header_values) {
if (preg_match('/^X-Drupal-Assertion-[0-9]+$/', $header_name, $matches)) {
foreach ($header_values as $header_value) {
// Call \Drupal\simpletest\WebTestBase::error() with the parameters from
// the header.
$parameters = unserialize(urldecode($header_value));
if (count($parameters) === 3) {
throw new \Exception($parameters[1] . ': ' . $parameters[0] . "\n" . Error::formatBacktrace([$parameters[2]]));
}
else {
throw new \Exception('Error thrown with the wrong amount of parameters.');
}
}
}
}
return $response;
});
};
};
}

View file

@ -13,9 +13,8 @@ use Drupal\Core\Routing\RouteMatchInterface;
* Defines an interface for classes which determine the active theme.
*
* To set the active theme, create a new service tagged with 'theme_negotiator'
* (see user.services.yml for an example). The only method this service needs
* to implement is determineActiveTheme. Return the name of the theme, or NULL
* if other negotiators like the configured default one should kick in instead.
* (see the theme.negotiator.admin_theme service in user.services.yml for an
* example). Your service class needs to implement this interface.
*
* If you are setting a theme which is closely tied to the functionality of a
* particular page or set of pages (such that the page might not function
@ -46,7 +45,8 @@ interface ThemeNegotiatorInterface {
* The current route match object.
*
* @return string|null
* Returns the active theme name, else return NULL.
* The name of the theme, or NULL if other negotiators, like the configured
* default one, should be used instead.
*/
public function determineActiveTheme(RouteMatchInterface $route_match);

View file

@ -25,6 +25,14 @@ namespace Drupal\Core\TypedData;
*/
interface ComplexDataInterface extends TraversableTypedDataInterface {
/**
* Gets the data definition.
*
* @return \Drupal\Core\TypedData\ComplexDataDefinitionInterface
* The data definition object describing the complex data.
*/
public function getDataDefinition();
/**
* Gets a property object.
*

View file

@ -22,6 +22,14 @@ namespace Drupal\Core\TypedData;
*/
interface ListInterface extends TraversableTypedDataInterface, \ArrayAccess, \Countable {
/**
* Gets the data definition.
*
* @return \Drupal\Core\TypedData\ListDataDefinitionInterface
* The data definition object describing the list.
*/
public function getDataDefinition();
/**
* Determines whether the list contains any non-empty items.
*

View file

@ -200,8 +200,9 @@
});
// Use jQuery UI Autocomplete on the textfield.
$autocomplete.autocomplete(autocomplete.options)
.data('ui-autocomplete')
._renderItem = autocomplete.options.renderItem;
.each(function() {
$(this).data('ui-autocomplete')._renderItem = autocomplete.options.renderItem;
});
}
},
detach: function (context, settings, trigger) {

View file

@ -12,7 +12,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
/**
* Form controller for the aggregator feed edit forms.
* Form handler for the aggregator feed edit forms.
*/
class FeedForm extends ContentEntityForm {

View file

@ -16,7 +16,7 @@ use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Render controller for aggregator feed items.
* View builder handler for aggregator feeds.
*/
class FeedViewBuilder extends EntityViewBuilder {

View file

@ -10,7 +10,7 @@ namespace Drupal\aggregator;
use Drupal\Core\Entity\EntityViewBuilder;
/**
* Render controller for aggregator feed items.
* View builder handler for aggregator feed items.
*/
class ItemViewBuilder extends EntityViewBuilder {

View file

@ -125,14 +125,21 @@
// Make our new row and select field.
var row = $(this).closest('tr');
var select = $(this);
// Find the correct region and insert the row as the last in the
// region.
table.find('.region-' + select[0].value + '-message').nextUntil('.region-message').eq(-1).before(row);
tableDrag.rowObject = new tableDrag.row(row[0]);
var region_message = table.find('.region-' + select[0].value + '-message');
var region_items = region_message.nextUntil('.region-message, .region-title');
if (region_items.length) {
region_items.last().after(row);
}
// We found that region_message is the last row.
else {
region_message.after(row);
}
updateBlockWeights(table, select[0].value);
// Modify empty regions with added or removed fields.
checkEmptyRegions(table, row);
checkEmptyRegions(table, tableDrag.rowObject);
// Update last placed block indication.
updateLastPlaced(table, row);
// Show unsaved changes warning.

View file

@ -74,7 +74,6 @@ process:
help: help
header: header
footer: footer
label: title
weight: weight
settings:
plugin: block_settings
@ -82,6 +81,7 @@ process:
- '@plugin'
- delta
- settings
- title
visibility:
plugin: block_visibility
source:

View file

@ -75,7 +75,6 @@ process:
help: help
header: header
footer: footer
label: title
weight: weight
settings:
plugin: block_settings
@ -83,6 +82,7 @@ process:
- '@plugin'
- delta
- settings
- title
visibility:
plugin: block_visibility
source:

View file

@ -7,6 +7,7 @@
namespace Drupal\block\Plugin\migrate\process;
use Drupal\block\BlockInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
@ -24,8 +25,15 @@ class BlockSettings extends ProcessPluginBase {
* Set the block configuration.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($plugin, $delta, $old_settings) = $value;
list($plugin, $delta, $old_settings, $title) = $value;
$settings = array();
$settings['label'] = $title;
if ($title) {
$settings['label_display'] = BlockInterface::BLOCK_LABEL_VISIBLE;
}
else {
$settings['label_display'] = '0';
}
switch ($plugin) {
case 'aggregator_feed_block':
list(, $id) = explode('-', $delta);

View file

@ -69,14 +69,22 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
* The theme.
* @param string $weight
* The block weight.
* @param string $label
* The block label.
* @param string $label_display
* The block label display setting.
*/
public function assertEntity($id, $visibility, $region, $theme, $weight) {
public function assertEntity($id, $visibility, $region, $theme, $weight, $label, $label_display) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
$this->assertIdentical($visibility, $block->getVisibility());
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
$this->assertIdentical($label_display, $config->get('settings.label_display'));
}
/**
@ -91,10 +99,10 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = "<front>\n/node/1\n/blog/*";
$this->assertEntity('user', $visibility, 'sidebar_first', 'bartik', 0);
$this->assertEntity('user', $visibility, 'sidebar_first', 'bartik', 0, '', '0');
$visibility = [];
$this->assertEntity('user_1', $visibility, 'sidebar_first', 'bartik', 0);
$this->assertEntity('user_1', $visibility, 'sidebar_first', 'bartik', 0, '', '0');
$visibility['user_role']['id'] = 'user_role';
$roles['authenticated'] = 'authenticated';
@ -102,7 +110,7 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$context_mapping['user'] = '@user.current_user_context:current_user';
$visibility['user_role']['context_mapping'] = $context_mapping;
$visibility['user_role']['negate'] = FALSE;
$this->assertEntity('user_2', $visibility, 'sidebar_second', 'bartik', -9);
$this->assertEntity('user_2', $visibility, 'sidebar_second', 'bartik', -9, '', '0');
$visibility = [];
$visibility['user_role']['id'] = 'user_role';
@ -112,32 +120,32 @@ class MigrateBlockTest extends MigrateDrupal6TestBase {
$context_mapping['user'] = '@user.current_user_context:current_user';
$visibility['user_role']['context_mapping'] = $context_mapping;
$visibility['user_role']['negate'] = FALSE;
$this->assertEntity('user_3', $visibility, 'sidebar_second', 'bartik', -6);
$this->assertEntity('user_3', $visibility, 'sidebar_second', 'bartik', -6, '', '0');
// Check system block
$visibility = [];
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = TRUE;
$visibility['request_path']['pages'] = '/node/1';
$this->assertEntity('system', $visibility, 'footer', 'bartik', -5);
$this->assertEntity('system', $visibility, 'footer', 'bartik', -5, '', '0');
// Check menu blocks
$visibility = [];
$this->assertEntity('menu', $visibility, 'header', 'bartik', -5);
$this->assertEntity('menu', $visibility, 'header', 'bartik', -5, '', '0');
// Check custom blocks
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '<front>';
$this->assertEntity('block', $visibility, 'content', 'bartik', 0);
$this->assertEntity('block', $visibility, 'content', 'bartik', 0, 'Static Block', 'visible');
$visibility['request_path']['id'] = 'request_path';
$visibility['request_path']['negate'] = FALSE;
$visibility['request_path']['pages'] = '/node';
$this->assertEntity('block_1', $visibility, 'sidebar_second', 'bluemarine', -4);
$this->assertEntity('block_1', $visibility, 'sidebar_second', 'bluemarine', -4, 'Another Static Block', 'visible');
$visibility = [];
$this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7);
$this->assertEntity('block_2', $visibility, 'right', 'test_theme', -7, '', '0');
// Custom block with php code is not migrated.
$block = Block::load('block_3');

View file

@ -77,8 +77,13 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
* The theme.
* @param string $weight
* The block weight.
* @param string $label
* The block label.
* @param string $label_display
* The block label display setting.
*/
public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight) {
public function assertEntity($id, $plugin_id, array $roles, $pages, $region, $theme, $weight, $label, $label_display) {
$block = Block::load($id);
$this->assertTrue($block instanceof Block);
/** @var \Drupal\block\BlockInterface $block */
@ -96,24 +101,28 @@ class MigrateBlockTest extends MigrateDrupal7TestBase {
$this->assertIdentical($region, $block->getRegion());
$this->assertIdentical($theme, $block->getTheme());
$this->assertIdentical($weight, $block->getWeight());
$config = $this->config('block.block.' . $id);
$this->assertIdentical($label, $config->get('settings.label'));
$this->assertIdentical($label_display, $config->get('settings.label_display'));
}
/**
* Tests the block migration.
*/
public function testBlockMigration() {
$this->assertEntity('bartik_system_main', 'system_main_block', [], '', 'content', 'bartik', 0);
$this->assertEntity('bartik_search_form', 'search_form_block', [], '', 'sidebar_first', 'bartik', -1);
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0);
$this->assertEntity('bartik_system_powered-by', 'system_powered_by_block', [], '', 'footer', 'bartik', 10);
$this->assertEntity('seven_system_main', 'system_main_block', [], '', 'content', 'seven', 0);
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10);
$this->assertEntity('bartik_system_main', 'system_main_block', [], '', 'content', 'bartik', 0, '', '0');
$this->assertEntity('bartik_search_form', 'search_form_block', [], '', 'sidebar_first', 'bartik', -1, '', '0');
$this->assertEntity('bartik_user_login', 'user_login_block', [], '', 'sidebar_first', 'bartik', 0, '', '0');
$this->assertEntity('bartik_system_powered-by', 'system_powered_by_block', [], '', 'footer', 'bartik', 10, '', '0');
$this->assertEntity('seven_system_main', 'system_main_block', [], '', 'content', 'seven', 0, '', '0');
$this->assertEntity('seven_user_login', 'user_login_block', [], '', 'content', 'seven', 10, '', '0');
// The d7_custom_block migration should have migrated a block containing a
// mildly amusing limerick. We'll need its UUID to determine
// bartik_block_1's plugin ID.
$uuid = BlockContent::load(1)->uuid();
$this->assertEntity('bartik_block_1', 'block_content:' . $uuid, ['authenticated'], '', 'highlighted', 'bartik', 0);
$this->assertEntity('bartik_block_1', 'block_content:' . $uuid, ['authenticated'], '', 'highlighted', 'bartik', 0, 'Mildly amusing limerick of the day', 'visible');
// Assert that disabled blocks (or enabled blocks whose plugin IDs could
// be resolved) did not migrate.

View file

@ -16,7 +16,7 @@ use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the custom block edit forms.
* Form handler for the custom block edit forms.
*/
class BlockContentForm extends ContentEntityForm {

View file

@ -12,7 +12,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityViewBuilder;
/**
* Render controller for custom blocks.
* View builder handler for custom blocks.
*/
class BlockContentViewBuilder extends EntityViewBuilder {

View file

@ -587,18 +587,20 @@ class BookManager implements BookManagerInterface {
* The Book ID to find links for.
* @param array $parameters
* (optional) An associative array of build parameters. Possible keys:
* - expanded: An array of parent link ids to return only book links that
* are children of one of the plids in this list. If empty, the whole
* outline is built, unless 'only_active_trail' is TRUE.
* - active_trail: An array of nids, representing the coordinates of the
* currently active book link.
* - expanded: An array of parent link IDs to return only book links that
* are children of one of the parent link IDs in this list. If empty,
* the whole outline is built, unless 'only_active_trail' is TRUE.
* - active_trail: An array of node IDs, representing the currently active
* book link.
* - only_active_trail: Whether to only return links that are in the active
* trail. This option is ignored, if 'expanded' is non-empty.
* trail. This option is ignored if 'expanded' is non-empty.
* - min_depth: The minimum depth of book links in the resulting tree.
* Defaults to 1, which is the default to build a whole tree for a book.
* Defaults to 1, which is to build the whole tree for the book.
* - max_depth: The maximum depth of book links in the resulting tree.
* - conditions: An associative array of custom database select query
* condition key/value pairs; see _menu_build_tree() for the actual query.
* condition key/value pairs; see
* \Drupal\book\BookOutlineStorage::getBookMenuTree() for the actual
* query.
*
* @return array
* A fully built book tree.
@ -618,7 +620,29 @@ class BookManager implements BookManagerInterface {
* to further massage the data manually before further processing happens.
* _menu_tree_check_access() needs to be invoked afterwards.
*
* @see menu_build_tree()
* @param int $bid
* The book ID to find links for.
* @param array $parameters
* (optional) An associative array of build parameters. Possible keys:
* - expanded: An array of parent link IDs to return only book links that
* are children of one of the parent link IDs in this list. If empty,
* the whole outline is built, unless 'only_active_trail' is TRUE.
* - active_trail: An array of node IDs, representing the currently active
* book link.
* - only_active_trail: Whether to only return links that are in the active
* trail. This option is ignored if 'expanded' is non-empty.
* - min_depth: The minimum depth of book links in the resulting tree.
* Defaults to 1, which is to build the whole tree for the book.
* - max_depth: The maximum depth of book links in the resulting tree.
* - conditions: An associative array of custom database select query
* condition key/value pairs; see
* \Drupal\book\BookOutlineStorage::getBookMenuTree() for the actual
* query.
*
* @return array
* An array with links representing the tree structure of the book.
*
* @see \Drupal\book\BookOutlineStorageInterface::getBookMenuTree()
*/
protected function doBookTreeBuild($bid, array $parameters = array()) {
// Static cache of already built menu trees.
@ -885,7 +909,7 @@ class BookManager implements BookManagerInterface {
* Sets the p1 through p9 properties for a book link being saved.
*
* @param array $link
* The book link to update.
* The book link to update, passed by reference.
* @param array $parent
* The parent values to set.
*/
@ -930,6 +954,9 @@ class BookManager implements BookManagerInterface {
/**
* Sorts the menu tree and recursively checks access for each item.
*
* @param array $tree
* The book tree to operate on.
*/
protected function doBookTreeCheckAccess(&$tree) {
$new_tree = array();
@ -1010,6 +1037,20 @@ class BookManager implements BookManagerInterface {
*
* The function is a bit complex because the rendering of a link depends on
* the next book link.
*
* @param array $links
* A flat array of book links that are part of the book. Each array element
* is an associative array of information about the book link, containing
* the fields from the {book} table. This array must be ordered depth-first.
* @param array $parents
* An array of the node ID values that are in the path from the current page
* to the root of the book tree.
* @param int $depth
* The minimum depth to include in the returned book tree.
*
* @return array
* Book tree.
*
*/
protected function buildBookOutlineRecursive(&$links, $parents, $depth) {
$tree = array();

View file

@ -253,7 +253,7 @@
// the "image" command's CKEditor dialog with a Drupal-native dialog.
editor.addCommand('editdrupalimage', {
allowedContent: 'img[alt,!src,width,height,!data-entity-type,!data-entity-uuid]',
requiredContent: 'img[alt,src,width,height,data-entity-type,data-entity-uuid]',
requiredContent: 'img[alt,src,data-entity-type,data-entity-uuid]',
modes: {wysiwyg: 1},
canUndo: true,
exec: function (editor, data) {

View file

@ -42,8 +42,8 @@ interface CKEditorPluginConfigurableInterface extends CKEditorPluginInterface {
* @param \Drupal\editor\Entity\Editor $editor
* A configured text editor object.
*
* @return array|FALSE
* A render array for the settings form, or FALSE if there is none.
* @return array
* A render array for the settings form.
*/
public function settingsForm(array $form, FormStateInterface $form_state, Editor $editor);

View file

@ -20,7 +20,7 @@ use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base for controller for comment forms.
* Base handler for comment forms.
*/
class CommentForm extends ContentEntityForm {

View file

@ -19,7 +19,7 @@ use Drupal\Core\Language\LanguageManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines the controller class for comments.
* Defines the storage handler class for comments.
*
* This extends the Drupal\Core\Entity\Sql\SqlContentEntityStorage class,
* adding required special handling for comment entities.

View file

@ -16,7 +16,7 @@ use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Base form controller for category edit forms.
* Base form handler for comment type edit forms.
*/
class CommentTypeForm extends EntityForm {

View file

@ -18,7 +18,7 @@ use Drupal\Core\Session\AccountInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Render controller for comments.
* View builder handler for comments.
*/
class CommentViewBuilder extends EntityViewBuilder {

View file

@ -64,9 +64,14 @@ class ConfigOtherModuleTest extends WebTestBase {
// Ensure that optional configuration with unmet dependencies is only
// installed once all the dependencies are met.
$this->assertNull(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration whose dependencies are met is not created.');
$this->assertNull(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_unmet whose dependencies are not met is not created.');
$this->assertNull(entity_load('config_test', 'other_module_test_optional_entity_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_optional_entity_unmet whose dependencies are not met is not created.');
$this->installModule('config_install_dependency_test');
$this->assertTrue(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration whose dependencies are met is now created.');
$this->assertTrue(entity_load('config_test', 'other_module_test_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_unmet whose dependencies are met is now created.');
// Although the following configuration entity's are now met it is not
// installed because it does not have a direct dependency on the
// config_install_dependency_test module.
$this->assertNull(entity_load('config_test', 'other_module_test_optional_entity_unmet', TRUE), 'The optional configuration config_test.dynamic.other_module_test_optional_entity_unmet whose dependencies are met is not created.');
}
/**

View file

@ -0,0 +1,11 @@
id: other_module_test_optional_entity_unmet
label: 'Other module test to test optional config installation'
weight: 0
style: ''
status: true
langcode: en
protected_property: Default
dependencies:
enforced:
config:
- config_test.dynamic.other_module_test_unmet

View file

@ -8,6 +8,7 @@
namespace Drupal\config_translation\Tests;
use Drupal\Component\Serialization\Json;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\Language;
@ -733,6 +734,46 @@ class ConfigTranslationUiTest extends WebTestBase {
$this->assertEscaped($translatable_storage_setting);
}
/**
* Tests the translation of a boolean field settings.
*/
public function testBooleanFieldConfigTranslation() {
// Add a test boolean field.
$field_name = strtolower($this->randomMachineName());
FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => 'entity_test',
'type' => 'boolean',
])->save();
$bundle = strtolower($this->randomMachineName());
entity_test_create_bundle($bundle);
$field = FieldConfig::create([
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => $bundle,
]);
$on_label = 'On label (with <em>HTML</em> & things)';
$field->setSetting('on_label', $on_label);
$off_label = 'Off label (with <em>HTML</em> & things)';
$field->setSetting('off_label', $off_label);
$field->save();
$this->drupalLogin($this->translatorUser);
$this->drupalGet("/entity_test/structure/$bundle/fields/entity_test.$bundle.$field_name/translate");
$this->clickLink('Add');
// Checks the text of details summary element that surrounds the translation
// options.
$this->assertText(Html::escape(strip_tags($on_label)) . ' Boolean settings');
// Checks that the correct on and off labels appear on the form.
$this->assertEscaped($on_label);
$this->assertEscaped($off_label);
}
/**
* Test translation storage in locale storage.
*/

View file

@ -11,11 +11,14 @@ use Drupal\content_translation\ContentTranslationManagerInterface;
use Drupal\Component\Plugin\Derivative\DeriverBase;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\StringTranslation\TranslationInterface;
/**
* Provides dynamic local tasks for content translation.
*/
class ContentTranslationLocalTasks extends DeriverBase implements ContainerDeriverInterface {
use StringTranslationTrait;
/**
* The base plugin ID
@ -38,10 +41,13 @@ class ContentTranslationLocalTasks extends DeriverBase implements ContainerDeriv
* The base plugin ID.
* @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
* The content translation manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
* The translation manager.
*/
public function __construct($base_plugin_id, ContentTranslationManagerInterface $content_translation_manager) {
public function __construct($base_plugin_id, ContentTranslationManagerInterface $content_translation_manager, TranslationInterface $string_translation) {
$this->basePluginId = $base_plugin_id;
$this->contentTranslationManager = $content_translation_manager;
$this->stringTranslation = $string_translation;
}
/**
@ -50,7 +56,8 @@ class ContentTranslationLocalTasks extends DeriverBase implements ContainerDeriv
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$base_plugin_id,
$container->get('content_translation.manager')
$container->get('content_translation.manager'),
$container->get('string_translation')
);
}
@ -66,7 +73,7 @@ class ContentTranslationLocalTasks extends DeriverBase implements ContainerDeriv
$base_route_name = "entity.$entity_type_id.canonical";
$this->derivatives[$translation_route_name] = array(
'entity_type' => $entity_type_id,
'title' => 'Translate',
'title' => $this->t('Translate'),
'route_name' => $translation_route_name,
'base_route' => $base_route_name,
) + $base_plugin_definition;

View file

@ -37,8 +37,11 @@ class ContentTranslationOperationsTest extends NodeTestBase {
*
* @var array
*/
public static $modules = ['language', 'content_translation', 'node', 'views'];
public static $modules = ['language', 'content_translation', 'node', 'views', 'block'];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
@ -63,7 +66,7 @@ class ContentTranslationOperationsTest extends NodeTestBase {
/**
* Test that the operation "Translate" is displayed in the content listing.
*/
function testOperationTranslateLink() {
public function testOperationTranslateLink() {
$node = $this->drupalCreateNode(['type' => 'article', 'langcode' => 'es']);
// Verify no translation operation links are displayed for users without
// permission.
@ -103,9 +106,30 @@ class ContentTranslationOperationsTest extends NodeTestBase {
$node->setPublished(FALSE)->save();
$this->drupalGet($node->urlInfo('drupal:content-translation-overview'));
$this->assertResponse(403);
$this->drupalLogout();
// Ensure the 'Translate' local task does not show up anymore when disabling
// translations for a content type.
$node->setPublished(TRUE)->save();
user_role_change_permissions(
Role::AUTHENTICATED_ID,
[
'administer content translation' => TRUE,
'administer languages' => TRUE,
]
);
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalLogin($this->baseUser2);
$this->drupalGet('node/' . $node->id());
$this->assertLinkByHref('node/' . $node->id() . '/translations');
$this->drupalPostForm('admin/config/regional/content-language', ['settings[node][article][translatable]' => FALSE], t('Save configuration'));
$this->drupalGet('node/' . $node->id());
$this->assertNoLinkByHref('node/' . $node->id() . '/translations');
}
/**
* Tests the access to the overview page for translations.
*
* @see content_translation_translate_access()
*/
public function testContentTranslationOverviewAccess() {

View file

@ -37,6 +37,7 @@ class ContentTranslationLocalTasksTest extends LocalTaskIntegrationTestBase {
'node' => $entity_type,
)));
\Drupal::getContainer()->set('content_translation.manager', $content_translation_manager);
\Drupal::getContainer()->set('string_translation', $this->getStringTranslationStub());
}
/**

View file

@ -130,14 +130,14 @@ class EntityDisplayModeListBuilder extends ConfigEntityListBuilder {
}
/**
* Filters entities based on their controllers.
* Filters entities based on their view builder handlers.
*
* @param $entity_type
* The entity type of the entity that needs to be validated.
*
* @return bool
* TRUE if the entity has the correct controller, FALSE if the entity
* doesn't has the correct controller.
* TRUE if the entity has the correct view builder handler, FALSE if the
* entity doesn't have the correct view builder handler.
*/
protected function isValidEntity($entity_type) {
return $this->entityTypes[$entity_type]->get('field_ui_base_route') && $this->entityTypes[$entity_type]->hasViewBuilderClass();

View file

@ -15,7 +15,7 @@ namespace Drupal\field_ui;
class EntityFormModeListBuilder extends EntityDisplayModeListBuilder {
/**
* Filters entities based on their controllers.
* Filters entities based on their form mode handlers.
*
* @param $entity_type
* The entity type of the entity that needs to be validated.

View file

@ -472,7 +472,7 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl
}
$predefined[$key] = new TranslatableMarkup($value[0]);
}
asort($predefined);
natcasesort($predefined);
return $predefined;
}

View file

@ -24,6 +24,7 @@ use Drupal\language\ContentLanguageSettingsInterface;
* entity_keys = {
* "id" = "id"
* },
* list_cache_tags = { "rendered" }
* )
*/
class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguageSettingsInterface {

View file

@ -61,7 +61,7 @@ class LanguageListBuilder extends DraggableListBuilder {
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type definition.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The entity storage controller class.
* The entity storage handler class.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\language\Tests\LanguageListTest.
*/
namespace Drupal\language\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageInterface;
/**
* Adds a new language with translations and tests language list order.
*
* @group language
*/
class LanguageLocaleListTest extends WebTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('language', 'locale');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// Add a default locale storage for all these tests.
$this->storage = $this->container->get('locale.storage');
}
/**
* Tests adding, editing, and deleting languages.
*/
function testLanguageLocaleList() {
// User to add and remove language.
$admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
$this->drupalLogin($admin_user);
// Add predefined language.
$edit = array(
'predefined_langcode' => 'fr',
);
$this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language'));
$this->assertText('The language French has been created and can now be used');
$this->assertUrl(\Drupal::url('entity.configurable_language.collection', [], ['absolute' => TRUE]));
$this->rebuildContainer();
// Translate Spanish language to French (Espagnol).
$source = $this->storage->createString(array(
'source' => 'Spanish',
'context' => '',
))->save();
$this->storage->createTranslation(array(
'lid' => $source->lid,
'language' => 'fr',
'translation' => 'Espagnol',
))->save();
// Get language list displayed in select list.
$this->drupalGet('fr/admin/config/regional/language/add');
$select = $this->xpath('//select[@id="edit-predefined-langcode"]');
$select_element = (array) end($select);
$options = $select_element['option'];
// Remove the 'Custom language...' option form the end.
array_pop($options);
// Order language list.
$options_ordered = $options;
natcasesort($options_ordered);
// Check the language list displayed is ordered.
$this->assertTrue($options === $options_ordered, 'Language list is ordered.');
}
}

View file

@ -51,6 +51,10 @@ class CckLink extends ProcessPluginBase implements ContainerFactoryPluginInterfa
$attributes = unserialize($attributes);
}
if (!$attributes) {
$attributes = [];
}
// Massage the values into the correct form for the link.
$route['uri'] = $value['url'];
$route['options']['attributes'] = $attributes;

View file

@ -33,10 +33,10 @@ require_once __DIR__ . '/locale.translation.inc';
* batch is finished. Optional, defaults to TRUE.
* - 'use_remote': Whether or not to check the remote translation file.
* Optional, defaults to TRUE.
* @param array $context
* @param array|\ArrayAccess $context.
* The batch context.
*/
function locale_translation_batch_status_check($project, $langcode, array $options, array &$context) {
function locale_translation_batch_status_check($project, $langcode, array $options, &$context) {
$failure = $checked = FALSE;
$options += array(
'finish_feedback' => TRUE,

View file

@ -185,10 +185,10 @@ function locale_translate_batch_build(array $files, array $options) {
* LOCALE_NOT_CUSTOMIZED.
* - 'message': Alternative message to display during import. Note, this must
* be sanitized text.
* @param array $context
* @param array|\ArrayAccess $context.
* Contains a list of files imported.
*/
function locale_translate_batch_import($file, array $options, array &$context) {
function locale_translate_batch_import($file, array $options, &$context) {
// Merge the default values in the $options array.
$options += array(
'overwrite_options' => array(),
@ -268,10 +268,10 @@ function locale_translate_batch_import($file, array $options, array &$context) {
*
* Save data of imported files.
*
* @param array $context
* @param array|\ArrayAccess $context.
* Contains a list of imported files.
*/
function locale_translate_batch_import_save(array $context) {
function locale_translate_batch_import_save($context) {
if (isset($context['results']['files'])) {
foreach ($context['results']['files'] as $file) {
// Update the file history if both project and version are known. This
@ -293,10 +293,10 @@ function locale_translate_batch_import_save(array $context) {
*
* Refreshes translations after importing strings.
*
* @param array $context
* @param array|\ArrayAccess $context.
* Contains a list of strings updated and information about the progress.
*/
function locale_translate_batch_refresh(array &$context) {
function locale_translate_batch_refresh(&$context) {
if (!isset($context['sandbox']['refresh'])) {
$strings = $langcodes = array();
if (isset($context['results']['stats'])) {
@ -597,12 +597,12 @@ function locale_config_batch_build(array $names, array $langcodes, array $option
* An array of names of configuration objects to update.
* @param array $langcodes
* (optional) Array of language codes to update. Defaults to all languages.
* @param array $context
* @param array|\ArrayAccess $context.
* Contains a list of files imported.
*
* @see locale_config_batch_build()
*/
function locale_config_batch_refresh_name(array $names, array $langcodes, array &$context) {
function locale_config_batch_refresh_name(array $names, array $langcodes, &$context) {
if (!isset($context['result']['stats']['config'])) {
$context['result']['stats']['config'] = 0;
}

View file

@ -51,7 +51,7 @@ class MenuLink extends DrupalSqlBase {
'link_path' => t('The Drupal path or external path this link points to.'),
'router_path' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
'link_title' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
'options' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
'options' => t('A serialized array of options to set on the URL, such as a query string or HTML attributes.'),
'module' => t('The name of the module that generated this link.'),
'hidden' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
'external' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),

View file

@ -10,6 +10,7 @@ namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
@ -38,6 +39,13 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
*/
protected $config;
/**
* The language manager.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $language_manager;
/**
* Constructs a Config destination object.
*
@ -51,10 +59,13 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
* The migration entity.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
* @param \Drupal\Core\Language\ConfigurableLanguageManagerInterface $language_manager
* The language manager.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory) {
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->config = $config_factory->getEditable($configuration['config_name']);
$this->language_manager = $language_manager;
}
/**
@ -66,7 +77,8 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
$plugin_id,
$plugin_definition,
$migration,
$container->get('config.factory')
$container->get('config.factory'),
$container->get('language_manager')
);
}
@ -74,6 +86,10 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
if ($row->hasDestinationProperty('langcode')) {
$this->config = $this->language_manager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $this->config->getName());
}
foreach ($row->getRawDestination() as $key => $value) {
if (isset($value) || !empty($this->configuration['store null'])) {
$this->config->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $key), $value);

View file

@ -35,10 +35,7 @@ class Get extends ProcessPluginBase {
$properties = is_string($source) ? array($source) : $source;
$return = array();
foreach ($properties as $property) {
if (empty($property)) {
$return[] = $value;
}
else {
if ($property || (string) $property === '0') {
$is_source = TRUE;
if ($property[0] == '@') {
$property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
@ -57,7 +54,11 @@ class Get extends ProcessPluginBase {
$return[] = $row->getDestinationProperty($property);
}
}
else {
$return[] = $value;
}
}
if (is_string($source)) {
$this->multiple = is_array($return[0]);
return $return[0];

View file

@ -96,7 +96,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
protected $skipCount = FALSE;
/**
* Flags whether to track changes to incloming data.
* Flags whether to track changes to incoming data.
*
* If TRUE, we will maintain hashed source rows to determine whether incoming
* data has changed.

View file

@ -263,6 +263,15 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
}
$id_map_database_options = $id_map->getDatabase()->getConnectionOptions();
$source_database_options = $this->getDatabase()->getConnectionOptions();
// Special handling for sqlite which deals with files.
if ($id_map_database_options['driver'] === 'sqlite' &&
$source_database_options['driver'] === 'sqlite' &&
$id_map_database_options['database'] != $source_database_options['database']
) {
return FALSE;
}
foreach (array('username', 'password', 'host', 'port', 'namespace', 'driver') as $key) {
if (isset($source_database_options[$key])) {
if ($id_map_database_options[$key] != $source_database_options[$key]) {

View file

@ -107,16 +107,33 @@ class SqlBaseTest extends UnitTestCase {
FALSE,
TRUE,
TRUE,
['username' => 'different_from_map', 'password' => 'different_from_map'],
['username' => 'different_from_source', 'password' => 'different_from_source'],
['driver' => 'mysql', 'username' => 'different_from_map', 'password' => 'different_from_map'],
['driver' => 'mysql', 'username' => 'different_from_source', 'password' => 'different_from_source'],
],
// Returns true because source and id map connection options are the same.
[
TRUE,
TRUE,
TRUE,
['username' => 'same_value', 'password' => 'same_value'],
['username' => 'same_value', 'password' => 'same_value'],
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
],
// Returns false because driver is sqlite and the databases are not the
// same.
[
FALSE,
TRUE,
TRUE,
['driver' => 'sqlite', 'database' => '1.sqlite', 'username' => '', 'password' => ''],
['driver' => 'sqlite', 'database' => '2.sqlite', 'username' => '', 'password' => ''],
],
// Returns false because driver is not the same.
[
FALSE,
TRUE,
TRUE,
['driver' => 'pgsql', 'username' => 'same_value', 'password' => 'same_value'],
['driver' => 'mysql', 'username' => 'same_value', 'password' => 'same_value'],
],
];
}

View file

@ -49,9 +49,72 @@ class ConfigTest extends UnitTestCase {
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue(FALSE));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory);
$language_manager = $this->getMockBuilder('Drupal\language\ConfigurableLanguageManagerInterface')
->disableOriginalConstructor()
->getMock();
$language_manager->expects($this->never())
->method('getLanguageConfigOverride')
->with('fr', 'd8_config')
->will($this->returnValue($config));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination_id = $destination->import($row);
$this->assertEquals($destination_id, ['d8_config']);
}
/**
* Test the import method.
*/
public function testLanguageImport() {
$source = array(
'langcode' => 'mi',
);
$migration = $this->getMockBuilder('Drupal\migrate\Entity\Migration')
->disableOriginalConstructor()
->getMock();
$config = $this->getMockBuilder('Drupal\Core\Config\Config')
->disableOriginalConstructor()
->getMock();
foreach ($source as $key => $val) {
$config->expects($this->once())
->method('set')
->with($this->equalTo($key), $this->equalTo($val))
->will($this->returnValue($config));
}
$config->expects($this->once())
->method('save');
$config->expects($this->any())
->method('getName')
->willReturn('d8_config');
$config_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface');
$config_factory->expects($this->once())
->method('getEditable')
->with('d8_config')
->will($this->returnValue($config));
$row = $this->getMockBuilder('Drupal\migrate\Row')
->disableOriginalConstructor()
->getMock();
$row->expects($this->once())
->method('hasDestinationProperty')
->will($this->returnValue($source));
$row->expects($this->any())
->method('getRawDestination')
->will($this->returnValue($source));
$row->expects($this->any())
->method('getDestinationProperty')
->will($this->returnValue($source['langcode']));
$language_manager = $this->getMockBuilder('Drupal\language\ConfigurableLanguageManagerInterface')
->disableOriginalConstructor()
->getMock();
$language_manager->expects($this->any())
->method('getLanguageConfigOverride')
->with('mi', 'd8_config')
->will($this->returnValue($config));
$destination = new Config(array('config_name' => 'd8_config'), 'd8_config', array('pluginId' => 'd8_config'), $migration, $config_factory, $language_manager);
$destination_id = $destination->import($row);
$this->assertEquals($destination_id, ['d8_config']);
}

View file

@ -82,6 +82,28 @@ class GetTest extends MigrateProcessTestCase {
$value = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame($value, array('source_value1', 'source_value2', 'source_value3', 'source_value4'));
}
/**
* Tests the Get plugin when source has integer values.
*/
public function testIntegerValues() {
$this->row->expects($this->exactly(2))
->method('getSourceProperty')
->willReturnOnConsecutiveCalls('val1', 'val2');
$this->plugin->setSource([0 => 0, 1 => 'test']);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([0 => 'val1', 1 => 'val2'], $return);
$this->plugin->setSource([FALSE]);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([NULL], $return);
$this->plugin->setSource([NULL]);
$return = $this->plugin->transform(NULL, $this->migrateExecutable, $this->row, 'destinationproperty');
$this->assertSame([NULL], $return);
}
}
namespace Drupal\migrate\Plugin\migrate\process;

View file

@ -151,3 +151,42 @@ migrate_entity_constant:
migrate.source.md_empty:
type: migrate.source.empty
label: 'Empty source for migrate_drupal migrations'
migrate.source.i18n_variable:
type: migrate_source_sql
label: 'i18n Variable'
mapping:
variables:
type: sequence
label: 'Variables'
sequence:
type: string
label: 'Variable'
constants:
type: mapping
label: 'Constants'
mapping:
entity_type:
type: string
label: 'Entity type'
id:
type: string
label: 'ID'
label:
type: label
label: 'Label'
description:
type: text
label: 'Description'
path:
type: string
label: 'Path'
plugin:
type: string
label: 'Plugin'
status:
type: boolean
label: 'Status'
slash:
type: string
label: 'Slash'

View file

@ -0,0 +1,105 @@
<?php
/**
* @file
* Contains \Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable.
*/
namespace Drupal\migrate_drupal\Plugin\migrate\source\d6;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
/**
* Drupal i18n_variable source from database.
*
* @MigrateSource(
* id = "i18n_variable"
* )
*/
class i18nVariable extends DrupalSqlBase {
/**
* The variable names to fetch.
*
* @var array
*/
protected $variables;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, StateInterface $state, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $state, $entity_manager);
$this->variables = $this->configuration['variables'];
}
/**
* {@inheritdoc}
*/
protected function initializeIterator() {
return new \ArrayIterator($this->values());
}
/**
* Return the values of the variables specified in the plugin configuration.
*
* @return array
* An associative array where the keys are the variables specified in the
* plugin configuration and the values are the values found in the source.
* A key/value pair is added for the language code. Only those values are
* returned that are actually in the database.
*/
protected function values() {
$values = [];
$result = $this->prepareQuery()->execute()->FetchAllAssoc('language');
foreach ($result as $i18n_variable) {
$values[]['language'] = $i18n_variable->language;
}
$result = $this->prepareQuery()->execute()->FetchAll();
foreach ($result as $i18n_variable) {
foreach ($values as $key => $value) {
if ($values[$key]['language'] === $i18n_variable->language) {
$values[$key][$i18n_variable->name] = unserialize($i18n_variable->value);
break;
}
}
}
return $values;
}
/**
* {@inheritdoc}
*/
public function count() {
return $this->initializeIterator()->count();
}
/**
* {@inheritdoc}
*/
public function fields() {
return array_combine($this->variables, $this->variables);
}
/**
* {@inheritdoc}
*/
public function query() {
return $this->getDatabase()
->select('i18n_variable', 'v')
->fields('v')
->condition('name', (array) $this->configuration['variables'], 'IN');
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['language']['type'] = 'string';
return $ids;
}
}

View file

@ -30531,6 +30531,21 @@ $connection->insert('profile_fields')
'autocomplete' => '1',
'options' => '',
))
->values(array(
'fid' => '13',
'title' => 'Blog',
'name' => 'profile_blog',
'explanation' => 'Paste the full URL, including http://, of your personal blog.',
'category' => 'Personal information',
'page' => '',
'type' => 'url',
'weight' => '3',
'required' => '0',
'register' => '0',
'visibility' => '3',
'autocomplete' => '0',
'options' => '',
))
->values(array(
'fid' => '14',
'title' => 'Birthdate',
@ -30561,21 +30576,6 @@ $connection->insert('profile_fields')
'autocomplete' => '0',
'options' => '',
))
->values(array(
'fid' => '16',
'title' => 'Blog',
'name' => 'profile_blog',
'explanation' => 'Paste the full URL, including http://, of your personal blog.',
'category' => 'Personal information',
'page' => '',
'type' => 'url',
'weight' => '3',
'required' => '0',
'register' => '0',
'visibility' => '3',
'autocomplete' => '0',
'options' => '',
))
->execute();
$connection->schema()->createTable('profile_values', array(
@ -34072,7 +34072,7 @@ $connection->insert('vocabulary')
'help' => '',
'relations' => '1',
'hierarchy' => '2',
'multiple' => '0',
'multiple' => '1',
'required' => '0',
'tags' => '0',
'module' => 'taxonomy',

View file

@ -0,0 +1,66 @@
<?php
/**
* @file
* Contains \Drupal\Tests\migrate_drupal\Unit\source\d6\i18nVariableTest.
*/
namespace Drupal\Tests\migrate_drupal\Unit\source\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
/**
* Tests the variable source plugin.
*
* @group migrate_drupal
*/
class i18nVariableTest extends MigrateSqlSourceTestCase {
// The plugin system is not working during unit testing so the source plugin
// class needs to be manually specified.
const PLUGIN_CLASS = 'Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable';
/**
* Define bare minimum migration configuration.
*/
protected $migrationConfiguration = [
'id' => 'test',
'highWaterProperty' => array('field' => 'test'),
'source' => [
'plugin' => 'i18n_variable',
'variables' => [
'site_slogan',
'site_name',
],
],
];
/**
* Expected results from the source.
*/
protected $expectedResults = [
[
'language' => 'fr',
'site_slogan' => 'Migrate est génial',
'site_name' => 'nom de site',
],
[
'language' => 'mi',
'site_slogan' => 'Ko whakamataku heke',
'site_name' => 'ingoa_pae',
]
];
/**
* Database contents for tests.
*/
protected $databaseContents = [
'i18n_variable' => [
array('name' => 'site_slogan', 'language' => 'fr', 'value' => 's:19:"Migrate est génial";'),
array('name' => 'site_name', 'language' => 'fr', 'value' => 's:11:"nom de site";'),
array('name' => 'site_slogan', 'language' => 'mi', 'value' => 's:19:"Ko whakamataku heke";'),
array('name' => 'site_name', 'language' => 'mi', 'value' => 's:9:"ingoa_pae";'),
],
];
}

View file

@ -108,10 +108,10 @@ function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode
* @param bool $revisions
* (optional) TRUE if $nodes contains an array of revision IDs instead of
* node IDs. Defaults to FALSE; will be ignored if $load is FALSE.
* @param array $context
* @param array|\ArrayAccess $context.
* An array of contextual key/values.
*/
function _node_mass_update_batch_process(array $nodes, array $updates, $load, $revisions, array &$context) {
function _node_mass_update_batch_process(array $nodes, array $updates, $load, $revisions, &$context) {
if (!isset($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = count($nodes);

View file

@ -1046,18 +1046,29 @@ function node_query_node_access_alter(AlterableInterface $query) {
$tables = $query->getTables();
$base_table = $query->getMetaData('base_table');
// If the base table is not given, default to node if present.
// If the base table is not given, default to one of the node base tables.
if (!$base_table) {
/** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */
$table_mapping = \Drupal::entityTypeManager()->getStorage('node')->getTableMapping();
$node_base_tables = $table_mapping->getTableNames();
foreach ($tables as $table_info) {
if (!($table_info instanceof SelectInterface)) {
$table = $table_info['table'];
// If the node table is in the query, it wins immediately.
// Ensure that 'node' and 'node_field_data' are always preferred over
// 'node_revision' and 'node_field_revision'.
if ($table == 'node' || $table == 'node_field_data') {
$base_table = $table;
break;
}
// If one of the node base tables are in the query, add it to the list
// of possible base tables to join against.
if (in_array($table, $node_base_tables)) {
$base_table = $table;
}
}
}
// Bail out if the base table is missing.
if (!$base_table) {
throw new Exception(t('Query tagged for node access but there is no node table, specify the base_table using meta data.'));

View file

@ -14,7 +14,7 @@ use Drupal\user\PrivateTempStoreFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for the node edit forms.
* Form handler for the node edit forms.
*/
class NodeForm extends ContentEntityForm {

View file

@ -16,7 +16,7 @@ use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Session\AccountInterface;
/**
* Defines a controller class that handles the node grants system.
* Defines a storage handler class that handles the node grants system.
*
* This is used to build node query access.
*

View file

@ -12,7 +12,7 @@ use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Language\LanguageInterface;
/**
* Defines the controller class for nodes.
* Defines the storage handler class for nodes.
*
* This extends the base storage class, adding required special handling for
* node entities.

View file

@ -15,7 +15,7 @@ use Drupal\language\Entity\ContentLanguageSettings;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Form controller for node type forms.
* Form handler for node type forms.
*/
class NodeTypeForm extends BundleEntityFormBase {

View file

@ -13,7 +13,7 @@ use Drupal\Core\Entity\EntityViewBuilder;
use Drupal\node\Entity\Node;
/**
* Render controller for nodes.
* View builder handler for nodes.
*/
class NodeViewBuilder extends EntityViewBuilder {

View file

@ -231,7 +231,7 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
->select('search_index', 'i', array('target' => 'replica'))
->extend('Drupal\search\SearchQuery')
->extend('Drupal\Core\Database\Query\PagerSelectExtender');
$query->join('node_field_data', 'n', 'n.nid = i.sid');
$query->join('node_field_data', 'n', 'n.nid = i.sid AND n.langcode = i.langcode');
$query->condition('n.status', 1)
->addTag('node_access')
->searchExpression($keys, $this->getPluginId());
@ -429,8 +429,23 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
// per cron run.
$limit = (int) $this->searchSettings->get('index.cron_limit');
$result = $this->database->queryRange("SELECT n.nid, MAX(sd.reindex) FROM {node} n LEFT JOIN {search_dataset} sd ON sd.sid = n.nid AND sd.type = :type WHERE sd.sid IS NULL OR sd.reindex <> 0 GROUP BY n.nid ORDER BY MAX(sd.reindex) is null DESC, MAX(sd.reindex) ASC, n.nid ASC", 0, $limit, array(':type' => $this->getPluginId()), array('target' => 'replica'));
$nids = $result->fetchCol();
$query = db_select('node', 'n', array('target' => 'replica'));
$query->addField('n', 'nid');
$query->leftJoin('search_dataset', 'sd', 'sd.sid = n.nid AND sd.type = :type', array(':type' => $this->getPluginId()));
$query->addExpression('CASE MAX(sd.reindex) WHEN NULL THEN 0 ELSE 1 END', 'ex');
$query->addExpression('MAX(sd.reindex)', 'ex2');
$query->condition(
$query->orConditionGroup()
->where('sd.sid IS NULL')
->condition('sd.reindex', 0, '<>')
);
$query->orderBy('ex', 'DESC')
->orderBy('ex2')
->orderBy('n.nid')
->groupBy('n.nid')
->range(0, $limit);
$nids = $query->execute()->fetchCol();
if (!$nids) {
return;
}

View file

@ -19,7 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class Type extends StringArgument {
/**
* NodeType storage controller.
* NodeType storage handler.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/

View file

@ -57,6 +57,30 @@ class MigrateNodeTest extends MigrateNodeTestBase {
$this->assertIdentical('test rev 3', $node->body->value);
$this->assertIdentical('filtered_html', $node->body->format);
// Test that link fields are migrated.
$this->assertIdentical('http://groups.drupal.org/', $node->field_test_link->uri);
$this->assertIdentical('Drupal Groups', $node->field_test_link->title);
$this->assertIdentical([], $node->field_test_link->options['attributes']);
// Rerun migration with invalid link attributes and a different URL and
// title. If only the attributes are changed the error does not occur.
Database::getConnection('default', 'migrate')
->update('content_type_story')
->fields([
'field_test_link_url' => 'https://www.drupal.org/node/2127611',
'field_test_link_title' => 'Migrate API in Drupal 8',
'field_test_link_attributes' => '',
])
->condition('nid', '2')
->condition('vid', '3')
->execute();
$this->rerunMigration();
$node = Node::load(2);
$this->assertIdentical('https://www.drupal.org/node/2127611', $node->field_test_link->uri);
$this->assertIdentical('Migrate API in Drupal 8', $node->field_test_link->title);
$this->assertIdentical([], $node->field_test_link->options['attributes']);
// Test that we can re-import using the EntityContentBase destination.
$title = $this->rerunMigration();
$node = Node::load(2);

View file

@ -72,6 +72,24 @@ class NodeQueryAlterTest extends NodeTestBase {
}
}
/**
* Tests 'node_access' query alter with revision-enabled nodes.
*/
public function testNodeQueryAlterWithRevisions() {
// Execute a query that only deals with the 'node_revision' table.
try {
$query = \Drupal::entityTypeManager()->getStorage('node')->getQuery();
$result = $query
->allRevisions()
->execute();
$this->assertEqual(count($result), 4, 'User with access can see correct nodes');
}
catch (\Exception $e) {
$this->fail('Altered query is malformed');
}
}
/**
* Tests 'node_access' query alter, for user without access.
*

View file

@ -14,6 +14,10 @@ use Drupal\simpletest\WebTestBase;
/**
* Ensures that node types translation work correctly.
*
* Note that the child site is installed in French; therefore, when making
* assertions on translated text it is important to provide a langcode. This
* ensures the asserts pass regardless of the Drupal version.
*
* @group node
*/
class NodeTypeTranslationTest extends WebTestBase {
@ -105,12 +109,16 @@ class NodeTypeTranslationTest extends WebTestBase {
// Check the name is translated without admin theme for editing.
$this->drupalPostForm('admin/appearance', array('use_admin_theme' => '0'), t('Save configuration'));
$this->drupalGet("$langcode/node/add/$type");
$this->assertRaw(t('Create @name', array('@name' => $translated_name)));
// This is a Spanish page, so ensure the text asserted is translated in
// Spanish and not French by adding the langcode option.
$this->assertRaw(t('Create @name', array('@name' => $translated_name), array('langcode' => $langcode)));
// Check the name is translated with admin theme for editing.
$this->drupalPostForm('admin/appearance', array('use_admin_theme' => '1'), t('Save configuration'));
$this->drupalGet("$langcode/node/add/$type");
$this->assertRaw(t('Create @name', array('@name' => $translated_name)));
// This is a Spanish page, so ensure the text asserted is translated in
// Spanish and not French by adding the langcode option.
$this->assertRaw(t('Create @name', array('@name' => $translated_name), array('langcode' => $langcode)));
}
/**
@ -128,17 +136,19 @@ class NodeTypeTranslationTest extends WebTestBase {
// Assert that the title label is displayed on the translation form with the right value.
$this->drupalGet("admin/structure/types/manage/$type/translate/$langcode/add");
$this->assertRaw(t('Label'));
$this->assertRaw(t('Edited title'));
$this->assertText('Edited title');
// Translate the title label.
$this->drupalPostForm(NULL, array("translation[config_names][core.base_field_override.node.$type.title][label]" => 'Translated title'), t('Save translation'));
// Assert that the right title label is displayed on the node add form.
// Assert that the right title label is displayed on the node add form. The
// translations are created in this test; therefore, the assertions do not
// use t(). If t() were used then the correct langcodes would need to be
// provided.
$this->drupalGet("node/add/$type");
$this->assertRaw(t('Edited title'));
$this->assertText('Edited title');
$this->drupalGet("$langcode/node/add/$type");
$this->assertRaw(t('Translated title'));
$this->assertText('Translated title');
}
}

View file

@ -18,6 +18,8 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Represents entities as resources.
*
* @see \Drupal\rest\Plugin\Deriver\EntityDeriver
*
* @RestResource(
* id = "entity",
* label = @Translation("Entity"),
@ -28,8 +30,6 @@ use Symfony\Component\HttpKernel\Exception\HttpException;
* "https://www.drupal.org/link-relations/create" = "/entity/{entity_type}"
* }
* )
*
* @see \Drupal\rest\Plugin\Deriver\EntityDeriver
*/
class EntityResource extends ResourceBase {

View file

@ -93,6 +93,7 @@ abstract class RESTTestBase extends WebTestBase {
$url = $this->buildUrl($url);
$curl_options = array();
switch ($method) {
case 'GET':
// Set query if there are additional GET parameters.
@ -105,6 +106,16 @@ abstract class RESTTestBase extends WebTestBase {
);
break;
case 'HEAD':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,
CURLOPT_CUSTOMREQUEST => 'HEAD',
CURLOPT_URL => $url,
CURLOPT_NOBODY => TRUE,
CURLOPT_HTTPHEADER => array('Accept: ' . $mime_type),
);
break;
case 'POST':
$curl_options = array(
CURLOPT_HTTPGET => FALSE,

View file

@ -0,0 +1,79 @@
<?php
/**
* @file
* Contains \Drupal\search\Tests\SearchDateIntervalTest.
*/
namespace Drupal\search\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests searching with date filters that exclude some translations.
*
* @group search
*/
class SearchDateIntervalTest extends SearchTestBase {
/**
* Modules to enable.
*
* @var string[]
*/
public static $modules = ['language', 'search_date_query_alter'];
protected function setUp() {
parent::setUp();
// Create and log in user.
$test_user = $this->drupalCreateUser(['access content', 'search content', 'use advanced search', 'administer nodes', 'administer languages', 'access administration pages', 'administer site configuration']);
$this->drupalLogin($test_user);
// Add a new language.
ConfigurableLanguage::createFromLangcode('es')->save();
// Set up times to be applied to the English and Spanish translations of the
// node create time, so that they are filtered in/out in the
// search_date_query_alter test module.
$created_time_en = new \DateTime('February 10 2016 10PM');
$created_time_es = new \DateTime('March 19 2016 10PM');
$default_format = filter_default_format();
$node = $this->drupalCreateNode([
'title' => 'Node EN',
'type' => 'page',
'body' => [
'value' => $this->randomMachineName(32),
'format' => $default_format,
],
'langcode' => 'en',
'created' => $created_time_en->format('U'),
]);
// Add Spanish translation to the node.
$translation = $node->addTranslation('es', ['title' => 'Node ES']);
$translation->body->value = $this->randomMachineName(32);
$translation->created->value = $created_time_es->format('U');
$node->save();
// Update the index.
$plugin = $this->container->get('plugin.manager.search')->createInstance('node_search');
$plugin->updateIndex();
search_update_totals();
}
/**
* Tests searching with date filters that exclude some translations.
*/
public function testDateIntervalQueryAlter() {
// Search for keyword node.
$edit = ['keys' => 'node'];
$this->drupalPostForm('search/node', $edit, t('Search'));
// The nodes must have the same node ID but the created date is different.
// So only the Spanish translation must appear.
$this->assertLink('Node ES', 0, 'Spanish translation found in search results');
$this->assertNoLink('Node EN', 'Search results do not contain English node');
}
}

View file

@ -0,0 +1,6 @@
name: 'Search Date Query Alter'
type: module
description: 'Test module that adds date conditions to node searches.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,18 @@
<?php
/**
* @file
* Adds date conditions to node searches.
*/
use Drupal\Core\Database\Query\AlterableInterface;
/**
* Implements hook_query_TAG_alter(): tag search_$type with $type node_search.
*/
function search_date_query_alter_query_search_node_search_alter(AlterableInterface $query) {
// Start date Sat, 19 Mar 2016 00:00:00 GMT.
$query->condition('n.created', 1458345600, '>=');
// End date Sun, 20 Mar 2016 00:00:00 GMT.
$query->condition('n.created', 1458432000, '<');
}

View file

@ -11,7 +11,7 @@ use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for the shortcut entity forms.
* Form handler for the shortcut entity forms.
*/
class ShortcutForm extends ContentEntityForm {

View file

@ -11,7 +11,7 @@ use Drupal\Core\Entity\BundleEntityFormBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Form controller for the shortcut set entity edit forms.
* Form handler for the shortcut set entity edit forms.
*/
class ShortcutSetForm extends BundleEntityFormBase {

View file

@ -489,9 +489,10 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) {
* each module for files matching the PSR-0 standard. Once loaded the test list
* is cached and stored in a static variable.
*
* @param string $module
* Name of a module. If set then only tests belonging to this module are
* returned.
* @param string $extension
* (optional) The name of an extension to limit discovery to; e.g., 'node'.
* @param string[] $types
* An array of included test types.
*
* @return array[]
* An array of tests keyed with the groups, and then keyed by test classes.
@ -506,8 +507,8 @@ function simpletest_log_read($test_id, $database_prefix, $test_class) {
* );
* @endcode
*/
function simpletest_test_get_all($module = NULL) {
return \Drupal::service('test_discovery')->getTestClasses($module);
function simpletest_test_get_all($extension = NULL, array $types = []) {
return \Drupal::service('test_discovery')->getTestClasses($extension, $types);
}
/**

View file

@ -1,4 +1,4 @@
services:
test_discovery:
class: Drupal\simpletest\TestDiscovery
arguments: ['@class_loader', '@?cache.discovery']
arguments: ['@app.root', '@class_loader', '@module_handler', '@?cache.discovery']

View file

@ -23,7 +23,7 @@ trait AssertHelperTrait {
* @return mixed
* The input value, with MarkupInterface objects casted to string.
*/
protected function castSafeStrings($value) {
protected static function castSafeStrings($value) {
if ($value instanceof MarkupInterface) {
$value = (string) $value;
}

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