From b11a755ba851c9c89001f22c319b0f61e7ce4e91 Mon Sep 17 00:00:00 2001 From: Pantheon Automation Date: Thu, 7 Apr 2016 11:19:57 -0700 Subject: [PATCH] Update to Drupal 8.0.6. For more information, see https://www.drupal.org/drupal-8.0.6-release-notes --- composer.lock | 10 +- core/MAINTAINERS.txt | 2 +- core/config/schema/core.data_types.schema.yml | 4 +- core/core.api.php | 41 +++- core/includes/bootstrap.inc | 8 +- core/includes/common.inc | 2 +- core/includes/entity.inc | 13 +- core/includes/form.inc | 4 +- core/lib/Drupal.php | 2 +- .../Drupal/Component/Utility/NestedArray.php | 22 ++ core/lib/Drupal/Component/Uuid/Php.php | 47 ++-- core/lib/Drupal/Core/Composer/Composer.php | 7 +- .../Drupal/Core/Config/ConfigInstaller.php | 29 ++- .../Drupal/Core/Entity/EntityHandlerBase.php | 2 + .../Core/Entity/EntityHandlerInterface.php | 2 + core/lib/Drupal/Core/Entity/entity.api.php | 14 +- .../Drupal/Core/Form/ConfigFormBaseTrait.php | 9 +- core/lib/Drupal/Core/Form/form.api.php | 4 +- .../lib/Drupal/Core/Language/language.api.php | 5 +- .../Core/PhpStorage/PhpStorageFactory.php | 11 +- .../Drupal/Core/Render/Element/HtmlTag.php | 2 +- .../Core/Render/Element/Tableselect.php | 1 + .../Core/Render/Element/VerticalTabs.php | 2 +- core/lib/Drupal/Core/Render/theme.api.php | 7 +- .../PluralTranslatableMarkup.php | 18 +- .../StringTranslationTrait.php | 4 +- .../TranslationInterface.php | 4 +- core/lib/Drupal/Core/Test/AssertMailTrait.php | 161 ++++++++++++++ .../TestHttpClientMiddleware.php | 26 ++- .../Core/Theme/ThemeNegotiatorInterface.php | 8 +- .../Core/TypedData/ComplexDataInterface.php | 8 + .../Drupal/Core/TypedData/ListInterface.php | 8 + core/misc/autocomplete.js | 5 +- core/modules/aggregator/src/FeedForm.php | 2 +- .../aggregator/src/FeedViewBuilder.php | 2 +- .../aggregator/src/ItemViewBuilder.php | 2 +- core/modules/block/js/block.js | 15 +- .../block/migration_templates/d6_block.yml | 2 +- .../block/migration_templates/d7_block.yml | 2 +- .../Plugin/migrate/process/BlockSettings.php | 10 +- .../src/Tests/Migrate/d6/MigrateBlockTest.php | 28 ++- .../src/Tests/Migrate/d7/MigrateBlockTest.php | 25 ++- .../block_content/src/BlockContentForm.php | 2 +- .../src/BlockContentViewBuilder.php | 2 +- core/modules/book/src/BookManager.php | 61 +++++- .../ckeditor/js/plugins/drupalimage/plugin.js | 2 +- .../CKEditorPluginConfigurableInterface.php | 4 +- core/modules/comment/src/CommentForm.php | 2 +- core/modules/comment/src/CommentStorage.php | 2 +- core/modules/comment/src/CommentTypeForm.php | 2 +- .../comment/src/CommentViewBuilder.php | 2 +- .../src/Tests/ConfigOtherModuleTest.php | 9 +- ...ther_module_test_optional_entity_unmet.yml | 11 + .../src/Tests/ConfigTranslationUiTest.php | 41 ++++ .../ContentTranslationLocalTasks.php | 13 +- .../ContentTranslationOperationsTest.php | 28 ++- .../Menu/ContentTranslationLocalTasksTest.php | 1 + .../src/EntityDisplayModeListBuilder.php | 6 +- .../src/EntityFormModeListBuilder.php | 2 +- .../src/ConfigurableLanguageManager.php | 2 +- .../src/Entity/ContentLanguageSettings.php | 1 + .../language/src/LanguageListBuilder.php | 2 +- .../src/Tests/LanguageLocaleListTest.php | 79 +++++++ .../src/Plugin/migrate/process/d6/CckLink.php | 4 + core/modules/locale/locale.batch.inc | 4 +- core/modules/locale/locale.bulk.inc | 16 +- .../src/Plugin/migrate/source/MenuLink.php | 2 +- .../src/Plugin/migrate/destination/Config.php | 20 +- .../src/Plugin/migrate/process/Get.php | 9 +- .../migrate/source/SourcePluginBase.php | 2 +- .../src/Plugin/migrate/source/SqlBase.php | 9 + .../migrate/tests/src/Unit/SqlBaseTest.php | 25 ++- .../tests/src/Unit/destination/ConfigTest.php | 65 +++++- .../tests/src/Unit/process/GetTest.php | 22 ++ .../schema/migrate_drupal.source.schema.yml | 39 ++++ .../Plugin/migrate/source/d6/i18nVariable.php | 105 +++++++++ .../migrate_drupal/tests/fixtures/drupal6.php | 32 +-- .../src/Unit/source/d6/i18nVariableTest.php | 66 ++++++ core/modules/node/node.admin.inc | 4 +- core/modules/node/node.module | 15 +- core/modules/node/src/NodeForm.php | 2 +- .../node/src/NodeGrantDatabaseStorage.php | 2 +- core/modules/node/src/NodeStorage.php | 2 +- core/modules/node/src/NodeTypeForm.php | 2 +- core/modules/node/src/NodeViewBuilder.php | 2 +- .../node/src/Plugin/Search/NodeSearch.php | 21 +- .../node/src/Plugin/views/argument/Type.php | 2 +- .../src/Tests/Migrate/d6/MigrateNodeTest.php | 24 +++ .../node/src/Tests/NodeQueryAlterTest.php | 18 ++ .../src/Tests/NodeTypeTranslationTest.php | 24 ++- .../Plugin/rest/resource/EntityResource.php | 4 +- core/modules/rest/src/Tests/RESTTestBase.php | 11 + .../src/Tests/SearchDateIntervalTest.php | 79 +++++++ .../search_date_query_alter.info.yml | 6 + .../search_date_query_alter.module | 18 ++ core/modules/shortcut/src/ShortcutForm.php | 2 +- core/modules/shortcut/src/ShortcutSetForm.php | 2 +- core/modules/simpletest/simpletest.module | 11 +- .../simpletest/simpletest.services.yml | 2 +- .../simpletest/src/AssertHelperTrait.php | 2 +- .../simpletest/src/BrowserTestBase.php | 6 + .../modules/simpletest/src/KernelTestBase.php | 6 + core/modules/simpletest/src/TestDiscovery.php | 58 +++-- core/modules/simpletest/src/WebTestBase.php | 150 +------------ .../src/Functional/BrowserTestBaseTest.php | 5 + .../tests/src/Unit/TestInfoParsingTest.php | 128 +++++++++++ .../EventSubscriber/AdminRouteSubscriber.php | 7 +- .../src/Tests/Entity/EntityUnitTestBase.php | 3 + .../Tests/Form/ElementsTableSelectTest.php | 21 ++ core/modules/system/system.module | 4 +- .../tests/modules/form_test/form_test.module | 7 + .../src/Form/FormTestTableSelectFormBase.php | 6 + .../test_page_test/src/Controller/Test.php | 7 + .../test_page_test/test_page_test.routing.yml | 8 + .../theme_test/theme_test.services.yml | 2 +- .../PhpStorage/PhpStorageFactoryTest.php | 15 +- .../d6_vocabulary_field.yml | 3 +- .../taxonomy/src/Form/OverviewTerms.php | 2 +- .../Plugin/migrate/source/d6/Vocabulary.php | 2 + core/modules/taxonomy/src/TermForm.php | 2 +- core/modules/taxonomy/src/TermViewBuilder.php | 2 +- .../d6/MigrateVocabularyFieldInstanceTest.php | 1 + .../Migrate/d6/MigrateVocabularyFieldTest.php | 2 + .../taxonomy/src/VocabularyStorage.php | 2 +- .../src/Unit/Migrate/d6/VocabularyTest.php | 3 + core/modules/user/src/ProfileForm.php | 2 +- core/modules/user/src/RegisterForm.php | 2 +- .../d6/MigrateUserProfileValuesTest.php | 5 + .../modules/user/templates/username.html.twig | 4 +- core/modules/views/src/EntityViewsData.php | 4 +- .../views/src/Plugin/views/HandlerBase.php | 4 +- .../Plugin/views/argument/NumericArgument.php | 4 +- .../Plugin/views/display/EntityReference.php | 8 +- .../Plugin/views/field/FieldPluginBase.php | 3 +- .../Plugin/views/filter/FilterPluginBase.php | 5 - .../src/Plugin/views/sort/SortPluginBase.php | 2 - .../views/src/Tests/Handler/HandlerTest.php | 31 +++ .../Plugin/DisplayEntityReferenceTest.php | 67 ++++++ .../src/Tests/Plugin/StyleSummaryTest.php | 70 +++++- .../test_views/views.view.test_summary.yml | 1 + core/modules/views/views.theme.inc | 43 +++- core/modules/views_ui/js/ajax.js | 32 +++ .../views_ui/src/Ajax/SetFormCommand.php | 46 ++++ .../views_ui/src/Form/Ajax/ConfigHandler.php | 3 - .../src/Form/Ajax/RearrangeFilter.php | 1 - .../views_ui/src/Form/Ajax/ViewsFormBase.php | 9 +- core/modules/views_ui/src/ViewUI.php | 11 - core/scripts/run-tests.sh | 49 ++++- .../Core/Entity/EntityKernelTestBase.php | 203 ++++++++++++++++++ .../Core/Test/AssertMailTraitTest.php | 96 +++++++++ .../Drupal/KernelTests/KernelTestBase.php | 16 ++ .../Component/Utility/NestedArrayTest.php | 26 +++ .../classy/templates/user/username.html.twig | 4 +- .../stable/templates/user/username.html.twig | 4 +- sites/default/default.settings.php | 4 +- vendor/composer/installed.json | 102 ++++----- .../composer-merge-plugin/.arcconfig | 6 - .../wikimedia/composer-merge-plugin/.arclint | 13 -- .../src/Merge/ExtraPackage.php | 22 +- 159 files changed, 2340 insertions(+), 543 deletions(-) create mode 100644 core/lib/Drupal/Core/Test/AssertMailTrait.php create mode 100644 core/modules/config/tests/config_other_module_config_test/config/optional/config_test.dynamic.other_module_test_optional_entity_unmet.yml create mode 100644 core/modules/language/src/Tests/LanguageLocaleListTest.php create mode 100644 core/modules/migrate_drupal/src/Plugin/migrate/source/d6/i18nVariable.php create mode 100644 core/modules/migrate_drupal/tests/src/Unit/source/d6/i18nVariableTest.php create mode 100644 core/modules/search/src/Tests/SearchDateIntervalTest.php create mode 100644 core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.info.yml create mode 100644 core/modules/search/tests/modules/search_date_query_alter/search_date_query_alter.module create mode 100644 core/modules/views_ui/src/Ajax/SetFormCommand.php create mode 100644 core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php create mode 100644 core/tests/Drupal/KernelTests/Core/Test/AssertMailTraitTest.php delete mode 100644 vendor/wikimedia/composer-merge-plugin/.arcconfig delete mode 100644 vendor/wikimedia/composer-merge-plugin/.arclint 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; } }