diff --git a/composer.lock b/composer.lock
index e092980a1..e20eaab87 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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",
diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index 63748c27b..96caad53c 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -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
diff --git a/core/config/schema/core.data_types.schema.yml b/core/config/schema/core.data_types.schema.yml
index 4ab0fd769..c6aca3615 100644
--- a/core/config/schema/core.data_types.schema.yml
+++ b/core/config/schema/core.data_types.schema.yml
@@ -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:
diff --git a/core/core.api.php b/core/core.api.php
index ee32e87b0..062092282 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -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
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 86e12c75d..3724fc718 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -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);
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 5d62aad4e..5cd3ce883 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -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
diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index 70e8019a8..e0711030d 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -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.
diff --git a/core/includes/form.inc b/core/includes/form.inc
index c1c82c16b..daf75cd24 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -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
diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php
index 3ebda8f1e..0233135cc 100644
--- a/core/lib/Drupal.php
+++ b/core/lib/Drupal.php
@@ -81,7 +81,7 @@ class Drupal {
/**
* The current system version.
*/
- const VERSION = '8.0.5';
+ const VERSION = '8.0.6';
/**
* Core API compatibility.
diff --git a/core/lib/Drupal/Component/Utility/NestedArray.php b/core/lib/Drupal/Component/Utility/NestedArray.php
index 74e631cdd..85b4befa4 100644
--- a/core/lib/Drupal/Component/Utility/NestedArray.php
+++ b/core/lib/Drupal/Component/Utility/NestedArray.php
@@ -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;
+ }
+
}
diff --git a/core/lib/Drupal/Component/Uuid/Php.php b/core/lib/Drupal/Component/Uuid/Php.php
index 08c526149..c56f6b448 100644
--- a/core/lib/Drupal/Component/Uuid/Php.php
+++ b/core/lib/Drupal/Component/Uuid/Php.php
@@ -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;
}
diff --git a/core/lib/Drupal/Core/Composer/Composer.php b/core/lib/Drupal/Core/Composer/Composer.php
index 0d5b6aacd..4f01b5484 100644
--- a/core/lib/Drupal/Core/Composer/Composer.php
+++ b/core/lib/Drupal/Core/Composer/Composer.php
@@ -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()));
- }
}
}
}
diff --git a/core/lib/Drupal/Core/Config/ConfigInstaller.php b/core/lib/Drupal/Core/Config/ConfigInstaller.php
index a77bb1c2f..f3f8f621f 100644
--- a/core/lib/Drupal/Core/Config/ConfigInstaller.php
+++ b/core/lib/Drupal/Core/Config/ConfigInstaller.php
@@ -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)) {
diff --git a/core/lib/Drupal/Core/Entity/EntityHandlerBase.php b/core/lib/Drupal/Core/Entity/EntityHandlerBase.php
index c4ec4a355..ef9eb34ef 100644
--- a/core/lib/Drupal/Core/Entity/EntityHandlerBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityHandlerBase.php
@@ -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;
diff --git a/core/lib/Drupal/Core/Entity/EntityHandlerInterface.php b/core/lib/Drupal/Core/Entity/EntityHandlerInterface.php
index f6b862a4b..f7811881d 100644
--- a/core/lib/Drupal/Core/Entity/EntityHandlerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityHandlerInterface.php
@@ -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 {
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 251a87c7b..064ef01be 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -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" = {
diff --git a/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php b/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
index 1cbac3c27..172a77b45 100644
--- a/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
+++ b/core/lib/Drupal/Core/Form/ConfigFormBaseTrait.php
@@ -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 */
diff --git a/core/lib/Drupal/Core/Form/form.api.php b/core/lib/Drupal/Core/Form/form.api.php
index 19c2543c4..41d1441b7 100644
--- a/core/lib/Drupal/Core/Form/form.api.php
+++ b/core/lib/Drupal/Core/Form/form.api.php
@@ -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');
diff --git a/core/lib/Drupal/Core/Language/language.api.php b/core/lib/Drupal/Core/Language/language.api.php
index eb56b59d1..1ac4b0835 100644
--- a/core/lib/Drupal/Core/Language/language.api.php
+++ b/core/lib/Drupal/Core/Language/language.api.php
@@ -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()
* @}
*/
diff --git a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
index 5ca097128..575e660b1 100644
--- a/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
+++ b/core/lib/Drupal/Core/PhpStorage/PhpStorageFactory.php
@@ -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;
}
diff --git a/core/lib/Drupal/Core/Render/Element/HtmlTag.php b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
index 5ec0d3f77..7b9b614cd 100644
--- a/core/lib/Drupal/Core/Render/Element/HtmlTag.php
+++ b/core/lib/Drupal/Core/Render/Element/HtmlTag.php
@@ -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'),
* ];
diff --git a/core/lib/Drupal/Core/Render/Element/Tableselect.php b/core/lib/Drupal/Core/Render/Element/Tableselect.php
index 0d1f969e9..9733ca925 100644
--- a/core/lib/Drupal/Core/Render/Element/Tableselect.php
+++ b/core/lib/Drupal/Core/Render/Element/Tableselect.php
@@ -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 {
diff --git a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
index 447fc06d3..085220903 100644
--- a/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
+++ b/core/lib/Drupal/Core/Render/Element/VerticalTabs.php
@@ -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.
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index d9c0fc9a2..19a7038d7 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -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.
*
diff --git a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
index 583ab1805..9e46b277f 100644
--- a/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
+++ b/core/lib/Drupal/Core/StringTranslation/PluralTranslatableMarkup.php
@@ -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;
diff --git a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
index e67bf2bc9..2f5ae9e80 100644
--- a/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
+++ b/core/lib/Drupal/Core/StringTranslation/StringTranslationTrait.php
@@ -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.
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
index cbee36f72..2bfb827ae 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
@@ -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.
diff --git a/core/lib/Drupal/Core/Test/AssertMailTrait.php b/core/lib/Drupal/Core/Test/AssertMailTrait.php
new file mode 100644
index 000000000..f6b5279ca
--- /dev/null
+++ b/core/lib/Drupal/Core/Test/AssertMailTrait.php
@@ -0,0 +1,161 @@
+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:
' . print_r($mail, TRUE) . '
');
+ }
+ }
+
+}
diff --git a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
index 148818971..23bd17470 100644
--- a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
+++ b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
@@ -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;
+ });
};
};
}
diff --git a/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php b/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
index 673893e70..94bd779ab 100644
--- a/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
+++ b/core/lib/Drupal/Core/Theme/ThemeNegotiatorInterface.php
@@ -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);
diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
index 56be8749b..1e4362176 100644
--- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
+++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
@@ -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.
*
diff --git a/core/lib/Drupal/Core/TypedData/ListInterface.php b/core/lib/Drupal/Core/TypedData/ListInterface.php
index 5639ba8ce..39222f37e 100644
--- a/core/lib/Drupal/Core/TypedData/ListInterface.php
+++ b/core/lib/Drupal/Core/TypedData/ListInterface.php
@@ -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.
*
diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js
index afcc84b67..4bef2849b 100644
--- a/core/misc/autocomplete.js
+++ b/core/misc/autocomplete.js
@@ -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) {
diff --git a/core/modules/aggregator/src/FeedForm.php b/core/modules/aggregator/src/FeedForm.php
index aa5069c97..cab27d29f 100644
--- a/core/modules/aggregator/src/FeedForm.php
+++ b/core/modules/aggregator/src/FeedForm.php
@@ -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 {
diff --git a/core/modules/aggregator/src/FeedViewBuilder.php b/core/modules/aggregator/src/FeedViewBuilder.php
index 11b78ab4d..e88090e24 100644
--- a/core/modules/aggregator/src/FeedViewBuilder.php
+++ b/core/modules/aggregator/src/FeedViewBuilder.php
@@ -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 {
diff --git a/core/modules/aggregator/src/ItemViewBuilder.php b/core/modules/aggregator/src/ItemViewBuilder.php
index 1f0c23a38..0e386e9ef 100644
--- a/core/modules/aggregator/src/ItemViewBuilder.php
+++ b/core/modules/aggregator/src/ItemViewBuilder.php
@@ -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 {
diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js
index df9327bbc..5eb231b52 100644
--- a/core/modules/block/js/block.js
+++ b/core/modules/block/js/block.js
@@ -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.
diff --git a/core/modules/block/migration_templates/d6_block.yml b/core/modules/block/migration_templates/d6_block.yml
index 6ab2a4cc3..8be753974 100644
--- a/core/modules/block/migration_templates/d6_block.yml
+++ b/core/modules/block/migration_templates/d6_block.yml
@@ -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:
diff --git a/core/modules/block/migration_templates/d7_block.yml b/core/modules/block/migration_templates/d7_block.yml
index 628bf7158..32e8f5d2a 100755
--- a/core/modules/block/migration_templates/d7_block.yml
+++ b/core/modules/block/migration_templates/d7_block.yml
@@ -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:
diff --git a/core/modules/block/src/Plugin/migrate/process/BlockSettings.php b/core/modules/block/src/Plugin/migrate/process/BlockSettings.php
index ac36054da..818ef490f 100644
--- a/core/modules/block/src/Plugin/migrate/process/BlockSettings.php
+++ b/core/modules/block/src/Plugin/migrate/process/BlockSettings.php
@@ -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);
diff --git a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
index b5d4fa519..874723f6a 100644
--- a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
+++ b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
@@ -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'] = "\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'] = '';
- $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');
diff --git a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php
index cecf4680d..f14f3fcf6 100644
--- a/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php
+++ b/core/modules/block/src/Tests/Migrate/d7/MigrateBlockTest.php
@@ -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.
diff --git a/core/modules/block_content/src/BlockContentForm.php b/core/modules/block_content/src/BlockContentForm.php
index 088c4d7bf..ffe2f992d 100644
--- a/core/modules/block_content/src/BlockContentForm.php
+++ b/core/modules/block_content/src/BlockContentForm.php
@@ -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 {
diff --git a/core/modules/block_content/src/BlockContentViewBuilder.php b/core/modules/block_content/src/BlockContentViewBuilder.php
index 87b7aed17..44380c54e 100644
--- a/core/modules/block_content/src/BlockContentViewBuilder.php
+++ b/core/modules/block_content/src/BlockContentViewBuilder.php
@@ -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 {
diff --git a/core/modules/book/src/BookManager.php b/core/modules/book/src/BookManager.php
index 31128f744..3b25e3157 100644
--- a/core/modules/book/src/BookManager.php
+++ b/core/modules/book/src/BookManager.php
@@ -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();
diff --git a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
index 73876ec5f..50aad67e0 100644
--- a/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
+++ b/core/modules/ckeditor/js/plugins/drupalimage/plugin.js
@@ -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) {
diff --git a/core/modules/ckeditor/src/CKEditorPluginConfigurableInterface.php b/core/modules/ckeditor/src/CKEditorPluginConfigurableInterface.php
index a5084fdf5..0ced0c17e 100644
--- a/core/modules/ckeditor/src/CKEditorPluginConfigurableInterface.php
+++ b/core/modules/ckeditor/src/CKEditorPluginConfigurableInterface.php
@@ -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);
diff --git a/core/modules/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php
index 51a6cce5b..1a264413f 100644
--- a/core/modules/comment/src/CommentForm.php
+++ b/core/modules/comment/src/CommentForm.php
@@ -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 {
diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php
index 099e5f41c..5ed080fce 100644
--- a/core/modules/comment/src/CommentStorage.php
+++ b/core/modules/comment/src/CommentStorage.php
@@ -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.
diff --git a/core/modules/comment/src/CommentTypeForm.php b/core/modules/comment/src/CommentTypeForm.php
index 12d65fb15..79e513ea8 100644
--- a/core/modules/comment/src/CommentTypeForm.php
+++ b/core/modules/comment/src/CommentTypeForm.php
@@ -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 {
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index 22914d310..9b47b7c7b 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -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 {
diff --git a/core/modules/config/src/Tests/ConfigOtherModuleTest.php b/core/modules/config/src/Tests/ConfigOtherModuleTest.php
index 085ea312f..dfc1d1f7c 100644
--- a/core/modules/config/src/Tests/ConfigOtherModuleTest.php
+++ b/core/modules/config/src/Tests/ConfigOtherModuleTest.php
@@ -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.');
}
/**
diff --git a/core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test_optional_entity_unmet.yml b/core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test_optional_entity_unmet.yml
new file mode 100644
index 000000000..2c5c48585
--- /dev/null
+++ b/core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test_optional_entity_unmet.yml
@@ -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
diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
index a718808f0..cb7e0d1ee 100644
--- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
@@ -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 HTML & things)';
+ $field->setSetting('on_label', $on_label);
+ $off_label = 'Off label (with HTML & 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.
*/
diff --git a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
index 995de59d0..00cdfe22d 100644
--- a/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
+++ b/core/modules/content_translation/src/Plugin/Derivative/ContentTranslationLocalTasks.php
@@ -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;
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationOperationsTest.php b/core/modules/content_translation/src/Tests/ContentTranslationOperationsTest.php
index f987494f8..522344bff 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationOperationsTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationOperationsTest.php
@@ -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() {
diff --git a/core/modules/content_translation/tests/src/Unit/Menu/ContentTranslationLocalTasksTest.php b/core/modules/content_translation/tests/src/Unit/Menu/ContentTranslationLocalTasksTest.php
index f11f78385..0b7fe8502 100644
--- a/core/modules/content_translation/tests/src/Unit/Menu/ContentTranslationLocalTasksTest.php
+++ b/core/modules/content_translation/tests/src/Unit/Menu/ContentTranslationLocalTasksTest.php
@@ -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());
}
/**
diff --git a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
index 159596319..080cf1ee3 100644
--- a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+++ b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
@@ -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();
diff --git a/core/modules/field_ui/src/EntityFormModeListBuilder.php b/core/modules/field_ui/src/EntityFormModeListBuilder.php
index f6fc24b47..741b23efb 100644
--- a/core/modules/field_ui/src/EntityFormModeListBuilder.php
+++ b/core/modules/field_ui/src/EntityFormModeListBuilder.php
@@ -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.
diff --git a/core/modules/language/src/ConfigurableLanguageManager.php b/core/modules/language/src/ConfigurableLanguageManager.php
index 6b72a4f53..59d6d5424 100644
--- a/core/modules/language/src/ConfigurableLanguageManager.php
+++ b/core/modules/language/src/ConfigurableLanguageManager.php
@@ -472,7 +472,7 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl
}
$predefined[$key] = new TranslatableMarkup($value[0]);
}
- asort($predefined);
+ natcasesort($predefined);
return $predefined;
}
diff --git a/core/modules/language/src/Entity/ContentLanguageSettings.php b/core/modules/language/src/Entity/ContentLanguageSettings.php
index 750101201..49ebc6392 100644
--- a/core/modules/language/src/Entity/ContentLanguageSettings.php
+++ b/core/modules/language/src/Entity/ContentLanguageSettings.php
@@ -24,6 +24,7 @@ use Drupal\language\ContentLanguageSettingsInterface;
* entity_keys = {
* "id" = "id"
* },
+ * list_cache_tags = { "rendered" }
* )
*/
class ContentLanguageSettings extends ConfigEntityBase implements ContentLanguageSettingsInterface {
diff --git a/core/modules/language/src/LanguageListBuilder.php b/core/modules/language/src/LanguageListBuilder.php
index fe4e7a12f..e6b279d36 100644
--- a/core/modules/language/src/LanguageListBuilder.php
+++ b/core/modules/language/src/LanguageListBuilder.php
@@ -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
diff --git a/core/modules/language/src/Tests/LanguageLocaleListTest.php b/core/modules/language/src/Tests/LanguageLocaleListTest.php
new file mode 100644
index 000000000..ca1317218
--- /dev/null
+++ b/core/modules/language/src/Tests/LanguageLocaleListTest.php
@@ -0,0 +1,79 @@
+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.');
+ }
+}
diff --git a/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php b/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php
index d70567122..bdd9822ce 100644
--- a/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php
+++ b/core/modules/link/src/Plugin/migrate/process/d6/CckLink.php
@@ -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;
diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc
index a5ef4231e..81ca96667 100644
--- a/core/modules/locale/locale.batch.inc
+++ b/core/modules/locale/locale.batch.inc
@@ -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,
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index de43c8590..fcf98c3ab 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -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;
}
diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
index 54c9a2011..736c02085 100644
--- a/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
+++ b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
@@ -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).'),
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/Config.php b/core/modules/migrate/src/Plugin/migrate/destination/Config.php
index 0dd9fd377..b3c07c971 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/Config.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/Config.php
@@ -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);
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Get.php b/core/modules/migrate/src/Plugin/migrate/process/Get.php
index 5b720bc25..fab2679af 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Get.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Get.php
@@ -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];
diff --git a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
index fc63bb850..d644dc357 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/SourcePluginBase.php
@@ -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.
diff --git a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
index dcef9b3e3..93569f445 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
@@ -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]) {
diff --git a/core/modules/migrate/tests/src/Unit/SqlBaseTest.php b/core/modules/migrate/tests/src/Unit/SqlBaseTest.php
index 829573cfd..936d00f24 100644
--- a/core/modules/migrate/tests/src/Unit/SqlBaseTest.php
+++ b/core/modules/migrate/tests/src/Unit/SqlBaseTest.php
@@ -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'],
],
];
}
diff --git a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
index 363e4f16e..f5e1c3318 100644
--- a/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
+++ b/core/modules/migrate/tests/src/Unit/destination/ConfigTest.php
@@ -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']);
}
diff --git a/core/modules/migrate/tests/src/Unit/process/GetTest.php b/core/modules/migrate/tests/src/Unit/process/GetTest.php
index dc8ea70a8..bdbb551e5 100644
--- a/core/modules/migrate/tests/src/Unit/process/GetTest.php
+++ b/core/modules/migrate/tests/src/Unit/process/GetTest.php
@@ -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;
diff --git a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
index 6152f9e45..96f5e651f 100644
--- a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
+++ b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
@@ -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'
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php
new file mode 100644
index 000000000..ca5184063
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php
@@ -0,0 +1,105 @@
+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;
+ }
+
+}
diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal6.php b/core/modules/migrate_drupal/tests/fixtures/drupal6.php
index f7d64f30b..8f883003a 100644
--- a/core/modules/migrate_drupal/tests/fixtures/drupal6.php
+++ b/core/modules/migrate_drupal/tests/fixtures/drupal6.php
@@ -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',
diff --git a/core/modules/migrate_drupal/tests/src/Unit/source/d6/i18nVariableTest.php b/core/modules/migrate_drupal/tests/src/Unit/source/d6/i18nVariableTest.php
new file mode 100644
index 000000000..b9bcb99c7
--- /dev/null
+++ b/core/modules/migrate_drupal/tests/src/Unit/source/d6/i18nVariableTest.php
@@ -0,0 +1,66 @@
+ '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";'),
+ ],
+ ];
+
+}
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index 55665662b..49a94d812 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -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);
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 2a7584470..50faa07d9 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -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.'));
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index bb6c49d72..e273fc339 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -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 {
diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
index eb87a4dd0..58f854043 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
@@ -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.
*
diff --git a/core/modules/node/src/NodeStorage.php b/core/modules/node/src/NodeStorage.php
index 830fc3265..da8e4a3b3 100644
--- a/core/modules/node/src/NodeStorage.php
+++ b/core/modules/node/src/NodeStorage.php
@@ -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.
diff --git a/core/modules/node/src/NodeTypeForm.php b/core/modules/node/src/NodeTypeForm.php
index e4f8cf570..8c85e47b2 100644
--- a/core/modules/node/src/NodeTypeForm.php
+++ b/core/modules/node/src/NodeTypeForm.php
@@ -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 {
diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
index 1ee05f6af..db3a7b489 100644
--- a/core/modules/node/src/NodeViewBuilder.php
+++ b/core/modules/node/src/NodeViewBuilder.php
@@ -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 {
diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php
index 0fa464ec0..d00cd6a48 100644
--- a/core/modules/node/src/Plugin/Search/NodeSearch.php
+++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
@@ -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;
}
diff --git a/core/modules/node/src/Plugin/views/argument/Type.php b/core/modules/node/src/Plugin/views/argument/Type.php
index 62ab17dbb..3ef433905 100644
--- a/core/modules/node/src/Plugin/views/argument/Type.php
+++ b/core/modules/node/src/Plugin/views/argument/Type.php
@@ -19,7 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
class Type extends StringArgument {
/**
- * NodeType storage controller.
+ * NodeType storage handler.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php
index b25c2700a..cf0c44bea 100644
--- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php
+++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeTest.php
@@ -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);
diff --git a/core/modules/node/src/Tests/NodeQueryAlterTest.php b/core/modules/node/src/Tests/NodeQueryAlterTest.php
index d0d615ee7..d8e06401c 100644
--- a/core/modules/node/src/Tests/NodeQueryAlterTest.php
+++ b/core/modules/node/src/Tests/NodeQueryAlterTest.php
@@ -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.
*
diff --git a/core/modules/node/src/Tests/NodeTypeTranslationTest.php b/core/modules/node/src/Tests/NodeTypeTranslationTest.php
index 10c686584..487a5e5cf 100644
--- a/core/modules/node/src/Tests/NodeTypeTranslationTest.php
+++ b/core/modules/node/src/Tests/NodeTypeTranslationTest.php
@@ -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');
}
}
diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index a483044fb..237b85d3d 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -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 {
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index db7d1236e..185a7e834 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -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,
diff --git a/core/modules/search/src/Tests/SearchDateIntervalTest.php b/core/modules/search/src/Tests/SearchDateIntervalTest.php
new file mode 100644
index 000000000..ec053942d
--- /dev/null
+++ b/core/modules/search/src/Tests/SearchDateIntervalTest.php
@@ -0,0 +1,79 @@
+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');
+ }
+}
diff --git a/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml
new file mode 100644
index 000000000..5b573478b
--- /dev/null
+++ b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml
@@ -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
diff --git a/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.module b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.module
new file mode 100644
index 000000000..e595796e1
--- /dev/null
+++ b/core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.module
@@ -0,0 +1,18 @@
+condition('n.created', 1458345600, '>=');
+ // End date Sun, 20 Mar 2016 00:00:00 GMT.
+ $query->condition('n.created', 1458432000, '<');
+}
diff --git a/core/modules/shortcut/src/ShortcutForm.php b/core/modules/shortcut/src/ShortcutForm.php
index c0f8714e3..f2f16e91a 100644
--- a/core/modules/shortcut/src/ShortcutForm.php
+++ b/core/modules/shortcut/src/ShortcutForm.php
@@ -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 {
diff --git a/core/modules/shortcut/src/ShortcutSetForm.php b/core/modules/shortcut/src/ShortcutSetForm.php
index fd21f7c77..7bb6ced92 100644
--- a/core/modules/shortcut/src/ShortcutSetForm.php
+++ b/core/modules/shortcut/src/ShortcutSetForm.php
@@ -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 {
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index 74d348e92..f5b85a50e 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -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);
}
/**
diff --git a/core/modules/simpletest/simpletest.services.yml b/core/modules/simpletest/simpletest.services.yml
index 56d48ca9a..8b645deb2 100644
--- a/core/modules/simpletest/simpletest.services.yml
+++ b/core/modules/simpletest/simpletest.services.yml
@@ -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']
diff --git a/core/modules/simpletest/src/AssertHelperTrait.php b/core/modules/simpletest/src/AssertHelperTrait.php
index d4770817f..dc68e23c4 100644
--- a/core/modules/simpletest/src/AssertHelperTrait.php
+++ b/core/modules/simpletest/src/AssertHelperTrait.php
@@ -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;
}
diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
index d93ed8b78..e6025bf2c 100644
--- a/core/modules/simpletest/src/BrowserTestBase.php
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -229,11 +229,17 @@ abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
*/
protected function initMink() {
$driver = $this->getDefaultDriverInstance();
+
+ if ($driver instanceof GoutteDriver) {
+ $driver->getClient()->setClient(\Drupal::httpClient());
+ }
+
$session = new Session($driver);
$this->mink = new Mink();
$this->mink->registerSession('default', $session);
$this->mink->setDefaultSessionName('default');
$this->registerSessions();
+
return $session;
}
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 10463d9ad..3460e738e 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -271,6 +271,12 @@ EOD;
// The temporary stream wrapper is able to operate both with and without
// configuration.
$this->registerStreamWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream');
+
+ // Manually configure the test mail collector implementation to prevent
+ // tests from sending out emails and collect them in state instead.
+ // While this should be enforced via settings.php prior to installation,
+ // some tests expect to be able to test mail system implementations.
+ $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
}
/**
diff --git a/core/modules/simpletest/src/TestDiscovery.php b/core/modules/simpletest/src/TestDiscovery.php
index d2c49d7b1..3538934fa 100644
--- a/core/modules/simpletest/src/TestDiscovery.php
+++ b/core/modules/simpletest/src/TestDiscovery.php
@@ -10,9 +10,11 @@ namespace Drupal\simpletest;
use Doctrine\Common\Annotations\SimpleAnnotationReader;
use Doctrine\Common\Reflection\StaticReflectionParser;
use Drupal\Component\Annotation\Reflection\MockFileFinder;
+use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\simpletest\Exception\MissingGroupException;
use PHPUnit_Util_Test;
@@ -49,18 +51,38 @@ class TestDiscovery {
*/
protected $availableExtensions;
+ /**
+ * The app root.
+ *
+ * @var string
+ */
+ protected $root;
+
+ /**
+ * The module handler.
+ *
+ * @var \Drupal\Core\Extension\ModuleHandlerInterface
+ */
+ protected $moduleHandler;
+
/**
* Constructs a new test discovery.
*
+ * @param string $root
+ * The app root.
* @param $class_loader
* The class loader. Normally Composer's ClassLoader, as included by the
* front controller, but may also be decorated; e.g.,
* \Symfony\Component\ClassLoader\ApcClassLoader.
+ * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+ * The module handler.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* (optional) Backend for caching discovery results.
*/
- public function __construct($class_loader, CacheBackendInterface $cache_backend = NULL) {
+ public function __construct($root, $class_loader, ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend = NULL) {
+ $this->root = $root;
$this->classLoader = $class_loader;
+ $this->moduleHandler = $module_handler;
$this->cacheBackend = $cache_backend;
}
@@ -80,15 +102,16 @@ class TestDiscovery {
$existing = $this->classLoader->getPrefixesPsr4();
// Add PHPUnit test namespaces of Drupal core.
- $this->testNamespaces['Drupal\\Tests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/Tests'];
- $this->testNamespaces['Drupal\\KernelTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/KernelTests'];
- $this->testNamespaces['Drupal\\FunctionalTests\\'] = [DRUPAL_ROOT . '/core/tests/Drupal/FunctionalTests'];
+ $this->testNamespaces['Drupal\\Tests\\'] = [$this->root . '/core/tests/Drupal/Tests'];
+ $this->testNamespaces['Drupal\\KernelTests\\'] = [$this->root . '/core/tests/Drupal/KernelTests'];
+ $this->testNamespaces['Drupal\\FunctionalTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalTests'];
+ $this->testNamespaces['Drupal\\FunctionalJavascriptTests\\'] = [$this->root . '/core/tests/Drupal/FunctionalJavascriptTests'];
$this->availableExtensions = array();
foreach ($this->getExtensions() as $name => $extension) {
$this->availableExtensions[$extension->getType()][$name] = $name;
- $base_path = DRUPAL_ROOT . '/' . $extension->getPath();
+ $base_path = $this->root . '/' . $extension->getPath();
// Add namespace of disabled/uninstalled extensions.
if (!isset($existing["Drupal\\$name\\"])) {
@@ -115,11 +138,12 @@ class TestDiscovery {
*
* @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 by the first @group specified in each test's
- * PHPDoc comment block, and then keyed by class names. For example:
- * @code
+ * An array of tests keyed by the the group name.
+ * @code
* $groups['block'] => array(
* 'Drupal\block\Tests\BlockTest' => array(
* 'name' => 'Drupal\block\Tests\BlockTest',
@@ -127,15 +151,12 @@ class TestDiscovery {
* 'group' => 'block',
* ),
* );
- * @endcode
- *
- * @throws \ReflectionException
- * If a discovered test class does not match the expected class name.
+ * @endcode
*
* @todo Remove singular grouping; retain list of groups in 'group' key.
* @see https://www.drupal.org/node/2296615
*/
- public function getTestClasses($extension = NULL) {
+ public function getTestClasses($extension = NULL, array $types = []) {
$reader = new SimpleAnnotationReader();
$reader->addNamespace('Drupal\\simpletest\\Annotation');
@@ -190,13 +211,20 @@ class TestDiscovery {
}
// Allow modules extending core tests to disable originals.
- \Drupal::moduleHandler()->alter('simpletest', $list);
+ $this->moduleHandler->alter('simpletest', $list);
if (!isset($extension)) {
if ($this->cacheBackend) {
$this->cacheBackend->set('simpletest:discovery:classes', $list);
}
}
+
+ if ($types) {
+ $list = NestedArray::filter($list, function ($element) use ($types) {
+ return !(is_array($element) && isset($element['type']) && !in_array($element['type'], $types));
+ });
+ }
+
return $list;
}
@@ -450,7 +478,7 @@ class TestDiscovery {
* An array of Extension objects, keyed by extension name.
*/
protected function getExtensions() {
- $listing = new ExtensionDiscovery(DRUPAL_ROOT);
+ $listing = new ExtensionDiscovery($this->root);
// Ensure that tests in all profiles are discovered.
$listing->setProfileDirectories(array());
$extensions = $listing->scan('module', TRUE);
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 667f705df..a3bfd4c06 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -28,6 +28,7 @@ use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\Session\UserSession;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\Test\AssertMailTrait;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
@@ -47,6 +48,9 @@ abstract class WebTestBase extends TestBase {
use ContentTypeCreationTrait {
createContentType as drupalCreateContentType;
}
+ use AssertMailTrait {
+ getMails as drupalGetMails;
+ }
use NodeCreationTrait {
getNodeByTitle as drupalGetNodeByTitle;
createNode as drupalCreateNode;
@@ -2518,32 +2522,6 @@ abstract class WebTestBase extends TestBase {
return $this->assertTrue($header_value == $value, $message ? $message : 'HTTP response header ' . $header . ' with value ' . $value . ' found, actual value: ' . $header_value, $group);
}
- /**
- * Gets an array containing all emails sent during this test case.
- *
- * @param $filter
- * An array containing key/value pairs used to filter the emails that are
- * returned.
- *
- * @return
- * An array containing email messages captured during the current test.
- */
- protected function drupalGetMails($filter = array()) {
- $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
- $filtered_emails = array();
-
- 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;
- }
-
/**
* Passes if the internal browser's URL matches the given path.
*
@@ -2644,126 +2622,6 @@ abstract class WebTestBase extends TestBase {
return $this->assertFalse($match, $message ? $message : SafeMarkup::format('HTTP response not expected @code, actual @curl_code', array('@code' => $code, '@curl_code' => $curl_code)), $group);
}
- /**
- * Asserts that the most recently sent email message has the given value.
- *
- * The field in $name must have the content described in $value.
- *
- * @param $name
- * Name of field or message property to assert. Examples: subject, body,
- * id, ...
- * @param $value
- * Value of the field to assert.
- * @param $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 $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
- * TRUE on pass, FALSE on fail.
- */
- protected function assertMail($name, $value = '', $message = '', $group = 'Email') {
- $captured_emails = \Drupal::state()->get('system.test_mail_collector') ?: array();
- $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 $field_name
- * Name of field or message property to assert: subject, body, id, ...
- * @param $string
- * String to search for.
- * @param $email_depth
- * Number of emails to search for string, starting with most recent.
- * @param $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 $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
- * TRUE on pass, FALSE on fail.
- */
- protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') {
- $mails = $this->drupalGetMails();
- $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".', array('@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 $field_name
- * Name of field or message property to assert: subject, body, id, ...
- * @param $regex
- * Pattern to search for.
- * @param $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 $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
- * TRUE on pass, FALSE on fail.
- */
- protected function assertMailPattern($field_name, $regex, $message = '', $group = 'Other') {
- $mails = $this->drupalGetMails();
- $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".', array('@field' => $field_name, '@expected' => $regex));
- }
- return $this->assertTrue($regex_found, $message, $group);
- }
-
- /**
- * Outputs to verbose the most recent $count emails sent.
- *
- * @param $count
- * Optional number of emails to output.
- */
- protected function verboseEmail($count = 1) {
- $mails = $this->drupalGetMails();
- for ($i = count($mails) -1; $i >= count($mails) - $count && $i >= 0; $i--) {
- $mail = $mails[$i];
- $this->verbose('Email:' . print_r($mail, TRUE) . '
');
- }
- }
-
/**
* Creates a mock request and sets it on the generator.
*
diff --git a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
index c082d0372..d660b1f02 100644
--- a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
+++ b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
@@ -58,4 +58,9 @@ class BrowserTestBaseTest extends BrowserTestBase {
$this->assertSame('green', $value);
}
+ public function testError() {
+ $this->setExpectedException('\Exception', 'User notice: foo');
+ $this->drupalGet('test-error');
+ }
+
}
diff --git a/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
index 509b9ab86..fda12f1a6 100644
--- a/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
+++ b/core/modules/simpletest/tests/src/Unit/TestInfoParsingTest.php
@@ -7,8 +7,12 @@
namespace Drupal\Tests\simpletest\Unit;
+use Composer\Autoload\ClassLoader;
+use Drupal\Core\Extension\Extension;
+use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\simpletest\TestDiscovery;
use Drupal\Tests\UnitTestCase;
+use org\bovigo\vfs\vfsStream;
/**
* @coversDefaultClass \Drupal\simpletest\TestDiscovery
@@ -256,6 +260,130 @@ EOT;
$this->assertEmpty($info['description']);
}
+ protected function setupVfsWithTestClasses() {
+ vfsStream::setup('drupal');
+
+ $test_file = << [
+ 'test_module' => [
+ 'tests' => [
+ 'src' => [
+ 'Functional' => [
+ 'FunctionalExampleTest.php' => $test_file,
+ 'FunctionalExampleTest2.php' => str_replace(['FunctionalExampleTest', '@group example'], ['FunctionalExampleTest2', '@group example2'], $test_file),
+ ],
+ 'Kernel' => [
+ 'KernelExampleTest3.php' => str_replace(['FunctionalExampleTest', '@group example'], ['KernelExampleTest3', '@group example2'], $test_file),
+ ],
+ ],
+ ],
+ ],
+ ],
+ ]);
+ }
+
+ /**
+ * @covers ::getTestClasses
+ */
+ public function testGetTestClasses() {
+ $this->setupVfsWithTestClasses();
+ $class_loader = $this->prophesize(ClassLoader::class);
+ $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+
+ $test_discovery = new TestTestDiscovery('vfs://drupal', $class_loader->reveal(), $module_handler->reveal());
+
+ $extensions = [
+ 'test_module' => new Extension('vfs://drupal', 'module', 'modules/test_module/test_module.info.yml'),
+ ];
+ $test_discovery->setExtensions($extensions);
+ $result = $test_discovery->getTestClasses();
+ $this->assertCount(2, $result);
+ $this->assertEquals([
+ 'example' => [
+ 'Drupal\Tests\test_module\Functional\FunctionalExampleTest' => [
+ 'name' => 'Drupal\Tests\test_module\Functional\FunctionalExampleTest',
+ 'description' => 'Test description',
+ 'group' => 'example',
+ 'type' => 'PHPUnit-Functional',
+ ],
+ ],
+ 'example2' => [
+ 'Drupal\Tests\test_module\Functional\FunctionalExampleTest2' => [
+ 'name' => 'Drupal\Tests\test_module\Functional\FunctionalExampleTest2',
+ 'description' => 'Test description',
+ 'group' => 'example2',
+ 'type' => 'PHPUnit-Functional',
+ ],
+ 'Drupal\Tests\test_module\Kernel\KernelExampleTest3' => [
+ 'name' => 'Drupal\Tests\test_module\Kernel\KernelExampleTest3',
+ 'description' => 'Test description',
+ 'group' => 'example2',
+ 'type' => 'PHPUnit-Kernel',
+ ],
+ ],
+ ], $result);
+ }
+
+ /**
+ * @covers ::getTestClasses
+ */
+ public function testGetTestClassesWithSelectedTypes() {
+ $this->setupVfsWithTestClasses();
+ $class_loader = $this->prophesize(ClassLoader::class);
+ $module_handler = $this->prophesize(ModuleHandlerInterface::class);
+
+ $test_discovery = new TestTestDiscovery('vfs://drupal', $class_loader->reveal(), $module_handler->reveal());
+
+ $extensions = [
+ 'test_module' => new Extension('vfs://drupal', 'module', 'modules/test_module/test_module.info.yml'),
+ ];
+ $test_discovery->setExtensions($extensions);
+ $result = $test_discovery->getTestClasses(NULL, ['PHPUnit-Kernel']);
+ $this->assertCount(2, $result);
+ $this->assertEquals([
+ 'example' => [
+ ],
+ 'example2' => [
+ 'Drupal\Tests\test_module\Kernel\KernelExampleTest3' => [
+ 'name' => 'Drupal\Tests\test_module\Kernel\KernelExampleTest3',
+ 'description' => 'Test description',
+ 'group' => 'example2',
+ 'type' => 'PHPUnit-Kernel',
+ ],
+ ],
+ ], $result);
+ }
+
+}
+
+class TestTestDiscovery extends TestDiscovery {
+
+ /**
+ * @var \Drupal\Core\Extension\Extension[]
+ */
+ protected $extensions = [];
+
+ public function setExtensions(array $extensions) {
+ $this->extensions = $extensions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getExtensions() {
+ return $this->extensions;
+ }
+
/**
* @covers ::getPhpunitTestSuite
* @dataProvider providerTestGetPhpunitTestSuite
diff --git a/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php b/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php
index b78b45df9..a44fb4d44 100644
--- a/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php
+++ b/core/modules/system/src/EventSubscriber/AdminRouteSubscriber.php
@@ -33,10 +33,9 @@ class AdminRouteSubscriber extends RouteSubscriberBase {
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
- // Use a higher priority than \Drupal\field_ui\Routing\RouteSubscriber or
- // \Drupal\views\EventSubscriber\RouteSubscriber to ensure we add the
- // option to their routes.
- // @todo https://www.drupal.org/node/2158571
+ // Use a lower priority than \Drupal\field_ui\Routing\RouteSubscriber or
+ // \Drupal\views\EventSubscriber\RouteSubscriber to ensure we add the option
+ // to their routes.
$events[RoutingEvents::ALTER] = array('onAlterRoutes', -200);
return $events;
diff --git a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
index 8940f0fca..c691ad6fa 100644
--- a/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
+++ b/core/modules/system/src/Tests/Entity/EntityUnitTestBase.php
@@ -12,6 +12,9 @@ use Drupal\Core\Entity\EntityInterface;
/**
* Defines an abstract test base for entity unit tests.
+ *
+ * @deprecated in Drupal 8.1.x, will be removed before Drupal 8.2.x. Use
+ * \Drupal\KernelTests\Core\Entity\EntityKernelTestBase instead.
*/
abstract class EntityUnitTestBase extends KernelTestBase {
diff --git a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
index f9ce925b5..b400f3de3 100644
--- a/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
+++ b/core/modules/system/src/Tests/Form/ElementsTableSelectTest.php
@@ -42,6 +42,27 @@ class ElementsTableSelectTest extends WebTestBase {
}
}
+ /**
+ * Test the presence of ajax functionality for all options.
+ */
+ function testAjax() {
+ $rows = array('row1', 'row2', 'row3');
+ // Test checkboxes (#multiple == TRUE).
+ foreach ($rows as $row) {
+ $element = 'tableselect[' . $row . ']';
+ $edit = array($element => TRUE);
+ $result = $this->drupalPostAjaxForm('form_test/tableselect/multiple-true', $edit, $element);
+ $this->assertFalse(empty($result), t('Ajax triggers on checkbox for @row.', array('@row' => $row)));
+ }
+ // Test radios (#multiple == FALSE).
+ $element = 'tableselect';
+ foreach ($rows as $row) {
+ $edit = array($element => $row);
+ $result = $this->drupalPostAjaxForm('form_test/tableselect/multiple-false', $edit, $element);
+ $this->assertFalse(empty($result), t('Ajax triggers on radio for @row.', array('@row' => $row)));
+ }
+ }
+
/**
* Test the display of radios when #multiple is FALSE.
*/
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index b563d3cad..f28086621 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -412,7 +412,7 @@ function system_authorized_init($callback, $file, $arguments = array(), $page_ti
* Return the URL for the authorize.php script.
*
* @param array $options
- * Optional array of options to pass to url().
+ * Optional array of options to set on the \Drupal\Core\Url object.
* @return \Drupal\Core\Url
* The full URL to authorize.php, using HTTPS if available.
*
@@ -431,7 +431,7 @@ function system_authorized_get_url(array $options = array()) {
* Returns the URL for the authorize.php script when it is processing a batch.
*
* @param array $options
- * Optional array of options to pass to url().
+ * Optional array of options to set on the \Drupal\Core\Url object.
*
* @return \Drupal\Core\Url
*/
diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module
index ee4c137cd..7114a29ca 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -104,3 +104,10 @@ function form_test_form_form_test_vertical_tabs_access_form_alter(&$form, &$form
$form['fieldset1']['#access'] = FALSE;
$form['container']['#access'] = FALSE;
}
+
+/**
+ * Ajax callback that returns the form element.
+ */
+function form_test_tableselect_ajax_callback($form, FormStateInterface $form_state) {
+ return $form['tableselect'];
+}
diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestTableSelectFormBase.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestTableSelectFormBase.php
index f618c92cc..5c3b3838d 100644
--- a/core/modules/system/tests/modules/form_test/src/Form/FormTestTableSelectFormBase.php
+++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestTableSelectFormBase.php
@@ -34,11 +34,17 @@ abstract class FormTestTableSelectFormBase extends FormBase {
$form['tableselect'] = $element_properties;
$form['tableselect'] += array(
+ '#prefix' => '',
+ '#suffix' => '
',
'#type' => 'tableselect',
'#header' => $header,
'#options' => $options,
'#multiple' => FALSE,
'#empty' => t('Empty text.'),
+ '#ajax' => array(
+ 'callback' => 'form_test_tableselect_ajax_callback',
+ 'wrapper' => 'tableselect-wrapper',
+ ),
);
$form['submit'] = array(
diff --git a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
index 7c817c2ce..64433c48f 100644
--- a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
+++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
@@ -86,4 +86,11 @@ class Test {
throw new HttpException($code);
}
+ public function error() {
+ trigger_error('foo', E_USER_NOTICE);
+ return [
+ '#markup' => 'Content',
+ ];
+ }
+
}
diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
index a00d0844c..a2d5ed5a4 100644
--- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
+++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
@@ -50,3 +50,11 @@ test_page_test.http_response_exception:
code: 200
requirements:
_access: 'TRUE'
+
+test_page_test.error:
+ path: '/test-error'
+ defaults:
+ _controller: '\Drupal\test_page_test\Controller\Test::error'
+ code: 200
+ requirements:
+ _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.services.yml b/core/modules/system/tests/modules/theme_test/theme_test.services.yml
index 482c219c7..271fd4be9 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.services.yml
+++ b/core/modules/system/tests/modules/theme_test/theme_test.services.yml
@@ -1,7 +1,7 @@
services:
theme_test.subscriber:
class: Drupal\theme_test\EventSubscriber\ThemeTestSubscriber
- arguments: [@current_route_match, @renderer]
+ arguments: ['@current_route_match', '@renderer']
tags:
- { name: event_subscriber }
diff --git a/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
index 47804ab80..de76f49cf 100644
--- a/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
+++ b/core/modules/system/tests/src/Kernel/PhpStorage/PhpStorageFactoryTest.php
@@ -65,18 +65,28 @@ class PhpStorageFactoryTest extends KernelTestBase {
$this->setSettings('test', array('bin' => NULL));
$php = PhpStorageFactory::get('test');
$this->assertTrue($php instanceof MockPhpStorage, 'An MockPhpStorage instance was returned from overridden settings.');
- $this->assertIdentical('test', $php->getConfigurationValue('bin'), 'Name value was used for bin.');
+ $this->assertSame('test', $php->getConfigurationValue('bin'), 'Name value was used for bin.');
// Test that a default directory is set if it's empty.
$this->setSettings('test', array('directory' => NULL));
$php = PhpStorageFactory::get('test');
$this->assertTrue($php instanceof MockPhpStorage, 'An MockPhpStorage instance was returned from overridden settings.');
- $this->assertIdentical(PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
+ $this->assertSame(PublicStream::basePath() . '/php', $php->getConfigurationValue('directory'), 'Default file directory was used.');
// Test that a default storage class is set if it's empty.
$this->setSettings('test', array('class' => NULL));
$php = PhpStorageFactory::get('test');
$this->assertTrue($php instanceof MTimeProtectedFileStorage, 'An MTimeProtectedFileStorage instance was returned from overridden settings with no class.');
+
+ // Test that a default secret is not returned if it's set in the override.
+ $this->setSettings('test');
+ $php = PhpStorageFactory::get('test');
+ $this->assertNotEquals('mock hash salt', $php->getConfigurationValue('secret'), 'The default secret is not used if a secret is set in the overridden settings.');
+
+ // Test that a default secret is set if it's empty.
+ $this->setSettings('test', array('secret' => NULL));
+ $php = PhpStorageFactory::get('test');
+ $this->assertSame('mock hash salt', $php->getConfigurationValue('secret'), 'The default secret is used if one is not set in the overridden settings.');
}
/**
@@ -94,6 +104,7 @@ class PhpStorageFactoryTest extends KernelTestBase {
'secret' => $this->randomString(),
'bin' => 'test',
);
+ $settings['hash_salt'] = 'mock hash salt';
new Settings($settings);
}
diff --git a/core/modules/taxonomy/migration_templates/d6_vocabulary_field.yml b/core/modules/taxonomy/migration_templates/d6_vocabulary_field.yml
index 5139f318b..a1c97356f 100644
--- a/core/modules/taxonomy/migration_templates/d6_vocabulary_field.yml
+++ b/core/modules/taxonomy/migration_templates/d6_vocabulary_field.yml
@@ -8,7 +8,6 @@ source:
entity_type: node
type: entity_reference
target_entity_type: taxonomy_term
- cardinality: -1
process:
entity_type: 'constants/entity_type'
type: 'constants/type'
@@ -21,7 +20,7 @@ process:
plugin: skip_on_empty
method: row
'settings/target_type': 'constants/target_entity_type'
- cardinality: 'constants/cardinality'
+ cardinality: cardinality
destination:
plugin: md_entity:field_storage_config
migration_dependencies:
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index dffbe34e6..a44bc54d4 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -27,7 +27,7 @@ class OverviewTerms extends FormBase {
protected $moduleHandler;
/**
- * The term storage controller.
+ * The term storage handler.
*
* @var \Drupal\taxonomy\TermStorageInterface
*/
diff --git a/core/modules/taxonomy/src/Plugin/migrate/source/d6/Vocabulary.php b/core/modules/taxonomy/src/Plugin/migrate/source/d6/Vocabulary.php
index d2d3f1985..5c605bb4f 100644
--- a/core/modules/taxonomy/src/Plugin/migrate/source/d6/Vocabulary.php
+++ b/core/modules/taxonomy/src/Plugin/migrate/source/d6/Vocabulary.php
@@ -9,6 +9,7 @@ namespace Drupal\taxonomy\Plugin\migrate\source\d6;
use Drupal\migrate\Row;
use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Drupal 6 vocabularies source from database.
@@ -72,6 +73,7 @@ class Vocabulary extends DrupalSqlBase {
->execute()
->fetchCol();
$row->setSourceProperty('node_types', $node_types);
+ $row->setSourceProperty('cardinality', ($row->getSourceProperty('tags') == 1 || $row->getSourceProperty('multiple') == 1) ? FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED : 1);
return parent::prepareRow($row);
}
diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php
index 9da099364..ba1dcb329 100644
--- a/core/modules/taxonomy/src/TermForm.php
+++ b/core/modules/taxonomy/src/TermForm.php
@@ -11,7 +11,7 @@ use Drupal\Core\Entity\ContentEntityForm;
use Drupal\Core\Form\FormStateInterface;
/**
- * Base for controller for taxonomy term edit forms.
+ * Base for handler for taxonomy term edit forms.
*/
class TermForm extends ContentEntityForm {
diff --git a/core/modules/taxonomy/src/TermViewBuilder.php b/core/modules/taxonomy/src/TermViewBuilder.php
index efd4374f1..ee9593f54 100644
--- a/core/modules/taxonomy/src/TermViewBuilder.php
+++ b/core/modules/taxonomy/src/TermViewBuilder.php
@@ -12,7 +12,7 @@ use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityViewBuilder;
/**
- * Render controller for taxonomy terms.
+ * View builder handler for taxonomy terms.
*/
class TermViewBuilder extends EntityViewBuilder {
diff --git a/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldInstanceTest.php b/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldInstanceTest.php
index bab6ae552..8f2307da4 100644
--- a/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldInstanceTest.php
+++ b/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldInstanceTest.php
@@ -10,6 +10,7 @@ namespace Drupal\taxonomy\Tests\Migrate\d6;
use Drupal\field\Entity\FieldConfig;
use Drupal\migrate\Entity\Migration;
use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
+use Drupal\taxonomy\Entity\Vocabulary;
/**
* Vocabulary field instance migration.
diff --git a/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldTest.php b/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldTest.php
index 9e866d82e..b8d99ed87 100644
--- a/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldTest.php
+++ b/core/modules/taxonomy/src/Tests/Migrate/d6/MigrateVocabularyFieldTest.php
@@ -37,11 +37,13 @@ class MigrateVocabularyFieldTest extends MigrateDrupal6TestBase {
public function testVocabularyField() {
// Test that the field exists.
$field_storage_id = 'node.tags';
+ /** @var \Drupal\field\FieldStorageConfigInterface $field_storage */
$field_storage = FieldStorageConfig::load($field_storage_id);
$this->assertIdentical($field_storage_id, $field_storage->id());
$settings = $field_storage->getSettings();
$this->assertIdentical('taxonomy_term', $settings['target_type'], "Target type is correct.");
+ $this->assertIdentical(1, $field_storage->getCardinality(), "Field cardinality in 1.");
$this->assertIdentical(array('node', 'tags'), Migration::load('d6_vocabulary_field')->getIdMap()->lookupDestinationID(array(4)), "Test IdMap");
}
diff --git a/core/modules/taxonomy/src/VocabularyStorage.php b/core/modules/taxonomy/src/VocabularyStorage.php
index b761bcc22..5fb3c3f0e 100644
--- a/core/modules/taxonomy/src/VocabularyStorage.php
+++ b/core/modules/taxonomy/src/VocabularyStorage.php
@@ -10,7 +10,7 @@ namespace Drupal\taxonomy;
use Drupal\Core\Config\Entity\ConfigEntityStorage;
/**
- * Defines a controller class for taxonomy vocabularies.
+ * Defines a storage handler class for taxonomy vocabularies.
*/
class VocabularyStorage extends ConfigEntityStorage implements VocabularyStorageInterface {
diff --git a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php b/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php
index 60362d73c..ab263d197 100644
--- a/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php
+++ b/core/modules/taxonomy/tests/src/Unit/Migrate/d6/VocabularyTest.php
@@ -8,6 +8,7 @@
namespace Drupal\Tests\taxonomy\Unit\Migrate\d6;
use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests D6 vocabulary source plugin.
@@ -39,6 +40,7 @@ class VocabularyTest extends MigrateSqlSourceTestCase {
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['page', 'article'],
+ 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
],
[
'vid' => 2,
@@ -53,6 +55,7 @@ class VocabularyTest extends MigrateSqlSourceTestCase {
'module' => 'taxonomy',
'weight' => 0,
'node_types' => ['article'],
+ 'cardinality' => 1,
],
];
diff --git a/core/modules/user/src/ProfileForm.php b/core/modules/user/src/ProfileForm.php
index e1196931c..c8cc6138b 100644
--- a/core/modules/user/src/ProfileForm.php
+++ b/core/modules/user/src/ProfileForm.php
@@ -13,7 +13,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
/**
- * Form controller for the profile forms.
+ * Form handler for the profile forms.
*/
class ProfileForm extends AccountForm {
diff --git a/core/modules/user/src/RegisterForm.php b/core/modules/user/src/RegisterForm.php
index d24a4b474..d236dd3b6 100644
--- a/core/modules/user/src/RegisterForm.php
+++ b/core/modules/user/src/RegisterForm.php
@@ -13,7 +13,7 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Language\LanguageManagerInterface;
/**
- * Form controller for the user register forms.
+ * Form handler for the user register forms.
*/
class RegisterForm extends AccountForm {
diff --git a/core/modules/user/src/Tests/Migrate/d6/MigrateUserProfileValuesTest.php b/core/modules/user/src/Tests/Migrate/d6/MigrateUserProfileValuesTest.php
index e2e4676a5..e4b5dd326 100644
--- a/core/modules/user/src/Tests/Migrate/d6/MigrateUserProfileValuesTest.php
+++ b/core/modules/user/src/Tests/Migrate/d6/MigrateUserProfileValuesTest.php
@@ -54,6 +54,11 @@ EOT;
$this->assertIdentical('Queen', $user->profile_bands[5]->value);
$this->assertIdentical('The White Stripes', $user->profile_bands[6]->value);
$this->assertIdentical('1974-06-02', $user->profile_birthdate->value);
+ $this->assertIdentical('http://example.com/blog', $user->profile_blog->uri);
+ $this->assertNull($user->profile_blog->title);
+ $this->assertIdentical([], $user->profile_blog->options);
+ $this->assertIdentical('http://example.com/blog', $user->profile_blog->uri);
+ $this->assertNull($user->profile_love_migrations->value);
$user = User::load(8);
$this->assertIdentical('Forward/slash', $user->profile_sold_to->value);
diff --git a/core/modules/user/templates/username.html.twig b/core/modules/user/templates/username.html.twig
index 5d4f2f75f..6c1fccbff 100644
--- a/core/modules/user/templates/username.html.twig
+++ b/core/modules/user/templates/username.html.twig
@@ -9,8 +9,8 @@
* - extra: Additional text to append to the user's name, sanitized.
* - link_path: The path or URL of the user's profile page, home page,
* or other desired page to link to for more information about the user.
- * - link_options: Options to pass to the url() function's $options parameter if
- * linking the user's name to the user's page.
+ * - link_options: Options to set on the \Drupal\Core\Url object if linking the
+ * user's name to the user's page.
* - attributes: HTML attributes for the containing element.
*
* @see template_preprocess_username()
diff --git a/core/modules/views/src/EntityViewsData.php b/core/modules/views/src/EntityViewsData.php
index 4dc23d8b7..00c70390a 100644
--- a/core/modules/views/src/EntityViewsData.php
+++ b/core/modules/views/src/EntityViewsData.php
@@ -28,7 +28,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
use StringTranslationTrait;
/**
- * Entity type for this views controller instance.
+ * Entity type for this views data handler instance.
*
* @var \Drupal\Core\Entity\EntityTypeInterface
*/
@@ -68,7 +68,7 @@ class EntityViewsData implements EntityHandlerInterface, EntityViewsDataInterfac
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
* The entity type to provide views integration for.
* @param \Drupal\Core\Entity\Sql\SqlEntityStorageInterface $storage_controller
- * The storage controller used for this entity type.
+ * The storage handler used for this entity type.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
diff --git a/core/modules/views/src/Plugin/views/HandlerBase.php b/core/modules/views/src/Plugin/views/HandlerBase.php
index b5bb16d6f..b8fa6d29a 100644
--- a/core/modules/views/src/Plugin/views/HandlerBase.php
+++ b/core/modules/views/src/Plugin/views/HandlerBase.php
@@ -735,12 +735,12 @@ abstract class HandlerBase extends PluginBase implements ViewsHandlerInterface {
// Determine if the string has 'or' operators (plus signs) or 'and'
// operators (commas) and split the string accordingly.
- if (preg_match('/^([\w0-9-_]+[+ ]+)+[\w0-9-_]+$/u', $str)) {
+ if (preg_match('/^([\w0-9-_\.]+[+ ]+)+[\w0-9-_\.]+$/u', $str)) {
// The '+' character in a query string may be parsed as ' '.
$operator = 'or';
$value = preg_split('/[+ ]/', $str);
}
- elseif (preg_match('/^([\w0-9-_]+[, ]+)*[\w0-9-_]+$/u', $str)) {
+ elseif (preg_match('/^([\w0-9-_\.]+[, ]+)*[\w0-9-_\.]+$/u', $str)) {
$operator = 'and';
$value = explode(',', $str);
}
diff --git a/core/modules/views/src/Plugin/views/argument/NumericArgument.php b/core/modules/views/src/Plugin/views/argument/NumericArgument.php
index 02e58f098..e7df12998 100644
--- a/core/modules/views/src/Plugin/views/argument/NumericArgument.php
+++ b/core/modules/views/src/Plugin/views/argument/NumericArgument.php
@@ -67,7 +67,7 @@ class NumericArgument extends ArgumentPluginBase {
}
if (!empty($this->options['break_phrase'])) {
- $break = static::breakString($this->argument, TRUE);
+ $break = static::breakString($this->argument, FALSE);
$this->value = $break->value;
$this->operator = $break->operator;
}
@@ -100,7 +100,7 @@ class NumericArgument extends ArgumentPluginBase {
$this->ensureMyTable();
if (!empty($this->options['break_phrase'])) {
- $break = static::breakString($this->argument, TRUE);
+ $break = static::breakString($this->argument, FALSE);
$this->value = $break->value;
$this->operator = $break->operator;
}
diff --git a/core/modules/views/src/Plugin/views/display/EntityReference.php b/core/modules/views/src/Plugin/views/display/EntityReference.php
index 38d8f290b..6f72ce9c6 100644
--- a/core/modules/views/src/Plugin/views/display/EntityReference.php
+++ b/core/modules/views/src/Plugin/views/display/EntityReference.php
@@ -119,7 +119,8 @@ class EntityReference extends DisplayPluginBase {
// Make sure the id field is included in the results.
$id_field = $this->view->storage->get('base_field');
- $this->id_field_alias = $this->view->query->addField($this->view->storage->get('base_table'), $id_field);
+ $id_table = $this->view->storage->get('base_table');
+ $this->id_field_alias = $this->view->query->addField($id_table, $id_field);
$options = $this->getOption('entity_reference_options');
@@ -139,7 +140,8 @@ class EntityReference extends DisplayPluginBase {
if (!empty($field_id)) {
// Get the table and field names for the checked field.
$field_handler = $this->view->field[$field_id];
- $field_alias = $this->view->query->addField($field_handler->table, $field_handler->realField);
+ $table_alias = $this->view->query->ensureTable($field_handler->table, $field_handler->relationship);
+ $field_alias = $this->view->query->addField($table_alias, $field_handler->realField);
$field = $this->view->query->fields[$field_alias];
// Add an OR condition for the field.
$conditions->condition($field['table'] . '.' . $field['field'], $value, 'LIKE');
@@ -151,7 +153,7 @@ class EntityReference extends DisplayPluginBase {
// Add an IN condition for validation.
if (!empty($options['ids'])) {
- $this->view->query->addWhere(0, $id_field, $options['ids'], 'IN');
+ $this->view->query->addWhere(0, $id_table . '.' . $id_field, $options['ids'], 'IN');
}
$this->view->setItemsPerPage($options['limit']);
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index 9208e10e8..0716a6a28 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1681,8 +1681,7 @@ abstract class FieldPluginBase extends HandlerBase implements FieldHandlerInterf
* fields as a list. For example, the field that displays all terms
* on a node might have tokens for the tid and the term.
*
- * By convention, tokens should follow the format of {{ token
- * subtoken }}
+ * By convention, tokens should follow the format of {{ token__subtoken }}
* where token is the field ID and subtoken is the field. If the
* field ID is terms, then the tokens might be {{ terms__tid }} and
* {{ terms__name }}.
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index dade1dfa7..c0cd5d037 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -412,7 +412,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#type' => 'submit',
'#value' => $this->t('Grouped filters'),
'#submit' => array(array($this, 'buildGroupForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['group_button']['radios']['radios']['#default_value'] = 0;
}
@@ -422,7 +421,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#type' => 'submit',
'#value' => $this->t('Single filter'),
'#submit' => array(array($this, 'buildGroupForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['group_button']['radios']['radios']['#default_value'] = 1;
}
@@ -487,7 +485,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#type' => 'submit',
'#value' => $this->t('Expose filter'),
'#submit' => array(array($this, 'displayExposedForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['expose_button']['checkbox']['checkbox']['#default_value'] = 0;
}
@@ -500,7 +497,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#type' => 'submit',
'#value' => $this->t('Hide filter'),
'#submit' => array(array($this, 'displayExposedForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['expose_button']['checkbox']['checkbox']['#default_value'] = 1;
}
@@ -1075,7 +1071,6 @@ abstract class FilterPluginBase extends HandlerBase implements CacheableDependen
'#type' => 'submit',
'#value' => $this->t('Add another item'),
'#submit' => array(array($this, 'addGroupForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$js = array();
diff --git a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php
index ca595fbe2..db467913a 100644
--- a/core/modules/views/src/Plugin/views/sort/SortPluginBase.php
+++ b/core/modules/views/src/Plugin/views/sort/SortPluginBase.php
@@ -125,7 +125,6 @@ abstract class SortPluginBase extends HandlerBase implements CacheableDependency
'#type' => 'submit',
'#value' => $this->t('Expose sort'),
'#submit' => array(array($this, 'displayExposedForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['expose_button']['checkbox']['checkbox']['#default_value'] = 0;
}
@@ -138,7 +137,6 @@ abstract class SortPluginBase extends HandlerBase implements CacheableDependency
'#type' => 'submit',
'#value' => $this->t('Hide sort'),
'#submit' => array(array($this, 'displayExposedForm')),
- '#attributes' => array('class' => array('use-ajax-submit')),
);
$form['expose_button']['checkbox']['checkbox']['#default_value'] = 1;
}
diff --git a/core/modules/views/src/Tests/Handler/HandlerTest.php b/core/modules/views/src/Tests/Handler/HandlerTest.php
index 6507e2315..fabb29a10 100644
--- a/core/modules/views/src/Tests/Handler/HandlerTest.php
+++ b/core/modules/views/src/Tests/Handler/HandlerTest.php
@@ -162,6 +162,37 @@ class HandlerTest extends ViewTestBase {
$handlerBase = HandlerBase::breakString("$s1+$n2+$n3", TRUE);
$this->assertEqualValue(array((int) $s1, $n2, $n3), $handlerBase);
$this->assertEqual('or', $handlerBase->operator);
+
+ // Generate three random decimals which can be used below;
+ $d1 = rand(0, 10) / 10;
+ $d2 = rand(0, 10) / 10;
+ $d3 = rand(0, 10) / 10;
+
+ // Test "or"s.
+ $handlerBase = HandlerBase::breakString("$s1 $d1+$d2");
+ $this->assertEqualValue(array($s1, $d1, $d2), $handlerBase);
+ $this->assertEqual('or', $handlerBase->operator);
+
+ $handlerBase = HandlerBase::breakString("$s1+$d1+$d3");
+ $this->assertEqualValue(array($s1, $d1, $d3), $handlerBase);
+ $this->assertEqual('or', $handlerBase->operator);
+
+ $handlerBase = HandlerBase::breakString("$s1 $d2 $d3");
+ $this->assertEqualValue(array($s1, $d2, $d3), $handlerBase);
+ $this->assertEqual('or', $handlerBase->operator);
+
+ $handlerBase = HandlerBase::breakString("$s1 $d2++$d3");
+ $this->assertEqualValue(array($s1, $d2, $d3), $handlerBase);
+ $this->assertEqual('or', $handlerBase->operator);
+
+ // Test "and"s.
+ $handlerBase = HandlerBase::breakString("$s1,$d2,$d3");
+ $this->assertEqualValue(array($s1, $d2, $d3), $handlerBase);
+ $this->assertEqual('and', $handlerBase->operator);
+
+ $handlerBase = HandlerBase::breakString("$s1,,$d2,$d3");
+ $this->assertEqualValue(array($s1, $d2, $d3), $handlerBase);
+ $this->assertEqual('and', $handlerBase->operator);
}
/**
diff --git a/core/modules/views/src/Tests/Plugin/DisplayEntityReferenceTest.php b/core/modules/views/src/Tests/Plugin/DisplayEntityReferenceTest.php
index 29f09f4ee..43a389dbf 100644
--- a/core/modules/views/src/Tests/Plugin/DisplayEntityReferenceTest.php
+++ b/core/modules/views/src/Tests/Plugin/DisplayEntityReferenceTest.php
@@ -10,6 +10,7 @@ namespace Drupal\views\Tests\Plugin;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\field\Tests\EntityReference\EntityReferenceTestTrait;
use Drupal\views\Views;
/**
@@ -21,6 +22,8 @@ use Drupal\views\Views;
*/
class DisplayEntityReferenceTest extends PluginTestBase {
+ use EntityReferenceTestTrait;
+
/**
* Views used by this test.
*
@@ -42,6 +45,13 @@ class DisplayEntityReferenceTest extends PluginTestBase {
*/
protected $fieldName;
+ /**
+ * The used entity reference field name in the test.
+ *
+ * @var string
+ */
+ protected $entityRefFieldName;
+
/**
* The field storage.
*
@@ -80,6 +90,10 @@ class DisplayEntityReferenceTest extends PluginTestBase {
]);
$this->field->save();
+ // Add an entity reference field to reference the same base table.
+ $this->entityRefFieldName = 'field_test_entity_ref_entity_ref';
+ $this->createEntityReferenceField('entity_test', 'entity_test', $this->entityRefFieldName, NULL, 'entity_test');
+
// Create some entities to search. Add a common string to the name and
// the text field in two entities so we can test that we can search in both.
for ($i = 0; $i < 5; $i++) {
@@ -129,6 +143,59 @@ class DisplayEntityReferenceTest extends PluginTestBase {
// Test that we have searched in both fields.
$this->assertEqual(count($view->result), 2, 'Search returned two rows');
+ $view->destroy();
+
+ // Add a relationship and a field using that relationship.
+ $this->drupalPostForm('admin/structure/views/nojs/add-handler/test_display_entity_reference/default/relationship', ['name[entity_test.user_id]' => TRUE], t('Add and configure relationships'));
+ $this->drupalPostForm(NULL, [], t('Apply'));
+
+ $this->drupalPostForm('admin/structure/views/nojs/add-handler/test_display_entity_reference/default/field', ['name[users_field_data.uid]' => TRUE], t('Add and configure fields'));
+ $this->drupalPostForm(NULL, [], t('Apply'));
+
+ // Add the new field to the search fields.
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_display_entity_reference/entity_reference_1/style_options', ['style_options[search_fields][uid]' => 'uid'], t('Apply'));
+ $this->drupalPostForm(NULL, [], t('Save'));
+
+ // Test that the search still works with the ralated field.
+ $view = Views::getView('test_display_entity_reference');
+ $view->setDisplay('entity_reference_1');
+
+ // Add the required settings to test a search operation.
+ $options = [
+ 'match' => '2',
+ 'match_operator' => 'CONTAINS',
+ 'limit' => 0,
+ 'ids' => NULL,
+ ];
+ $view->display_handler->setOption('entity_reference_options', $options);
+
+ $this->executeView($view);
+
+ // Run validation when using a relationship to the same base table.
+ $this->assertEqual(count($view->result), 2, 'Search returned two rows');
+ $view->destroy();
+
+ $this->drupalPostForm('admin/structure/views/nojs/add-handler/test_display_entity_reference/default/relationship', ['name[entity_test__field_test_entity_ref_entity_ref.field_test_entity_ref_entity_ref]' => TRUE], t('Add and configure relationships'));
+ $this->drupalPostForm(NULL, [], t('Apply'));
+
+ $this->drupalPostForm(NULL, [], t('Save'));
+
+ // Test that the search still works with the related field.
+ $view = Views::getView('test_display_entity_reference');
+ $view->setDisplay('entity_reference_1');
+
+ // Add IDs to trigger validation.
+ $options = [
+ 'match' => '1',
+ 'match_operator' => 'CONTAINS',
+ 'limit' => 0,
+ 'ids' => [1, 2],
+ ];
+ $view->display_handler->setOption('entity_reference_options', $options);
+
+ $this->executeView($view);
+
+ $this->assertEqual(count($view->result), 2, 'Search returned two rows');
}
}
diff --git a/core/modules/views/src/Tests/Plugin/StyleSummaryTest.php b/core/modules/views/src/Tests/Plugin/StyleSummaryTest.php
index cea8fba1b..cf585ce4f 100644
--- a/core/modules/views/src/Tests/Plugin/StyleSummaryTest.php
+++ b/core/modules/views/src/Tests/Plugin/StyleSummaryTest.php
@@ -20,7 +20,7 @@ class StyleSummaryTest extends ViewTestBase {
/**
* {@inheritdoc}
*/
- public static $modules = ['entity_test'];
+ public static $modules = ['entity_test', 'views_ui'];
/**
* {@inheritdoc}
@@ -48,6 +48,9 @@ class StyleSummaryTest extends ViewTestBase {
$entity->save();
}
}
+
+ $views_user = $this->drupalCreateUser(['administer views']);
+ $this->drupalLogin($views_user);
}
/**
@@ -72,6 +75,71 @@ class StyleSummaryTest extends ViewTestBase {
$this->clickLink('type1');
$entries = $this->cssSelect('div.view-content div.views-row');
$this->assertEqual(2, count($entries));
+
+ // Add a base path to the summary settings.
+ $edit = [
+ 'options[summary][options][default_summary][base_path]' => 'test-summary',
+ ];
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_summary/page_1/argument/type', $edit, t('Apply'));
+ $this->drupalPostForm(NULL, [], t('Save'));
+
+ // Test that the links still work.
+ $this->drupalGet('test-summary');
+ $this->clickLink('type1');
+ $entries = $this->cssSelect('div.view-content div.views-row');
+ $this->assertEqual(2, count($entries));
+
+ // Change the summary display to an unformatted list displaying 3 items.
+ $edit = [
+ 'options[summary][format]' => 'unformatted_summary',
+ 'options[summary][options][unformatted_summary][override]' => '1',
+ 'options[summary][options][unformatted_summary][items_per_page]' => '3',
+ ];
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_summary/page_1/argument/type', $edit, t('Apply'));
+ $this->drupalPostForm(NULL, [], t('Save'));
+
+ $this->drupalGet('admin/structure/views/nojs/handler/test_summary/page_1/argument/type');
+ $this->drupalGet('test-summary');
+
+ $summary_list = $this->cssSelect('.views-summary-unformatted');
+ $this->assertEqual(3, count($summary_list));
+
+ foreach ($summary_list as $summary_list_item) {
+ $this->assertEqual('(5)', trim((string) $summary_list_item));
+ }
+
+ $summary_links = $this->cssSelect('.views-summary-unformatted a');
+ $this->assertEqual(3, count($summary_links));
+ foreach ($summary_links as $index => $summary_link) {
+ $this->assertEqual('type' . $index, trim((string) $summary_link));
+ }
+
+ $this->clickLink('type1');
+ $entries = $this->cssSelect('div.view-content div.views-row');
+ $this->assertEqual(2, count($entries));
+
+ // Add a base path to the summary settings.
+ $edit = [
+ 'options[summary][options][unformatted_summary][base_path]' => 'test-summary',
+ ];
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_summary/page_1/argument/type', $edit, t('Apply'));
+ $this->drupalPostForm(NULL, [], t('Save'));
+
+ // Test that the links still work.
+ $this->drupalGet('test-summary');
+ $this->clickLink('type1');
+ $entries = $this->cssSelect('div.view-content div.views-row');
+ $this->assertEqual(2, count($entries));
+
+ // Set base_path to an unknown path and test that the links lead to the
+ // front page.
+ $edit = [
+ 'options[summary][options][unformatted_summary][base_path]' => 'unknown-path',
+ ];
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_summary/page_1/argument/type', $edit, t('Apply'));
+ $this->drupalPostForm(NULL, [], t('Save'));
+ $this->drupalGet('test-summary');
+ $this->assertLinkByHref('/');
}
}
diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml
index daf92fa2a..3784bd960 100644
--- a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml
+++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_summary.yml
@@ -75,6 +75,7 @@ display:
specify_validation: true
plugin_id: string
entity_type: entity_test
+ admin_label: type
fields:
id:
id: id
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index c14416f39..94c519e71 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -277,11 +277,29 @@ function template_preprocess_views_view_summary(&$variables) {
if (!empty($argument->options['summary_options']['base_path'])) {
$base_path = $argument->options['summary_options']['base_path'];
- $tokens = $this->getArgumentsTokens();
- $base_path = $this->viewsTokenReplace($base_path, $tokens);
+ $tokens = $view->getDisplay()->getArgumentsTokens();
+ $base_path = $argument->globalTokenReplace($base_path, $tokens);
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$url = Url::fromUserInput('/' . $base_path);
+ try {
+ /** @var \Symfony\Component\Routing\Route $route */
+ $route_name = $url->getRouteName();
+ $route = \Drupal::service('router.route_provider')->getRouteByName($route_name);
+
+ $route_variables = $route->compile()->getVariables();
+ $parameters = $url->getRouteParameters();
+
+ foreach ($route_variables as $variable_name) {
+ $parameters[$variable_name] = array_shift($args);
+ }
+
+ $url->setRouteParameters($parameters);
+ }
+ catch (Exception $e) {
+ // If the given route doesn't exist, default to
+ $url = Url::fromRoute('');
+ }
}
else {
$url = $view->getUrl($args)->setOptions($url_options);
@@ -307,6 +325,7 @@ function template_preprocess_views_view_summary(&$variables) {
* visually distinct.
*/
function template_preprocess_views_view_summary_unformatted(&$variables) {
+ /** @var \Drupal\views\ViewExecutable $view */
$view = $variables['view'];
$argument = $view->argument[$view->build_info['summary_level']];
@@ -346,11 +365,27 @@ function template_preprocess_views_view_summary_unformatted(&$variables) {
if (!empty($argument->options['summary_options']['base_path'])) {
$base_path = $argument->options['summary_options']['base_path'];
- $tokens = $this->getArgumentsTokens();
- $base_path = $this->viewsTokenReplace($base_path, $tokens);
+ $tokens = $view->getDisplay()->getArgumentsTokens();
+ $base_path = $argument->globalTokenReplace($base_path, $tokens);
// @todo Views should expect and store a leading /. See:
// https://www.drupal.org/node/2423913
$url = Url::fromUserInput('/' . $base_path);
+ try {
+ /** @var \Symfony\Component\Routing\Route $route */
+ $route = \Drupal::service('router.route_provider')->getRouteByName($url->getRouteName());
+ $route_variables = $route->compile()->getVariables();
+ $parameters = $url->getRouteParameters();
+
+ foreach ($route_variables as $variable_name) {
+ $parameters[$variable_name] = array_shift($args);
+ }
+
+ $url->setRouteParameters($parameters);
+ }
+ catch (Exception $e) {
+ // If the given route doesn't exist, default to
+ $url = Url::fromRoute('');
+ }
}
else {
$url = $view->getUrl($args)->setOptions($url_options);
diff --git a/core/modules/views_ui/js/ajax.js b/core/modules/views_ui/js/ajax.js
index 0dc0a74d3..b044688c3 100644
--- a/core/modules/views_ui/js/ajax.js
+++ b/core/modules/views_ui/js/ajax.js
@@ -24,6 +24,38 @@
$(response.selector).addClass('hilited');
};
+ /**
+ * Ajax command to set the form submit action in the views modal edit form.
+ *
+ * @param {Drupal.Ajax} [ajax]
+ * An Ajax object.
+ * @param {object} response
+ * The Ajax response. Contains .url
+ * @param {string} [status]
+ * The XHR status code?
+ */
+ Drupal.AjaxCommands.prototype.viewsSetForm = function (ajax, response, status) {
+ var $form = $('.js-views-ui-dialog form');
+ // Identify the button that was clicked so that .ajaxSubmit() can use it.
+ // We need to do this for both .click() and .mousedown() since JavaScript
+ // code might trigger either behavior.
+ var $submit_buttons = $form.find('input[type=submit].js-form-submit, button.js-form-submit').once('views-ajax-submit');
+ $submit_buttons.on('click mousedown', function () {
+ this.form.clk = this;
+ });
+ $form.once('views-ajax-submit').each(function () {
+ var $form = $(this);
+ var element_settings = {
+ url: response.url,
+ event: 'submit',
+ base: $form.attr('id'),
+ element: this
+ };
+ var ajaxForm = Drupal.ajax(element_settings);
+ ajaxForm.$form = $form;
+ });
+ };
+
/**
* Ajax command to show certain buttons in the views edit form.
*
diff --git a/core/modules/views_ui/src/Ajax/SetFormCommand.php b/core/modules/views_ui/src/Ajax/SetFormCommand.php
new file mode 100644
index 000000000..e11452960
--- /dev/null
+++ b/core/modules/views_ui/src/Ajax/SetFormCommand.php
@@ -0,0 +1,46 @@
+url = $url;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function render() {
+ return array(
+ 'command' => 'viewsSetForm',
+ 'url' => $this->url,
+ );
+ }
+
+}
diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
index 8df344a83..e82dc4234 100644
--- a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
+++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php
@@ -181,9 +181,6 @@ class ConfigHandler extends ViewsFormBase {
'#value' => $this->t('Remove'),
'#submit' => array(array($this, 'remove')),
'#limit_validation_errors' => array(array('override')),
- '#ajax' => array(
- 'url' => Url::fromRoute(''),
- ),
'#button_type' => 'danger',
);
}
diff --git a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
index f58484cc1..478dae155 100644
--- a/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
+++ b/core/modules/views_ui/src/Form/Ajax/RearrangeFilter.php
@@ -134,7 +134,6 @@ class RearrangeFilter extends ViewsFormBase {
'class' => array('views-remove-group'),
),
'#group' => $id,
- '#ajax' => ['url' => NULL],
);
}
$group_options[$id] = $id == 1 ? $this->t('Default group') : $this->t('Group @group', array('@group' => $id));
diff --git a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
index b3edb6471..b5497b641 100644
--- a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
+++ b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php
@@ -16,6 +16,7 @@ use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\views\ViewEntityInterface;
use Drupal\views\Ajax;
+use Drupal\views_ui\Ajax as AjaxUI;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\CloseModalDialogCommand;
use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -242,12 +243,18 @@ abstract class ViewsFormBase extends FormBase implements ViewsFormInterface {
$display .= $output;
$options = array(
- 'dialogClass' => 'views-ui-dialog',
+ 'dialogClass' => 'views-ui-dialog js-views-ui-dialog',
'width' => '75%',
);
$response->addCommand(new OpenModalDialogCommand($title, $display, $options));
+ // Views provides its own custom handling of AJAX form submissions.
+ // Usually this happens at the same path, but custom paths may be
+ // specified in $form_state.
+ $form_url = $form_state->has('url') ? $form_state->get('url')->toString() : $this->url('');
+ $response->addCommand(new AjaxUI\SetFormCommand($form_url));
+
if ($section = $form_state->get('#section')) {
$response->addCommand(new Ajax\HighlightCommand('.' . Html::cleanCssIdentifier($section)));
}
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 33c328423..8783c3e73 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -295,11 +295,6 @@ class ViewUI implements ViewEntityInterface {
$names = array(t('Apply'), t('Apply and continue'));
}
- // Views provides its own custom handling of AJAX form submissions. Usually
- // this happens at the same path, but custom paths may be specified in
- // $form_state.
- $form_url = $form_state->get('url') ?: Url::fromRouteMatch(\Drupal::routeMatch());
-
// Forms that are purely informational set an ok_button flag, so we know not
// to create an "Apply" button for them.
if (!$form_state->get('ok_button')) {
@@ -314,9 +309,6 @@ class ViewUI implements ViewEntityInterface {
// take care of running the regular submit handler as appropriate.
'#submit' => array(array($this, 'standardSubmit')),
'#button_type' => 'primary',
- '#ajax' => array(
- 'url' => $form_url,
- ),
);
// Form API button click detection requires the button's #value to be the
// same between the form build of the initial page request, and the
@@ -342,9 +334,6 @@ class ViewUI implements ViewEntityInterface {
'#value' => !$form_state->get('ok_button') ? t('Cancel') : t('Ok'),
'#submit' => array($cancel_submit),
'#validate' => array(),
- '#ajax' => array(
- 'path' => $form_url,
- ),
'#limit_validation_errors' => array(),
);
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index b9c7a0170..df613284b 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -14,6 +14,7 @@ use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\Test\TestRunnerKernel;
use Drupal\simpletest\Form\SimpletestResultsForm;
use Drupal\simpletest\TestBase;
+use Drupal\simpletest\TestDiscovery;
use Symfony\Component\HttpFoundation\Request;
$autoloader = require_once __DIR__ . '/../../autoload.php';
@@ -180,6 +181,12 @@ All arguments are long options.
Note that ':memory:' cannot be used, because this script spawns
sub-processes. However, you may use e.g. '/tmpfs/test.sqlite'
+ --keep-results-table
+
+ Boolean flag to indicate to not cleanup the simpletest result
+ table. For testbots or repeated execution of a single test it can
+ be helpful to not cleanup the simpletest result table.
+
--dburl A URI denoting the database driver, credentials, server hostname,
and database name to use in tests.
Required when running tests without a Drupal installation that
@@ -208,6 +215,12 @@ All arguments are long options.
Specify the path and the extension
(i.e. 'core/modules/user/user.test').
+ --types
+
+ Runs just tests from the specified test type, for example
+ run-tests.sh
+ (i.e. --types "Simpletest,PHPUnit-Functional")
+
--directory Run all tests found within the specified file directory.
--xml
@@ -292,10 +305,12 @@ function simpletest_script_parse_args() {
'module' => NULL,
'class' => FALSE,
'file' => FALSE,
+ 'types' => [],
'directory' => NULL,
'color' => FALSE,
'verbose' => FALSE,
'keep-results' => FALSE,
+ 'keep-results-table' => FALSE,
'test_names' => array(),
'repeat' => 1,
'die-on-fail' => FALSE,
@@ -320,6 +335,10 @@ function simpletest_script_parse_args() {
if (is_bool($args[$previous_arg])) {
$args[$matches[1]] = TRUE;
}
+ elseif (is_array($args[$previous_arg])) {
+ $value = array_shift($_SERVER['argv']);
+ $args[$matches[1]] = array_map('trim', explode(',', $value));
+ }
else {
$args[$matches[1]] = array_shift($_SERVER['argv']);
}
@@ -528,7 +547,8 @@ function simpletest_script_setup_database($new = FALSE) {
// Create the Simpletest schema.
try {
- $schema = Database::getConnection('default', 'test-runner')->schema();
+ $connection = Database::getConnection('default', 'test-runner');
+ $schema = $connection->schema();
}
catch (\PDOException $e) {
simpletest_script_print_error($databases['test-runner']['default']['driver'] . ': ' . $e->getMessage());
@@ -538,10 +558,13 @@ function simpletest_script_setup_database($new = FALSE) {
require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simpletest') . '/simpletest.install';
foreach (simpletest_schema() as $name => $table_spec) {
try {
- if ($schema->tableExists($name)) {
- $schema->dropTable($name);
+ $table_exists = $schema->tableExists($name);
+ if (empty($args['keep-results-table']) && $table_exists) {
+ $connection->truncate($name)->execute();
+ }
+ if (!$table_exists) {
+ $schema->createTable($name, $table_spec);
}
- $schema->createTable($name, $table_spec);
}
catch (Exception $e) {
echo (string) $e;
@@ -891,10 +914,12 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
function simpletest_script_get_test_list() {
global $args;
+ $types_processed = empty($args['types']);
$test_list = array();
if ($args['all'] || $args['module']) {
try {
- $groups = simpletest_test_get_all($args['module']);
+ $groups = simpletest_test_get_all($args['module'], $args['types']);
+ $types_processed = TRUE;
}
catch (Exception $e) {
echo (string) $e;
@@ -916,7 +941,7 @@ function simpletest_script_get_test_list() {
}
else {
try {
- $groups = simpletest_test_get_all();
+ $groups = simpletest_test_get_all(NULL, $args['types']);
}
catch (Exception $e) {
echo (string) $e;
@@ -1017,7 +1042,8 @@ function simpletest_script_get_test_list() {
}
else {
try {
- $groups = simpletest_test_get_all();
+ $groups = simpletest_test_get_all(NULL, $args['types']);
+ $types_processed = TRUE;
}
catch (Exception $e) {
echo (string) $e;
@@ -1036,6 +1062,15 @@ function simpletest_script_get_test_list() {
}
}
+ // If the test list creation does not automatically limit by test type then
+ // we need to do so here.
+ if (!$types_processed) {
+ $test_list = array_filter($test_list, function ($test_class) use ($args) {
+ $test_info = TestDiscovery::getTestInfo($test_class);
+ return in_array($test_info['type'], $args['types'], TRUE);
+ });
+ }
+
if (empty($test_list)) {
simpletest_script_print_error('No valid tests were specified.');
exit(SIMPLETEST_SCRIPT_EXIT_FAILURE);
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php
new file mode 100644
index 000000000..c94d12493
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php
@@ -0,0 +1,203 @@
+entityManager = $this->container->get('entity.manager');
+ $this->state = $this->container->get('state');
+
+ $this->installSchema('system', 'sequences');
+
+ $this->installEntitySchema('user');
+ $this->installEntitySchema('entity_test');
+
+ // If the concrete test sub-class installs the Node or Comment modules,
+ // ensure that the node and comment entity schema are created before the
+ // field configurations are installed. This is because the entity tables
+ // need to be created before the body field storage tables. This prevents
+ // trying to create the body field tables twice.
+ $class = get_class($this);
+ while ($class) {
+ if (property_exists($class, 'modules')) {
+ // Only check the modules, if the $modules property was not inherited.
+ $rp = new \ReflectionProperty($class, 'modules');
+ if ($rp->class == $class) {
+ foreach (array_intersect(array('node', 'comment'), $class::$modules) as $module) {
+ $this->installEntitySchema($module);
+ }
+ if (in_array('forum', $class::$modules, TRUE)) {
+ // Forum module is particular about the order that dependencies are
+ // enabled in. The comment, node and taxonomy config and the
+ // taxonomy_term schema need to be installed before the forum config
+ // which in turn needs to be installed before field config.
+ $this->installConfig(['comment', 'node', 'taxonomy']);
+ $this->installEntitySchema('taxonomy_term');
+ $this->installConfig(['forum']);
+ }
+ }
+ }
+ $class = get_parent_class($class);
+ }
+
+ $this->installConfig(array('field'));
+ }
+
+ /**
+ * Creates a user.
+ *
+ * @param array $values
+ * (optional) The values used to create the entity.
+ * @param array $permissions
+ * (optional) Array of permission names to assign to user.
+ *
+ * @return \Drupal\user\Entity\User
+ * The created user entity.
+ */
+ protected function createUser($values = array(), $permissions = array()) {
+ if ($permissions) {
+ // Create a new role and apply permissions to it.
+ $role = Role::create(array(
+ 'id' => strtolower($this->randomMachineName(8)),
+ 'label' => $this->randomMachineName(8),
+ ));
+ $role->save();
+ user_role_grant_permissions($role->id(), $permissions);
+ $values['roles'][] = $role->id();
+ }
+
+ $account = User::create($values + array(
+ 'name' => $this->randomMachineName(),
+ 'status' => 1,
+ ));
+ $account->enforceIsNew();
+ $account->save();
+ return $account;
+ }
+
+ /**
+ * Reloads the given entity from the storage and returns it.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ * The entity to be reloaded.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface
+ * The reloaded entity.
+ */
+ protected function reloadEntity(EntityInterface $entity) {
+ $controller = $this->entityManager->getStorage($entity->getEntityTypeId());
+ $controller->resetCache(array($entity->id()));
+ return $controller->load($entity->id());
+ }
+
+ /**
+ * Returns the entity_test hook invocation info.
+ *
+ * @return array
+ * An associative array of arbitrary hook data keyed by hook name.
+ */
+ protected function getHooksInfo() {
+ $key = 'entity_test.hooks';
+ $hooks = $this->state->get($key);
+ $this->state->set($key, array());
+ return $hooks;
+ }
+
+ /**
+ * Installs a module and refreshes services.
+ *
+ * @param string $module
+ * The module to install.
+ */
+ protected function installModule($module) {
+ $this->enableModules(array($module));
+ $this->refreshServices();
+ }
+
+ /**
+ * Uninstalls a module and refreshes services.
+ *
+ * @param string $module
+ * The module to uninstall.
+ */
+ protected function uninstallModule($module) {
+ $this->disableModules(array($module));
+ $this->refreshServices();
+ }
+
+ /**
+ * Refresh services.
+ */
+ protected function refreshServices() {
+ $this->container = \Drupal::getContainer();
+ $this->entityManager = $this->container->get('entity.manager');
+ $this->state = $this->container->get('state');
+ }
+
+ /**
+ * Generates a random ID avoiding collisions.
+ *
+ * @param bool $string
+ * (optional) Whether the id should have string type. Defaults to FALSE.
+ *
+ * @return int|string
+ * The entity identifier.
+ */
+ protected function generateRandomEntityId($string = FALSE) {
+ srand(time());
+ do {
+ // 0x7FFFFFFF is the maximum allowed value for integers that works for all
+ // Drupal supported databases and is known to work for other databases
+ // like SQL Server 2014 and Oracle 10 too.
+ $id = $string ? $this->randomMachineName() : mt_rand(1, 0x7FFFFFFF);
+ }
+ while (isset($this->generatedIds[$id]));
+ $this->generatedIds[$id] = $id;
+ return $id;
+ }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Test/AssertMailTraitTest.php b/core/tests/Drupal/KernelTests/Core/Test/AssertMailTraitTest.php
new file mode 100644
index 000000000..7af7718d6
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Test/AssertMailTraitTest.php
@@ -0,0 +1,96 @@
+randomString(64);
+ $body = $this->randomString(128);
+ $message = [
+ 'id' => 'drupal_mail_test',
+ 'headers' => ['Content-type' => 'text/html'],
+ 'subject' => $subject,
+ 'to' => 'foobar@example.com',
+ 'body' => $body,
+ ];
+
+ // Before we send the email, \Drupal\Core\Test\AssertMailTrait::getMails()
+ // should return an empty array.
+ $captured_emails = $this->getMails();
+ $this->assertCount(0, $captured_emails, 'The captured emails queue is empty.');
+
+ // Send the email.
+ $mail_service->getInstance(['module' => 'simpletest', 'key' => 'drupal_mail_test'])->mail($message);
+
+ // Ensure that there is one email in the captured emails array.
+ $captured_emails = $this->getMails();
+ $this->assertEquals(count($captured_emails), 1, 'One email was captured.');
+
+ // Assert that the email was sent by iterating over the message properties
+ // and ensuring that they are captured intact.
+ foreach ($message as $field => $value) {
+ $this->assertMail($field, $value, "The email was sent and the value for property $field is intact.");
+ }
+
+ // Send additional emails so more than one email is captured.
+ for ($index = 0; $index < 5; $index++) {
+ $message = [
+ 'id' => 'drupal_mail_test_' . $index,
+ 'headers' => ['Content-type' => 'text/html'],
+ 'subject' => $this->randomString(64),
+ 'to' => $this->randomMachineName(32) . '@example.com',
+ 'body' => $this->randomString(512),
+ ];
+ $mail_service->getInstance(['module' => 'drupal_mail_test', 'key' => $index])->mail($message);
+ }
+
+ // There should now be 6 emails captured.
+ $captured_emails = $this->getMails();
+ $this->assertCount(6, $captured_emails, 'All emails were captured.');
+
+ // Test different ways of getting filtered emails via
+ // \Drupal\Core\Test\AssertMailTrait::getMails().
+ $captured_emails = $this->getMails(['id' => 'drupal_mail_test']);
+ $this->assertCount(1, $captured_emails, 'Only one email is returned when filtering by id.');
+ $captured_emails = $this->getMails(['id' => 'drupal_mail_test', 'subject' => $subject]);
+ $this->assertCount(1, $captured_emails, 'Only one email is returned when filtering by id and subject.');
+ $captured_emails = $this->getMails([
+ 'id' => 'drupal_mail_test',
+ 'subject' => $subject,
+ 'from' => 'this_was_not_used@example.com',
+ ]);
+ $this->assertCount(0, $captured_emails, 'No emails are returned when querying with an unused from address.');
+
+ // Send the last email again, so we can confirm that
+ // \Drupal\Core\Test\AssertMailTrait::getMails() filters correctly returns
+ // all emails with a given property/value.
+ $mail_service->getInstance(['module' => 'drupal_mail_test', 'key' => $index])->mail($message);
+ $captured_emails = $this->getMails(['id' => 'drupal_mail_test_4']);
+ $this->assertCount(2, $captured_emails, 'All emails with the same id are returned when filtering by id.');
+ }
+
+}
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index f1a141425..e60abc1a7 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -370,6 +370,12 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
'class' => '\Drupal\Component\PhpStorage\FileStorage',
];
new Settings($settings);
+
+ // Manually configure the test mail collector implementation to prevent
+ // tests from sending out emails and collect them in state instead.
+ // While this should be enforced via settings.php prior to installation,
+ // some tests expect to be able to test mail system implementations.
+ $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
}
/**
@@ -1140,4 +1146,14 @@ abstract class KernelTestBase extends \PHPUnit_Framework_TestCase implements Ser
return [];
}
+ /**
+ * {@inheritdoc}
+ */
+ public static function assertEquals($expected, $actual, $message = '', $delta = 0.0, $maxDepth = 10, $canonicalize = false, $ignoreCase = false) {
+ $expected = static::castSafeStrings($expected);
+ $actual = static::castSafeStrings($actual);
+ parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
+ }
+
+
}
diff --git a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
index a120e2a06..7964298c9 100644
--- a/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/NestedArrayTest.php
@@ -259,4 +259,30 @@ class NestedArrayTest extends UnitTestCase {
$this->assertSame($expected, $actual, 'drupal_array_merge_deep() ignores numeric key order when merging.');
}
+ /**
+ * @covers ::filter
+ * @dataProvider providerTestFilter
+ */
+ public function testFilter($array, $callable, $expected) {
+ $this->assertEquals($expected, NestedArray::filter($array, $callable));
+ }
+
+ public function providerTestFilter() {
+ $data = [];
+ $data['1d-array'] = [
+ [0, 1, '', TRUE], NULL, [1 => 1, 3 => TRUE]
+ ];
+ $data['1d-array-callable'] = [
+ [0, 1, '', TRUE], function ($element) { return $element === ''; }, [2 => '']
+ ];
+ $data['2d-array'] = [
+ [[0, 1, '', TRUE], [0, 1, 2, 3]], NULL, [0 => [1 => 1, 3 => TRUE], 1 => [1 => 1, 2 => 2, 3 => 3]],
+ ];
+ $data['2d-array-callable'] = [
+ [[0, 1, '', TRUE], [0, 1, 2, 3]], function ($element) { return is_array($element) || $element === 3; }, [0 => [], 1 => [3 => 3]],
+ ];
+
+ return $data;
+ }
+
}
diff --git a/core/themes/classy/templates/user/username.html.twig b/core/themes/classy/templates/user/username.html.twig
index 3ed5c95cb..5a6c3a552 100644
--- a/core/themes/classy/templates/user/username.html.twig
+++ b/core/themes/classy/templates/user/username.html.twig
@@ -9,8 +9,8 @@
* - extra: Additional text to append to the user's name, sanitized.
* - link_path: The path or URL of the user's profile page, home page,
* or other desired page to link to for more information about the user.
- * - link_options: Options to pass to the url() function's $options parameter if
- * linking the user's name to the user's page.
+ * - link_options: Options to set on the \Drupal\Core\Url object if linking the
+ * user's name to the user's page.
* - attributes: HTML attributes for the containing element.
*
* @see template_preprocess_username()
diff --git a/core/themes/stable/templates/user/username.html.twig b/core/themes/stable/templates/user/username.html.twig
index 828f6c6e6..480225f15 100644
--- a/core/themes/stable/templates/user/username.html.twig
+++ b/core/themes/stable/templates/user/username.html.twig
@@ -9,8 +9,8 @@
* - extra: Additional text to append to the user's name, sanitized.
* - link_path: The path or URL of the user's profile page, home page,
* or other desired page to link to for more information about the user.
- * - link_options: Options to pass to the url() function's $options parameter if
- * linking the user's name to the user's page.
+ * - link_options: Options to set on the \Drupal\Core\Url object if linking the
+ * user's name to the user's page.
* - attributes: HTML attributes for the containing element.
*
* @see template_preprocess_username()
diff --git a/sites/default/default.settings.php b/sites/default/default.settings.php
index 58c82cffc..910bec4b9 100644
--- a/sites/default/default.settings.php
+++ b/sites/default/default.settings.php
@@ -713,9 +713,7 @@ $settings['container_yamls'][] = __DIR__ . '/services.yml';
* a local development environment, to insure that
* the site settings remain consistent.
*/
-if (file_exists(__DIR__ . '/settings.pantheon.php')) {
- include __DIR__ . "/settings.pantheon.php";
-}
+include __DIR__ . "/settings.pantheon.php";
/**
* Load local development override configuration, if available.
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 38b3a3dc9..28ad2e498 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -3685,57 +3685,6 @@
"templating"
]
},
- {
- "name": "wikimedia/composer-merge-plugin",
- "version": "v1.3.0",
- "version_normalized": "1.3.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/wikimedia/composer-merge-plugin.git",
- "reference": "bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9",
- "reference": "bfed1f8d4eb97e9ba80eee57ea46229d7e5364d9",
- "shasum": ""
- },
- "require": {
- "composer-plugin-api": "^1.0",
- "php": ">=5.3.2"
- },
- "require-dev": {
- "composer/composer": "1.0.*@dev",
- "jakub-onderka/php-parallel-lint": "~0.8",
- "phpunit/phpunit": "~4.8|~5.0",
- "squizlabs/php_codesniffer": "~2.1.0"
- },
- "time": "2015-11-06 20:31:16",
- "type": "composer-plugin",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3.x-dev"
- },
- "class": "Wikimedia\\Composer\\MergePlugin"
- },
- "installation-source": "dist",
- "autoload": {
- "psr-4": {
- "Wikimedia\\Composer\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bryan Davis",
- "email": "bd808@wikimedia.org"
- }
- ],
- "description": "Composer plugin to merge multiple composer.json files"
- },
{
"name": "jcalderonzumba/mink-phantomjs-driver",
"version": "v0.3.1",
@@ -3857,5 +3806,56 @@
"headless",
"phantomjs"
]
+ },
+ {
+ "name": "wikimedia/composer-merge-plugin",
+ "version": "v1.3.1",
+ "version_normalized": "1.3.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/composer-merge-plugin.git",
+ "reference": "0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4",
+ "reference": "0bdf8543d445ee067c9ba7d5d4a5dde70b9785f4",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "^1.0",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "composer/composer": "1.0.*@dev",
+ "jakub-onderka/php-parallel-lint": "~0.8",
+ "phpunit/phpunit": "~4.8|~5.0",
+ "squizlabs/php_codesniffer": "~2.1.0"
+ },
+ "time": "2016-03-08 17:11:37",
+ "type": "composer-plugin",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.3.x-dev"
+ },
+ "class": "Wikimedia\\Composer\\MergePlugin"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Wikimedia\\Composer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Bryan Davis",
+ "email": "bd808@wikimedia.org"
+ }
+ ],
+ "description": "Composer plugin to merge multiple composer.json files"
}
]
diff --git a/vendor/wikimedia/composer-merge-plugin/.arcconfig b/vendor/wikimedia/composer-merge-plugin/.arcconfig
deleted file mode 100644
index 3dfae3d90..000000000
--- a/vendor/wikimedia/composer-merge-plugin/.arcconfig
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "phabricator.uri" : "https://phabricator.wikimedia.org/",
- "repository.callsign" : "GCMP",
- "history.immutable" : false,
- "unit.engine": "PhpunitTestEngine"
-}
diff --git a/vendor/wikimedia/composer-merge-plugin/.arclint b/vendor/wikimedia/composer-merge-plugin/.arclint
deleted file mode 100644
index da42f29a2..000000000
--- a/vendor/wikimedia/composer-merge-plugin/.arclint
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "exclude": "(^vendor/)",
- "linters": {
- "php": {
- "type": "php",
- "include": "(\\.php$)"
- },
- "json": {
- "type": "json",
- "include": "(\\.json$)"
- }
- }
-}
diff --git a/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php b/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
index ebecdff5e..b32131ffd 100644
--- a/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
+++ b/vendor/wikimedia/composer-merge-plugin/src/Merge/ExtraPackage.php
@@ -120,6 +120,7 @@ class ExtraPackage
}
/**
+ * @param string $json
* @return CompletePackage
*/
protected function loadPackage($json)
@@ -350,12 +351,14 @@ class ExtraPackage
$links = $this->package->{$getter}();
if (!empty($links)) {
$unwrapped = self::unwrapIfNeeded($root, $setter);
+ // @codeCoverageIgnoreStart
if ($root !== $unwrapped) {
$this->logger->warning(
'This Composer version does not support ' .
"'{$type}' merging for aliased packages."
);
}
+ // @codeCoverageIgnoreEnd
$unwrapped->{$setter}(array_merge(
$root->{$getter}(),
$this->replaceSelfVersionDependencies($type, $links, $root)
@@ -437,9 +440,24 @@ class ExtraPackage
$prettyVersion = $root->getPrettyVersion();
$vp = new VersionParser();
+ $method = 'get' . ucfirst($linkType['method']);
+ $packages = $root->$method();
+
return array_map(
- function ($link) use ($linkType, $version, $prettyVersion, $vp) {
+ function ($link) use ($linkType, $version, $prettyVersion, $vp, $packages) {
if ('self.version' === $link->getPrettyConstraint()) {
+ if (isset($packages[$link->getSource()])) {
+ /** @var Link $package */
+ $package = $packages[$link->getSource()];
+ return new Link(
+ $link->getSource(),
+ $link->getTarget(),
+ $vp->parseConstraints($package->getConstraint()->getPrettyString()),
+ $linkType['description'],
+ $package->getPrettyConstraint()
+ );
+ }
+
return new Link(
$link->getSource(),
$link->getTarget(),
@@ -475,12 +493,14 @@ class ExtraPackage
RootPackageInterface $root,
$method = 'setExtra'
) {
+ // @codeCoverageIgnoreStart
if ($root instanceof RootAliasPackage &&
!method_exists($root, $method)
) {
// Unwrap and return the aliased RootPackage.
$root = $root->getAliasOf();
}
+ // @codeCoverageIgnoreEnd
return $root;
}
}