diff --git a/core/INSTALL.txt b/core/INSTALL.txt index 8c5ee8723..c83976126 100644 --- a/core/INSTALL.txt +++ b/core/INSTALL.txt @@ -90,6 +90,19 @@ INSTALLATION mv drupal-x.y.z/* drupal-x.y.z/.htaccess drupal-x.y.z/.csslintrc drupal-x.y.z/.editorconfig drupal-x.y.z/.eslintignore drupal-x.y.z/.eslintrc drupal-x.y.z/.gitattributes /path/to/your/installation + You can also download the latest version of Drupal using Git on the command + line and set up a repository by following the instructions at + https://www.drupal.org/project/drupal/git-instructions for "Setting up + repository for the first time". + + Once you have downloaded Drupal successfully, you may install Composer + globally using the instructions at + https://getcomposer.org/doc/00-intro.md#globally + + With Composer installed, run the following command from the Drupal web root: + + composer install + 2. Create the Drupal database. Because Drupal stores all site information in a database, the Drupal diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 60aefd152..e884c10c5 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -23,9 +23,10 @@ Drupal 8 (Release Manager) - Alex Pott 'alexpott' https://www.drupal.org/u/alexpott (Framework Manager) - -Provisional membership: - Scott Reeves 'Cottser' https://www.drupal.org/u/cottser + (Framework Manager - Frontend) + +Provisional membership: None at this time. Drupal 7 - Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx @@ -472,11 +473,13 @@ Views module - Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett - Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip - Jess Myrbo 'xjm' https://www.drupal.org/u/xjm +- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude Views UI module - Daniel Wehner 'dawehner' https://www.drupal.org/u/dawehner - Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett - Damian Lee 'damiankloip' https://www.drupal.org/u/damiankloip +- Len Swaneveld 'Lendude' https://www.drupal.org/u/lendude Theme maintainers @@ -523,6 +526,9 @@ Multi-lingual Web services - Larry Garfield 'Crell' https://www.drupal.org/u/crell +Workflow Initiative +- Dick Olsson 'dixon_' https://www.drupal.org/u/dixon_ + Provisional membership: None at this time. diff --git a/core/core.api.php b/core/core.api.php index 4b2fe62fd..7b90ae21f 100644 --- a/core/core.api.php +++ b/core/core.api.php @@ -1894,21 +1894,32 @@ function hook_cron() { // Short-running operation example, not using a queue: // Delete all expired records since the last cron run. - $expires = \Drupal::state()->get('mymodule.cron_last_run', REQUEST_TIME); - db_delete('mymodule_table') + $expires = \Drupal::state()->get('mymodule.last_check', 0); + \Drupal::database()->delete('mymodule_table') ->condition('expires', $expires, '>=') ->execute(); - \Drupal::state()->set('mymodule.cron_last_run', REQUEST_TIME); + \Drupal::state()->set('mymodule.last_check', REQUEST_TIME); // Long-running operation example, leveraging a queue: - // Fetch feeds from other sites. - $result = db_query('SELECT * FROM {aggregator_feed} WHERE checked + refresh < :time AND refresh <> :never', array( - ':time' => REQUEST_TIME, - ':never' => AGGREGATOR_CLEAR_NEVER, - )); + // Queue news feeds for updates once their refresh interval has elapsed. $queue = \Drupal::queue('aggregator_feeds'); - foreach ($result as $feed) { - $queue->createItem($feed); + $ids = \Drupal::entityManager()->getStorage('aggregator_feed')->getFeedIdsToRefresh(); + foreach (Feed::loadMultiple($ids) as $feed) { + if ($queue->createItem($feed)) { + // Add timestamp to avoid queueing item more than once. + $feed->setQueuedTime(REQUEST_TIME); + $feed->save(); + } + } + $ids = \Drupal::entityQuery('aggregator_feed') + ->condition('queued', REQUEST_TIME - (3600 * 6), '<') + ->execute(); + if ($ids) { + $feeds = Feed::loadMultiple($ids); + foreach ($feeds as $feed) { + $feed->setQueuedTime(0); + $feed->save(); + } } } @@ -2036,7 +2047,7 @@ function hook_mail_alter(&$message) { * An array of parameters supplied by the caller of * MailManagerInterface->mail(). * - * @see \Drupal\Core\Mail\MailManagerInterface->mail() + * @see \Drupal\Core\Mail\MailManagerInterface::mail() */ function hook_mail($key, &$message, $params) { $account = $params['account']; diff --git a/core/includes/file.inc b/core/includes/file.inc index 017a3d650..a58e6dcd7 100644 --- a/core/includes/file.inc +++ b/core/includes/file.inc @@ -1220,7 +1220,9 @@ function file_directory_os_temp() { foreach ($directories as $directory) { if (is_dir($directory) && is_writable($directory)) { - return $directory; + // Both sys_get_temp_dir() and ini_get('upload_tmp_dir') can return paths + // with a trailing directory separator. + return rtrim($directory, DIRECTORY_SEPARATOR); } } return FALSE; diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index d75ed9f40..11bfbb3e6 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -369,7 +369,9 @@ function install_begin_request($class_loader, &$install_state) { $install_state['config_verified'] = FALSE; } $install_state['database_verified'] = install_verify_database_settings($site_path); - $install_state['settings_verified'] = $install_state['config_verified'] && $install_state['database_verified']; + // A valid settings.php has database settings and a hash_salt value. Other + // settings like config_directories will be checked by system_requirements(). + $install_state['settings_verified'] = $install_state['database_verified'] && (bool) Settings::get('hash_salt', FALSE); // Install factory tables only after checking the database. if ($install_state['database_verified'] && $install_state['database_ready']) { diff --git a/core/includes/update.inc b/core/includes/update.inc index 2c020db6b..ea126f864 100644 --- a/core/includes/update.inc +++ b/core/includes/update.inc @@ -596,7 +596,7 @@ function update_already_performed($module, $number) { * An array of return values obtained by merging the results of the * hook_update_dependencies() implementations in all installed modules. * - * @see \Drupal::moduleHandler()->invokeAll() + * @see \Drupal\Core\Extension\ModuleHandlerInterface::invokeAll() * @see hook_update_dependencies() */ function update_retrieve_dependencies() { diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index 0e82afad8..6539eb6d9 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -81,7 +81,7 @@ class Drupal { /** * The current system version. */ - const VERSION = '8.1.8'; + const VERSION = '8.1.9'; /** * Core API compatibility. diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php index 4cd660b4b..972a6e4cf 100644 --- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php +++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php @@ -23,7 +23,7 @@ class Schema extends DatabaseSchema { * This is collected by DatabaseConnection_pgsql->queryTableInformation(), * by introspecting the database. * - * @see DatabaseConnection_pgsql->queryTableInformation() + * @see \Drupal\Core\Database\Driver\pgsql\Schema::queryTableInformation() * @var array */ protected $tableInformation = array(); diff --git a/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php index 07bb83638..266022be8 100644 --- a/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php +++ b/core/lib/Drupal/Core/Datetime/FormattedDateDiff.php @@ -51,10 +51,23 @@ class FormattedDateDiff implements RenderableInterface, CacheableDependencyInter } /** + * {@inheritdoc} + */ + public function getCacheMaxAge() { + return $this->maxAge; + } + + /** + * The maximum age for which this object may be cached. + * * @return int + * The maximum time in seconds that this object may be cached. + * + * @deprecated in Drupal 8.1.9 and will be removed before Drupal 9.0.0. Use + * \Drupal\Core\Datetime\FormattedDateDiff::getCacheMaxAge() instead. */ public function getMaxAge() { - return $this->maxAge; + return $this->getCacheMaxAge(); } /** diff --git a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php index 23fd0b50a..51adf91b7 100644 --- a/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php +++ b/core/lib/Drupal/Core/Entity/Element/EntityAutocomplete.php @@ -297,7 +297,7 @@ class EntityAutocomplete extends Textfield { $multiples[] = $name . ' (' . $id . ')'; } $params['@id'] = $id; - $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)))); + $form_state->setError($element, t('Multiple entities match this reference; "%multiple". Specify the one you want by appending the id in parentheses, like "@value (@id)".', array('%multiple' => implode('", "', $multiples)) + $params)); } else { // Take the one and only matching entity. diff --git a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php index 0de9b0141..bc8bd5973 100644 --- a/core/lib/Drupal/Core/Entity/EntityAccessCheck.php +++ b/core/lib/Drupal/Core/Entity/EntityAccessCheck.php @@ -17,13 +17,26 @@ class EntityAccessCheck implements AccessInterface { * Checks access to the entity operation on the given route. * * The value of the '_entity_access' key must be in the pattern - * 'entity_type.operation.' The entity type must match the {entity_type} - * parameter in the route pattern. This will check a node for 'update' access: + * 'entity_slug_name.operation.' For example, this will check a node for + * 'update' access: * @code * pattern: '/foo/{node}/bar' * requirements: * _entity_access: 'node.update' * @endcode + * And this will check a dynamic entity type: + * @code + * example.route: + * path: foo/{entity_type}/{example} + * requirements: + * _entity_access: example.update + * options: + * parameters: + * example: + * type: entity:{entity_type} + * @endcode + * @see \Drupal\Core\ParamConverter\EntityConverter + * * Available operations are 'view', 'update', 'create', and 'delete'. * * @param \Symfony\Component\Routing\Route $route diff --git a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php index 17b266ffc..630d17673 100644 --- a/core/lib/Drupal/Core/Entity/Query/QueryInterface.php +++ b/core/lib/Drupal/Core/Entity/Query/QueryInterface.php @@ -31,7 +31,6 @@ interface QueryInterface extends AlterableInterface { * ->condition('greetings', 'merhaba', '=', 'tr') * ->condition('greetings.value', 'siema', '=', 'pl') * ->execute(); - * $entity_ids = $query->execute(); * @endcode * * @param $field diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php index e115bd211..b2be1c0c4 100644 --- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php +++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php @@ -1543,6 +1543,7 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $item_query = $this->database->select($table_name, 't', array('fetch' => \PDO::FETCH_ASSOC)) ->fields('t') ->condition('entity_id', $row['entity_id']) + ->condition('deleted', 1) ->orderBy('delta'); foreach ($item_query->execute() as $item_row) { @@ -1581,10 +1582,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id(); $this->database->delete($table_name) ->condition('revision_id', $revision_id) + ->condition('deleted', 1) ->execute(); if ($this->entityType->isRevisionable()) { $this->database->delete($revision_name) ->condition('revision_id', $revision_id) + ->condition('deleted', 1) ->execute(); } } @@ -1684,6 +1687,12 @@ class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEnt * Whether the field has been already deleted. */ protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) { + // Configurable fields are marked for deletion. + if ($storage_definition instanceOf FieldStorageConfigInterface) { + return $storage_definition->isDeleted(); + } + // For non configurable fields check whether they are still in the last + // installed schema repository. return !array_key_exists($storage_definition->getName(), $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityTypeId)); } diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php index c61c2368e..36873845c 100644 --- a/core/lib/Drupal/Core/Entity/entity.api.php +++ b/core/lib/Drupal/Core/Entity/entity.api.php @@ -1252,21 +1252,6 @@ function hook_ENTITY_TYPE_revision_delete(Drupal\Core\Entity\EntityInterface $en } } -/** - * Alter or execute an Drupal\Core\Entity\Query\EntityQueryInterface. - * - * @param \Drupal\Core\Entity\Query\QueryInterface $query - * Note the $query->altered attribute which is TRUE in case the query has - * already been altered once. This happens with cloned queries. - * If there is a pager, then such a cloned query will be executed to count - * all elements. This query can be detected by checking for - * ($query->pager && $query->count), allowing the driver to return 0 from - * the count query and disable the pager. - */ -function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query) { - // @todo: code example. -} - /** * Act on entities being assembled before rendering. * @@ -1860,7 +1845,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * * @param string $operation * The operation to be performed. See - * \Drupal\Core\Access\AccessibleInterface::access() for possible values. + * \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess() + * for possible values. * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition * The field definition. * @param \Drupal\Core\Session\AccountInterface $account @@ -1871,6 +1857,8 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent * * @return \Drupal\Core\Access\AccessResultInterface * The access result. + * + * @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface::fieldAccess() */ function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) { if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') { diff --git a/core/lib/Drupal/Core/File/file.api.php b/core/lib/Drupal/Core/File/file.api.php index 43913fbd0..c26ac2be3 100644 --- a/core/lib/Drupal/Core/File/file.api.php +++ b/core/lib/Drupal/Core/File/file.api.php @@ -157,12 +157,6 @@ function hook_archiver_info_alter(&$info) { * will always be passed the full path to the root of the site that should * be used to restrict where file transfer operations can occur (the $jail) * and an array of settings values returned by the settings form. - * - 'file': Required. The include file containing the FileTransfer class. - * This should be a separate .inc file, not just the .module file, so that - * the minimum possible code is loaded when authorize.php is running. - * - 'file path': Optional. The directory (relative to the Drupal root) - * where the include file lives. If not defined, defaults to the base - * directory of the module implementing the hook. * - 'weight': Optional. Integer weight used for sorting connection types on * the authorize.php form. * diff --git a/core/lib/Drupal/Core/Form/ConfigFormBase.php b/core/lib/Drupal/Core/Form/ConfigFormBase.php index c2f83fadb..fb508f854 100644 --- a/core/lib/Drupal/Core/Form/ConfigFormBase.php +++ b/core/lib/Drupal/Core/Form/ConfigFormBase.php @@ -41,7 +41,7 @@ abstract class ConfigFormBase extends FormBase { '#button_type' => 'primary', ); - // By default, render the form using theme_system_config_form(). + // By default, render the form using system-config-form.html.twig. $form['#theme'] = 'system_config_form'; return $form; diff --git a/core/lib/Drupal/Core/Utility/ProjectInfo.php b/core/lib/Drupal/Core/Utility/ProjectInfo.php index 1eed1fe9a..24043d3b4 100644 --- a/core/lib/Drupal/Core/Utility/ProjectInfo.php +++ b/core/lib/Drupal/Core/Utility/ProjectInfo.php @@ -172,7 +172,7 @@ class ProjectInfo { * @return * Array of .info.yml file data we need for the update manager. * - * @see \Drupal\Core\Utility\ProjectInfo->processInfoList() + * @see \Drupal\Core\Utility\ProjectInfo::processInfoList() */ function filterProjectInfo($info, $additional_whitelist = array()) { $whitelist = array( diff --git a/core/misc/dialog/dialog.ajax.js b/core/misc/dialog/dialog.ajax.js index 16f72f00a..3f1b0c2ef 100644 --- a/core/misc/dialog/dialog.ajax.js +++ b/core/misc/dialog/dialog.ajax.js @@ -62,7 +62,7 @@ */ prepareDialogButtons: function ($dialog) { var buttons = []; - var $buttons = $dialog.find('.form-actions input[type=submit]'); + var $buttons = $dialog.find('.form-actions input[type=submit], .form-actions a.button'); $buttons.each(function () { // Hidden form buttons need special attention. For browser consistency, // the button needs to be "visible" in order to have the enter key fire @@ -74,14 +74,22 @@ width: 0, height: 0, padding: 0, - border: 0 + border: 0, + overflow: 'hidden' }); buttons.push({ text: $originalButton.html() || $originalButton.attr('value'), class: $originalButton.attr('class'), click: function (e) { - $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); - e.preventDefault(); + // If the original button is an anchor tag, triggering the "click" + // event will not simulate a click. Use the click method instead. + if ($originalButton.is('a')) { + $originalButton[0].click(); + } + else { + $originalButton.trigger('mousedown').trigger('mouseup').trigger('click'); + e.preventDefault(); + } } }); }); diff --git a/core/modules/action/src/ActionListBuilder.php b/core/modules/action/src/ActionListBuilder.php index 09024037b..0a54cd652 100644 --- a/core/modules/action/src/ActionListBuilder.php +++ b/core/modules/action/src/ActionListBuilder.php @@ -108,10 +108,10 @@ class ActionListBuilder extends ConfigEntityListBuilder { * {@inheritdoc} */ public function render() { - $build['action_header']['#markup'] = '
-
-
- ', $filters['filter_html']['settings']['allowed_html']);
- $this->assertIdentical(TRUE, $filters['filter_html']['settings']['filter_html_help']);
- $this->assertIdentical(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
- $this->assertIdentical(72, $filters['filter_url']['settings']['filter_url_length']);
+ $this->assertSame('
-
-
- ', $filters['filter_html']['settings']['allowed_html']);
+ $this->assertSame(TRUE, $filters['filter_html']['settings']['filter_html_help']);
+ $this->assertSame(FALSE, $filters['filter_html']['settings']['filter_html_nofollow']);
+ $this->assertSame(72, $filters['filter_url']['settings']['filter_url_length']);
- // Check that the PHP code filter is converted to filter_null.
- $filters = FilterFormat::load('php_code')->get('filters');
- $this->assertTrue(isset($filters['filter_null']));
+ // Assert that the php_code format was migrated with filter_null in the
+ // php_code filter's place.
+ $filter_format = FilterFormat::load('php_code');
+ $this->assertInstanceOf(FilterFormatInterface::class, $filter_format);
+ $filters = $filter_format->get('filters');
+ $this->assertArrayHasKey('filter_null', $filters);
+ $this->assertArrayNotHasKey('php_code', $filters);
}
}
diff --git a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
index 8eb13b9d8..e4053ae18 100644
--- a/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
+++ b/core/modules/filter/tests/src/Kernel/Migrate/d7/MigrateFilterFormatTest.php
@@ -42,13 +42,13 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
protected function assertEntity($id, $label, array $enabled_filters, $weight) {
/** @var \Drupal\filter\FilterFormatInterface $entity */
$entity = FilterFormat::load($id);
- $this->assertTrue($entity instanceof FilterFormatInterface);
- $this->assertIdentical($label, $entity->label());
+ $this->assertInstanceOf(FilterFormatInterface::class, $entity);
+ $this->assertSame($label, $entity->label());
// get('filters') will return enabled filters only, not all of them.
- $this->assertIdentical(array_keys($enabled_filters), array_keys($entity->get('filters')));
- $this->assertIdentical($weight, $entity->get('weight'));
+ $this->assertSame(array_keys($enabled_filters), array_keys($entity->get('filters')));
+ $this->assertSame($weight, $entity->get('weight'));
foreach ($entity->get('filters') as $filter_id => $filter) {
- $this->assertIdentical($filter['weight'], $enabled_filters[$filter_id]);
+ $this->assertSame($filter['weight'], $enabled_filters[$filter_id]);
}
}
@@ -68,15 +68,17 @@ class MigrateFilterFormatTest extends MigrateDrupal7TestBase {
// Ensure that filter-specific settings were migrated.
/** @var \Drupal\filter\FilterFormatInterface $format */
$format = FilterFormat::load('filtered_html');
+ $this->assertInstanceOf(FilterFormatInterface::class, $format);
$config = $format->filters('filter_html')->getConfiguration();
- $this->assertIdentical('
-
', $config['settings']['allowed_html']);
+ $this->assertSame(' -
', $config['settings']['allowed_html']);
$config = $format->filters('filter_url')->getConfiguration();
- $this->assertIdentical(128, $config['settings']['filter_url_length']);
+ $this->assertSame(128, $config['settings']['filter_url_length']);
// The php_code format gets migrated, but the php_code filter is changed to
// filter_null.
- $filters = FilterFormat::load('php_code')->get('filters');
- $this->assertTrue(isset($filters['filter_null']));
+ $format = FilterFormat::load('php_code');
+ $this->assertInstanceOf(FilterFormatInterface::class, $format);
+ $this->assertArrayHasKey('filter_null', $format->get('filters'));
}
}
diff --git a/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php
new file mode 100644
index 000000000..39e4037c1
--- /dev/null
+++ b/core/modules/filter/tests/src/Kernel/Plugin/migrate/process/FilterIdTest.php
@@ -0,0 +1,119 @@
+executable = $this->getMock(MigrateExecutableInterface::class);
+ }
+
+ /**
+ * Tests the filter_id plugin.
+ *
+ * @param mixed $value
+ * The input value to the plugin.
+ * @param string $expected_value
+ * The output value expected from the plugin.
+ * @param string $invalid_id
+ * (optional) The invalid plugin ID which is expected to be logged by the
+ * MigrateExecutable object.
+ *
+ * @dataProvider testProvider
+ *
+ * @covers ::transform
+ */
+ public function test($value, $expected_value, $invalid_id = NULL) {
+ $configuration = [
+ 'bypass' => TRUE,
+ 'map' => [
+ 'foo' => 'filter_html',
+ 'baz' => 'php_code',
+ ],
+ ];
+ $plugin = FilterID::create($this->container, $configuration, 'filter_id', []);
+
+ if (isset($invalid_id)) {
+ $this->executable
+ ->expects($this->exactly(1))
+ ->method('saveMessage')
+ ->with(
+ 'Filter ' . $invalid_id . ' could not be mapped to an existing filter plugin; defaulting to filter_null.',
+ MigrationInterface::MESSAGE_WARNING
+ );
+ }
+
+ $row = new Row([], []);
+ $output_value = $plugin->transform($value, $this->executable, $row, 'foo');
+
+ $this->assertSame($expected_value, $output_value);
+ }
+
+ /**
+ * The test data provider.
+ *
+ * @return array
+ */
+ public function testProvider() {
+ return [
+ // The filter ID is mapped, and the plugin exists.
+ [
+ 'foo',
+ 'filter_html',
+ ],
+ // The filter ID isn't mapped, but it's unchanged from the source (i.e.,
+ // it bypasses the static map) and the plugin exists.
+ [
+ 'filter_html',
+ 'filter_html',
+ ],
+ // The filter ID is mapped, but the plugin does not exist.
+ [
+ 'baz',
+ 'filter_null',
+ 'php_code',
+ ],
+ // The filter ID isn't mapped, but it's unchanged from the source (i.e.,
+ // it bypasses the static map) but the plugin does not exist.
+ [
+ 'php_code',
+ 'filter_null',
+ 'php_code',
+ ],
+ [
+ ['filter', 1],
+ 'filter_null',
+ 'filter:1',
+ ],
+ ];
+ }
+
+}
diff --git a/core/modules/image/src/Controller/ImageStyleDownloadController.php b/core/modules/image/src/Controller/ImageStyleDownloadController.php
index 885557c5b..5da6bc89d 100644
--- a/core/modules/image/src/Controller/ImageStyleDownloadController.php
+++ b/core/modules/image/src/Controller/ImageStyleDownloadController.php
@@ -113,14 +113,9 @@ class ImageStyleDownloadController extends FileDownloadController {
// If using the private scheme, let other modules provide headers and
// control access to the file.
if ($scheme == 'private') {
- if (file_exists($derivative_uri)) {
- return parent::download($request, $scheme);
- }
- else {
- $headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri));
- if (in_array(-1, $headers) || empty($headers)) {
- throw new AccessDeniedHttpException();
- }
+ $headers = $this->moduleHandler()->invokeAll('file_download', array($image_uri));
+ if (in_array(-1, $headers) || empty($headers)) {
+ throw new AccessDeniedHttpException();
}
}
diff --git a/core/modules/image/src/ImageStyleListBuilder.php b/core/modules/image/src/ImageStyleListBuilder.php
index e4e0be658..140ed46b8 100644
--- a/core/modules/image/src/ImageStyleListBuilder.php
+++ b/core/modules/image/src/ImageStyleListBuilder.php
@@ -4,10 +4,7 @@ namespace Drupal\image;
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Routing\UrlGeneratorInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Url;
/**
* Defines a class to build a listing of image style entities.
@@ -16,40 +13,6 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
*/
class ImageStyleListBuilder extends ConfigEntityListBuilder {
- /**
- * The URL generator.
- *
- * @var \Drupal\Core\Routing\UrlGeneratorInterface
- */
- protected $urlGenerator;
-
- /**
- * Constructs a new ImageStyleListBuilder object.
- *
- * @param EntityTypeInterface $entity_type
- * The entity type definition.
- * @param \Drupal\Core\Entity\EntityStorageInterface $image_style_storage
- * The image style entity storage class.
- * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator
- * The URL generator.
- */
- public function __construct(EntityTypeInterface $entity_type, EntityStorageInterface $image_style_storage, UrlGeneratorInterface $url_generator) {
- parent::__construct($entity_type, $image_style_storage);
- $this->urlGenerator = $url_generator;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
- return new static(
- $entity_type,
- $container->get('entity.manager')->getStorage($entity_type->id()),
- $container->get('url_generator'),
- $container->get('string_translation')
- );
- }
-
/**
* {@inheritdoc}
*/
@@ -87,7 +50,7 @@ class ImageStyleListBuilder extends ConfigEntityListBuilder {
public function render() {
$build = parent::render();
$build['table']['#empty'] = $this->t('There are currently no styles. Add a new one.', [
- ':url' => $this->urlGenerator->generateFromRoute('image.style_add'),
+ ':url' => Url::fromRoute('image.style_add')->toString(),
]);
return $build;
}
diff --git a/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php
index f146ccc5a..1fed32f3b 100644
--- a/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/src/Tests/ImageStylesPathAndUrlTest.php
@@ -155,6 +155,11 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$image = $this->container->get('image.factory')->get($generated_uri);
$this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.');
$this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), 'Expected Content-Length was reported.');
+
+ // Check that we did not download the original file.
+ $original_image = $this->container->get('image.factory')->get($original_uri);
+ $this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
+
if ($scheme == 'private') {
$this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
$this->assertNotEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
@@ -165,6 +170,12 @@ class ImageStylesPathAndUrlTest extends WebTestBase {
$this->drupalGet($generate_url);
$this->assertResponse(200, 'Image was generated at the URL.');
+ // Check that the second request also returned the generated image.
+ $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize());
+
+ // Check that we did not download the original file.
+ $this->assertNotEqual($this->drupalGetHeader('Content-Length'), $original_image->getFileSize());
+
// Make sure that access is denied for existing style files if we do not
// have access.
\Drupal::state()->delete('image.test_file_download');
diff --git a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
index 3c92345ba..6e98ae225 100644
--- a/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
+++ b/core/modules/link/src/Plugin/Field/FieldType/LinkItem.php
@@ -169,7 +169,7 @@ class LinkItem extends FieldItemBase implements LinkItemInterface {
* {@inheritdoc}
*/
public function getUrl() {
- return Url::fromUri($this->uri, $this->options);
+ return Url::fromUri($this->uri, (array) $this->options);
}
/**
diff --git a/core/modules/link/tests/src/Kernel/LinkItemTest.php b/core/modules/link/tests/src/Kernel/LinkItemTest.php
index 6473efbf1..2785164ba 100644
--- a/core/modules/link/tests/src/Kernel/LinkItemTest.php
+++ b/core/modules/link/tests/src/Kernel/LinkItemTest.php
@@ -5,6 +5,7 @@ namespace Drupal\Tests\link\Kernel;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
+use Drupal\Core\Url;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
@@ -154,6 +155,11 @@ class LinkItemTest extends FieldKernelTestBase {
$this->assertNull($entity->field_test->title);
$this->assertIdentical($entity->field_test->options, []);
+ // Check that setting options to NULL does not trigger an error when
+ // calling getUrl();
+ $entity->field_test->options = NULL;
+ $this->assertInstanceOf(Url::class, $entity->field_test[0]->getUrl());
+
// Check that setting LinkItem value NULL doesn't generate any error or
// warning.
$entity->field_test[0] = NULL;
diff --git a/core/modules/migrate/src/Event/MigrateEvents.php b/core/modules/migrate/src/Event/MigrateEvents.php
index edc2beccb..e2de42eb9 100644
--- a/core/modules/migrate/src/Event/MigrateEvents.php
+++ b/core/modules/migrate/src/Event/MigrateEvents.php
@@ -81,7 +81,7 @@ final class MigrateEvents {
*
* This event allows modules to perform an action whenever a specific item
* is about to be saved by the destination plugin. The event listener method
- * receives a \Drupal\migrate\Event\MigratePreSaveEvent instance.
+ * receives a \Drupal\migrate\Event\MigratePreRowSaveEvent instance.
*
* @Event
*
diff --git a/core/modules/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index c0b3537ac..ef5b86c4f 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -329,6 +329,12 @@ class MigrateExecutable implements MigrateExecutableInterface {
// We're now done with this row, so remove it from the map.
$id_map->deleteDestination($destination_key);
}
+ else {
+ // If there is no destination key the import probably failed and we can
+ // remove the row without further action.
+ $source_key = $id_map->currentSource();
+ $id_map->delete($source_key);
+ }
// Check for memory exhaustion.
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
diff --git a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
index d3bdb8923..eafac2966 100644
--- a/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
+++ b/core/modules/migrate/src/Plugin/MigrateIdMapInterface.php
@@ -243,6 +243,14 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
*/
public function currentDestination();
+ /**
+ * Looks up the source identifier(s) currently being iterated.
+ *
+ * @return array
+ * The source identifier values of the record, or NULL on failure.
+ */
+ public function currentSource();
+
/**
* Removes any persistent storage used by this map.
*
diff --git a/core/modules/migrate/src/Plugin/MigratePluginManager.php b/core/modules/migrate/src/Plugin/MigratePluginManager.php
index a51efdae2..8fd1644e5 100644
--- a/core/modules/migrate/src/Plugin/MigratePluginManager.php
+++ b/core/modules/migrate/src/Plugin/MigratePluginManager.php
@@ -21,7 +21,7 @@ use Drupal\Core\Plugin\DefaultPluginManager;
*
* @ingroup migration
*/
-class MigratePluginManager extends DefaultPluginManager {
+class MigratePluginManager extends DefaultPluginManager implements MigratePluginManagerInterface {
/**
* Constructs a MigratePluginManager object.
@@ -49,8 +49,6 @@ class MigratePluginManager extends DefaultPluginManager {
/**
* {@inheritdoc}
- *
- * A specific createInstance method is necessary to pass the migration on.
*/
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
$plugin_definition = $this->getDefinition($plugin_id);
diff --git a/core/modules/migrate/src/Plugin/MigratePluginManagerInterface.php b/core/modules/migrate/src/Plugin/MigratePluginManagerInterface.php
new file mode 100644
index 000000000..d6ed39fdc
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/MigratePluginManagerInterface.php
@@ -0,0 +1,29 @@
+migrationPluginManager = $migration_plugin_manager;
$this->sourcePluginManager = $source_plugin_manager;
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php
index 54bc35843..966cb3708 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldInstance.php
@@ -21,4 +21,12 @@ class EntityFieldInstance extends EntityConfigBase {
return $ids;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function rollback(array $destination_identifier) {
+ $destination_identifier = implode('.', $destination_identifier);
+ parent::rollback(array($destination_identifier));
+ }
+
}
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php
index 6851f2708..7ad01045a 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityFieldStorageConfig.php
@@ -20,4 +20,12 @@ class EntityFieldStorageConfig extends EntityConfigBase {
return $ids;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function rollback(array $destination_identifier) {
+ $destination_identifier = implode('.', $destination_identifier);
+ parent::rollback(array($destination_identifier));
+ }
+
}
diff --git a/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php b/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php
index 810152aa1..fad171b59 100644
--- a/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php
+++ b/core/modules/migrate/src/Plugin/migrate/destination/EntityViewMode.php
@@ -20,4 +20,12 @@ class EntityViewMode extends EntityConfigBase {
return $ids;
}
+ /**
+ * {@inheritdoc}
+ */
+ public function rollback(array $destination_identifier) {
+ $destination_identifier = implode('.', $destination_identifier);
+ parent::rollback(array($destination_identifier));
+ }
+
}
diff --git a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
index cf39275f8..cbf4a9f46 100644
--- a/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
+++ b/core/modules/migrate/src/Plugin/migrate/id_map/Sql.php
@@ -845,7 +845,25 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
if ($this->valid()) {
$result = array();
foreach ($this->destinationIdFields() as $destination_field_name => $idmap_field_name) {
- $result[$destination_field_name] = $this->currentRow[$idmap_field_name];
+ if (!is_null($this->currentRow[$idmap_field_name])) {
+ $result[$destination_field_name] = $this->currentRow[$idmap_field_name];
+ }
+ }
+ return $result;
+ }
+ else {
+ return NULL;
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function currentSource() {
+ if ($this->valid()) {
+ $result = array();
+ foreach ($this->sourceIdFields() as $field_name => $source_id) {
+ $result[$field_name] = $this->currentKey[$source_id];
}
return $result;
}
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Callback.php b/core/modules/migrate/src/Plugin/migrate/process/Callback.php
index 2565a31fb..618e05d4b 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Callback.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Callback.php
@@ -15,6 +15,8 @@ use Drupal\migrate\Row;
* arguments can be passed to the callback as this would make the migration YAML
* file too complex.
*
+ * @link https://www.drupal.org/node/2181783 Online handbook documentation for callback process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "callback"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Concat.php b/core/modules/migrate/src/Plugin/migrate/process/Concat.php
index bb1891e30..c013bdc78 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Concat.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Concat.php
@@ -10,6 +10,8 @@ use Drupal\migrate\Row;
/**
* Concatenates the strings in the current value.
*
+ * @link https://www.drupal.org/node/2345927 Online handbook documentation for concat process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "concat",
* handle_multiples = TRUE
diff --git a/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php b/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php
index a15d058c2..adaf72009 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/DedupeBase.php
@@ -15,6 +15,8 @@ use Drupal\Component\Utility\Unicode;
* creating filter format names, the current value is checked against the
* existing filter format names and if it exists, a numeric postfix is added
* and incremented until a unique value is created.
+ *
+ * @link https://www.drupal.org/node/2345929 Online handbook documentation for dedupebase process plugin @endlink
*/
abstract class DedupeBase extends ProcessPluginBase {
diff --git a/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php b/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php
index b04dba719..d2afe8f58 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/DedupeEntity.php
@@ -10,6 +10,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Ensures value is not duplicated against an entity field.
*
+ * @link https://www.drupal.org/node/2135325 Online handbook documentation for dedupe_entity process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "dedupe_entity"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/DefaultValue.php b/core/modules/migrate/src/Plugin/migrate/process/DefaultValue.php
index e9ff3d9f5..db216f6f0 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/DefaultValue.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/DefaultValue.php
@@ -9,6 +9,8 @@ use Drupal\migrate\Row;
/**
* This plugin sets missing values on the destination.
*
+ * @link https://www.drupal.org/node/2135313 Online handbook documentation for default_value process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "default_value"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Explode.php b/core/modules/migrate/src/Plugin/migrate/process/Explode.php
index 5a22a2170..fd3cc9cfb 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Explode.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Explode.php
@@ -10,6 +10,8 @@ use Drupal\migrate\Row;
/**
* This plugin explodes a delimited string into an array of values.
*
+ * @link https://www.drupal.org/node/2674504 Online handbook documentation for explode process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "explode"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Extract.php b/core/modules/migrate/src/Plugin/migrate/process/Extract.php
index 123388cef..81671d879 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Extract.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Extract.php
@@ -11,7 +11,7 @@ use Drupal\migrate\Row;
/**
* This plugin extracts a value from an array.
*
- * @see https://www.drupal.org/node/2152731
+ * @link https://www.drupal.org/node/2152731 Online handbook documentation for extract process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "extract"
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Flatten.php b/core/modules/migrate/src/Plugin/migrate/process/Flatten.php
index e10bc63c9..f663e4d95 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Flatten.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Flatten.php
@@ -12,7 +12,7 @@ use Drupal\migrate\Row;
* once a single value gets transformed into multiple values. This plugin will
* flatten them back down to single values again.
*
- * @see https://www.drupal.org/node/2154215
+ * @link https://www.drupal.org/node/2154215 Online handbook documentation for flatten process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "flatten",
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Get.php b/core/modules/migrate/src/Plugin/migrate/process/Get.php
index 20a9ba8c9..f6b3c34e8 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Get.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Get.php
@@ -9,6 +9,8 @@ use Drupal\migrate\Row;
/**
* This plugin copies from the source to the destination.
*
+ * @link https://www.drupal.org/node/2135307 Online handbook documentation for get process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "get"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
index 7affba94d..70df9baa4 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Iterator.php
@@ -9,7 +9,7 @@ use Drupal\migrate\Row;
/**
* This plugin iterates and processes an array.
*
- * @see https://www.drupal.org/node/2135345
+ * @link https://www.drupal.org/node/2135345 Online handbook documentation for iterator process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "iterator",
diff --git a/core/modules/migrate/src/Plugin/migrate/process/MachineName.php b/core/modules/migrate/src/Plugin/migrate/process/MachineName.php
index aef8c24c5..15ed35677 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/MachineName.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/MachineName.php
@@ -17,6 +17,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* and replaced by an underscore and multiple underscores are collapsed into
* one.
*
+ * @link https://www.drupal.org/node/2135323 Online handbook documentation for machine_name process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "machine_name"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Migration.php b/core/modules/migrate/src/Plugin/migrate/process/Migration.php
index c67c75497..a8d5b601b 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Migration.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Migration.php
@@ -4,7 +4,7 @@ namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateSkipProcessException;
-use Drupal\migrate\Plugin\MigratePluginManager;
+use Drupal\migrate\Plugin\MigratePluginManagerInterface;
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\ProcessPluginBase;
@@ -16,6 +16,8 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Calculates the value of a property based on a previous migration.
*
+ * @link https://www.drupal.org/node/2149801 Online handbook documentation for migration process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "migration"
* )
@@ -39,7 +41,7 @@ class Migration extends ProcessPluginBase implements ContainerFactoryPluginInter
/**
* {@inheritdoc}
*/
- public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $process_plugin_manager) {
+ public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $process_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migrationPluginManager = $migration_plugin_manager;
$this->migration = $migration;
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Route.php b/core/modules/migrate/src/Plugin/migrate/process/Route.php
index ff04a527f..2c0144bcb 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Route.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Route.php
@@ -11,7 +11,10 @@ use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
- * @MigrateProcessPlugin(
+ *
+ * @link https://www.drupal.org/node/2750777 Online handbook documentation for route process plugin @endlink
+ *
+ * * @MigrateProcessPlugin(
* id = "route"
* )
*/
diff --git a/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php b/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
index e88946bb3..28b6df0d7 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/SkipOnEmpty.php
@@ -11,6 +11,8 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip processing or the whole row.
*
+ * @link https://www.drupal.org/node/2228793 Online handbook documentation for skip_on_empty process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "skip_on_empty"
* )
diff --git a/core/modules/migrate/src/Plugin/migrate/process/SkipRowIfNotSet.php b/core/modules/migrate/src/Plugin/migrate/process/SkipRowIfNotSet.php
index 4b5e3397b..a6cffceb4 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/SkipRowIfNotSet.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/SkipRowIfNotSet.php
@@ -10,6 +10,8 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip the current row.
*
+ * @link https://www.drupal.org/node/2345935 Online handbook documentation for skip_row_if_not_set process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "skip_row_if_not_set",
* handle_multiples = TRUE
diff --git a/core/modules/migrate/src/Plugin/migrate/process/StaticMap.php b/core/modules/migrate/src/Plugin/migrate/process/StaticMap.php
index aa74736e0..769da3b7e 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/StaticMap.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/StaticMap.php
@@ -12,7 +12,7 @@ use Drupal\migrate\MigrateSkipRowException;
/**
* This plugin changes the current value based on a static lookup map.
*
- * @see https://www.drupal.org/node/2143521
+ * @link https://www.drupal.org/node/2143521 Online handbook documentation for static_map process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "static_map"
diff --git a/core/modules/migrate/src/Plugin/migrate/process/Substr.php b/core/modules/migrate/src/Plugin/migrate/process/Substr.php
index ab6171a4d..8ebc98c6d 100644
--- a/core/modules/migrate/src/Plugin/migrate/process/Substr.php
+++ b/core/modules/migrate/src/Plugin/migrate/process/Substr.php
@@ -11,6 +11,8 @@ use Drupal\Component\Utility\Unicode;
/**
* This plugin returns a substring of the current value.
*
+ * @link https://www.drupal.org/node/2771965 Online handbook documentation for substr process plugin @endlink
+ *
* @MigrateProcessPlugin(
* id = "substr"
* )
diff --git a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
index 9ec0c65e0..4b78e6526 100644
--- a/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
+++ b/core/modules/migrate/tests/src/Kernel/MigrateRollbackTest.php
@@ -128,6 +128,9 @@ class MigrateRollbackTest extends MigrateTestBase {
$this->assertNotNull($map_row['destid1']);
}
+ // Add a failed row to test if this can be rolled back without errors.
+ $this->mockFailure($term_migration, ['id' => '4', 'vocab' => '2', 'name' => 'FAIL']);
+
// Rollback and verify the entities are gone.
$term_executable->rollback();
foreach ($term_data_rows as $row) {
diff --git a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
index a9d1313f5..e38e971be 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateSqlIdMapTest.php
@@ -640,6 +640,35 @@ class MigrateSqlIdMapTest extends MigrateTestCase {
$this->assertSame(0, count($source_id));
}
+ /**
+ * Tests currentDestination() and currentSource().
+ */
+ public function testCurrentDestinationAndSource() {
+ // Simple map with one source and one destination ID.
+ $id_map = $this->setupRows(['nid'], ['nid'], [
+ [1, 101],
+ [2, 102],
+ [3, 103],
+ // Mock a failed row by setting the destination ID to NULL.
+ [4, NULL],
+ ]);
+
+ // The rows are ordered by destination ID so the failed row should be first.
+ $id_map->rewind();
+ $this->assertEquals([], $id_map->currentDestination());
+ $this->assertEquals(['nid' => 4], $id_map->currentSource());
+ $id_map->next();
+ $this->assertEquals(['nid' => 101], $id_map->currentDestination());
+ $this->assertEquals(['nid' => 1], $id_map->currentSource());
+ $id_map->next();
+ $this->assertEquals(['nid' => 102], $id_map->currentDestination());
+ $this->assertEquals(['nid' => 2], $id_map->currentSource());
+ $id_map->next();
+ $this->assertEquals(['nid' => 103], $id_map->currentDestination());
+ $this->assertEquals(['nid' => 3], $id_map->currentSource());
+ $id_map->next();
+ }
+
/**
* Tests the imported count method.
*
diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php
index 44151ccfa..56c7b5bad 100644
--- a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php
+++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManager.php
@@ -15,7 +15,7 @@ use Drupal\migrate\Plugin\MigrationInterface;
*
* @ingroup migration
*/
-class MigrateCckFieldPluginManager extends MigratePluginManager {
+class MigrateCckFieldPluginManager extends MigratePluginManager implements MigrateCckFieldPluginManagerInterface {
/**
* The default version of core to use for cck field plugins.
@@ -29,7 +29,7 @@ class MigrateCckFieldPluginManager extends MigratePluginManager {
/**
* {@inheritdoc}
*/
- public function createInstance($field_type, array $configuration = array(), MigrationInterface $migration = NULL) {
+ public function getPluginIdFromFieldType($field_type, array $configuration = [], MigrationInterface $migration = NULL) {
$core = static::DEFAULT_CORE_VERSION;
if (!empty($configuration['core'])) {
$core = $configuration['core'];
@@ -45,7 +45,7 @@ class MigrateCckFieldPluginManager extends MigratePluginManager {
foreach ($this->getDefinitions() as $plugin_id => $definition) {
if (in_array($core, $definition['core'])) {
if (array_key_exists($field_type, $definition['type_map']) || $field_type === $plugin_id) {
- return parent::createInstance($plugin_id, $configuration, $migration);
+ return $plugin_id;
}
}
}
diff --git a/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php
new file mode 100644
index 000000000..a5371b9dc
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/MigrateCckFieldPluginManagerInterface.php
@@ -0,0 +1,28 @@
+cckPluginManager = $cck_manager;
}
@@ -106,12 +108,19 @@ class CckMigration extends Migration implements ContainerFactoryPluginInterface
}
foreach ($source_plugin as $row) {
$field_type = $row->getSourceProperty('type');
- if (!isset($this->processedFieldTypes[$field_type]) && $this->cckPluginManager->hasDefinition($field_type)) {
+ try {
+ $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, [], $this);
+ }
+ catch (PluginNotFoundException $ex) {
+ continue;
+ }
+
+ if (!isset($this->processedFieldTypes[$field_type])) {
$this->processedFieldTypes[$field_type] = TRUE;
// Allow the cckfield plugin to alter the migration as necessary so
// that it knows how to handle fields of this type.
if (!isset($this->cckPluginCache[$field_type])) {
- $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, [], $this);
+ $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, [], $this);
}
call_user_func([$this->cckPluginCache[$field_type], $this->pluginDefinition['cck_plugin_method']], $this);
}
diff --git a/core/modules/migrate_drupal/tests/fixtures/drupal7.php b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
index bbc808827..a02d12f57 100644
--- a/core/modules/migrate_drupal/tests/fixtures/drupal7.php
+++ b/core/modules/migrate_drupal/tests/fixtures/drupal7.php
@@ -3835,6 +3835,18 @@ $connection->insert('field_data_body')
'body_summary' => '',
'body_format' => 'filtered_html',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.",
+ 'body_summary' => '',
+ 'body_format' => 'filtered_html',
+))
->execute();
$connection->schema()->createTable('field_data_comment_body', array(
@@ -4930,6 +4942,18 @@ $connection->insert('field_data_field_link')
'field_link_title' => 'Home',
'field_link_attributes' => 'a:0:{}',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'field_link_url' => '',
+ 'field_link_title' => 'Home',
+ 'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}',
+))
->execute();
$connection->schema()->createTable('field_data_field_long_text', array(
@@ -5167,6 +5191,16 @@ $connection->insert('field_data_field_tags')
'delta' => '0',
'field_tags_tid' => '9',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'field_tags_tid' => '9',
+))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@@ -5177,6 +5211,16 @@ $connection->insert('field_data_field_tags')
'delta' => '1',
'field_tags_tid' => '14',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '1',
+ 'field_tags_tid' => '14',
+))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@@ -5187,6 +5231,16 @@ $connection->insert('field_data_field_tags')
'delta' => '2',
'field_tags_tid' => '17',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '2',
+ 'field_tags_tid' => '17',
+))
->execute();
$connection->schema()->createTable('field_data_field_term_reference', array(
@@ -5603,6 +5657,18 @@ $connection->insert('field_revision_body')
'body_summary' => '',
'body_format' => 'filtered_html',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'body_value' => "is - ...is that it's the absolute best show ever. Trust me, I would know.",
+ 'body_summary' => '',
+ 'body_format' => 'filtered_html',
+))
->execute();
$connection->schema()->createTable('field_revision_comment_body', array(
@@ -6710,6 +6776,18 @@ $connection->insert('field_revision_field_link')
'field_link_title' => 'Home',
'field_link_attributes' => 'a:0:{}',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'field_link_url' => '',
+ 'field_link_title' => 'Home',
+ 'field_link_attributes' => 'a:1:{s:5:"title";s:0:"";}',
+))
->execute();
$connection->schema()->createTable('field_revision_field_long_text', array(
@@ -6950,6 +7028,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '0',
'field_tags_tid' => '9',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '0',
+ 'field_tags_tid' => '9',
+))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@@ -6960,6 +7048,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '1',
'field_tags_tid' => '14',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '1',
+ 'field_tags_tid' => '14',
+))
->values(array(
'entity_type' => 'node',
'bundle' => 'article',
@@ -6970,6 +7068,16 @@ $connection->insert('field_revision_field_tags')
'delta' => '2',
'field_tags_tid' => '17',
))
+->values(array(
+ 'entity_type' => 'node',
+ 'bundle' => 'article',
+ 'deleted' => '0',
+ 'entity_id' => '3',
+ 'revision_id' => '3',
+ 'language' => 'und',
+ 'delta' => '2',
+ 'field_tags_tid' => '17',
+))
->execute();
$connection->schema()->createTable('field_revision_field_term_reference', array(
@@ -29674,7 +29782,23 @@ $connection->insert('node')
'comment' => '2',
'promote' => '1',
'sticky' => '0',
- 'tnid' => '0',
+ 'tnid' => '2',
+ 'translate' => '0',
+))
+->values(array(
+ 'nid' => '3',
+ 'vid' => '3',
+ 'type' => 'article',
+ 'language' => 'is',
+ 'title' => 'is - The thing about Deep Space 9',
+ 'uid' => '1',
+ 'status' => '1',
+ 'created' => '1471428152',
+ 'changed' => '1471428152',
+ 'comment' => '2',
+ 'promote' => '1',
+ 'sticky' => '0',
+ 'tnid' => '2',
'translate' => '0',
))
->execute();
@@ -29813,6 +29937,14 @@ $connection->insert('node_comment_statistics')
'last_comment_uid' => '1',
'comment_count' => '1',
))
+->values(array(
+ 'nid' => '3',
+ 'cid' => '0',
+ 'last_comment_timestamp' => '1471428152',
+ 'last_comment_name' => NULL,
+ 'last_comment_uid' => '1',
+ 'comment_count' => '0',
+))
->execute();
$connection->schema()->createTable('node_counter', array(
@@ -29864,6 +29996,18 @@ $connection->insert('node_counter')
'daycount' => '0',
'timestamp' => '1421727536',
))
+->values(array(
+ 'nid' => '2',
+ 'totalcount' => '1',
+ 'daycount' => '1',
+ 'timestamp' => '1471428059',
+))
+->values(array(
+ 'nid' => '3',
+ 'totalcount' => '1',
+ 'daycount' => '1',
+ 'timestamp' => '1471428153',
+))
->execute();
$connection->schema()->createTable('node_revision', array(
@@ -29972,6 +30116,18 @@ $connection->insert('node_revision')
'promote' => '1',
'sticky' => '0',
))
+->values(array(
+ 'nid' => '3',
+ 'vid' => '3',
+ 'uid' => '1',
+ 'title' => 'is - The thing about Deep Space 9',
+ 'log' => '',
+ 'timestamp' => '1471428152',
+ 'status' => '1',
+ 'comment' => '2',
+ 'promote' => '1',
+ 'sticky' => '0',
+))
->execute();
$connection->schema()->createTable('node_type', array(
@@ -40112,6 +40268,24 @@ $connection->insert('taxonomy_index')
'sticky' => '0',
'created' => '1441306772',
))
+->values(array(
+ 'nid' => '3',
+ 'tid' => '9',
+ 'sticky' => '0',
+ 'created' => '1471428152',
+))
+->values(array(
+ 'nid' => '3',
+ 'tid' => '14',
+ 'sticky' => '0',
+ 'created' => '1471428152',
+))
+->values(array(
+ 'nid' => '3',
+ 'tid' => '17',
+ 'sticky' => '0',
+ 'created' => '1471428152',
+))
->execute();
$connection->schema()->createTable('taxonomy_term_data', array(
@@ -41342,7 +41516,7 @@ $connection->insert('variable')
))
->values(array(
'name' => 'language_content_type_article',
- 'value' => 's:1:"0";',
+ 'value' => 's:1:"2";',
))
->values(array(
'name' => 'language_content_type_blog',
@@ -41442,7 +41616,7 @@ $connection->insert('variable')
))
->values(array(
'name' => 'menu_override_parent_selector',
- 'value' => 'b:1;',
+ 'value' => 'b:0;',
))
->values(array(
'name' => 'menu_parent_article',
diff --git a/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php b/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php
index 8cea74c4d..1754cfef5 100644
--- a/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php
+++ b/core/modules/migrate_drupal/tests/src/Kernel/MigrateCckFieldPluginManagerTest.php
@@ -22,32 +22,33 @@ class MigrateCckFieldPluginManagerTest extends MigrateDrupalTestBase {
public function testPluginSelection() {
$plugin_manager = \Drupal::service('plugin.manager.migrate.cckfield');
- $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance('filefield', ['core' => 6])));
+ $plugin_id = $plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 6]);
+ $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d6\\FileField', get_class($plugin_manager->createInstance($plugin_id, ['core' => 6])));
try {
- // If this test passes, createInstance will raise a
+ // If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
- $plugin_manager->createInstance('filefield', ['core' => 7]);
+ $plugin_manager->getPluginIdFromFieldType('filefield', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {
$this->assertIdentical($e->getMessage(), "Plugin ID 'filefield' was not found.");
}
- $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\ImageField', get_class($plugin_manager->createInstance('image', ['core' => 7])));
- $this->assertIdentical('Drupal\\file\\Plugin\\migrate\\cckfield\\d7\\FileField', get_class($plugin_manager->createInstance('file', ['core' => 7])));
- $this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6FileField', get_class($plugin_manager->createInstance('file', ['core' => 6])));
+ $this->assertIdentical('image', $plugin_manager->getPluginIdFromFieldType('image', ['core' => 7]));
+ $this->assertIdentical('file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 7]));
+ $this->assertIdentical('d6_file', $plugin_manager->getPluginIdFromFieldType('file', ['core' => 6]));
- $this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 6])));
- $this->assertIdentical('Drupal\\text\\Plugin\\migrate\\cckfield\\TextField', get_class($plugin_manager->createInstance('text', ['core' => 7])));
+ $this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 6]));
+ $this->assertIdentical('text', $plugin_manager->getPluginIdFromFieldType('text', ['core' => 7]));
// Test fallback when no core version is specified.
- $this->assertIdentical('Drupal\\migrate_cckfield_plugin_manager_test\\Plugin\\migrate\\cckfield\\D6NoCoreVersionSpecified', get_class($plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 6])));
+ $this->assertIdentical('d6_no_core_version_specified', $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 6]));
try {
- // If this test passes, createInstance will raise a
+ // If this test passes, getPluginIdFromFieldType will raise a
// PluginNotFoundException and we'll never reach fail().
- $plugin_manager->createInstance('d6_no_core_version_specified', ['core' => 7]);
+ $plugin_manager->getPluginIdFromFieldType('d6_no_core_version_specified', ['core' => 7]);
$this->fail('Expected Drupal\Component\Plugin\Exception\PluginNotFoundException.');
}
catch (PluginNotFoundException $e) {
diff --git a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
index 6b30adea5..2769b4431 100644
--- a/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
+++ b/core/modules/migrate_drupal_ui/src/Tests/d7/MigrateUpgrade7Test.php
@@ -48,9 +48,9 @@ class MigrateUpgrade7Test extends MigrateUpgradeTestBase {
'file' => 1,
'filter_format' => 7,
'image_style' => 6,
- 'language_content_settings' => 1,
+ 'language_content_settings' => 2,
'migration' => 59,
- 'node' => 2,
+ 'node' => 3,
'node_type' => 6,
'rdf_mapping' => 5,
'search_page' => 2,
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index aade3e833..53d43ab61 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1213,7 +1213,7 @@ function _node_access_rebuild_batch_operation(&$context) {
// Initiate multistep processing.
$context['sandbox']['progress'] = 0;
$context['sandbox']['current_node'] = 0;
- $context['sandbox']['max'] = \Drupal::entityQuery('node')->count()->execute();
+ $context['sandbox']['max'] = \Drupal::entityQuery('node')->accessCheck(FALSE)->count()->execute();
}
// Process the next 20 nodes.
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index 7244bdfeb..7131083bb 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -127,7 +127,7 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
public function revisionShow($node_revision) {
$node = $this->entityManager()->getStorage('node')->loadRevision($node_revision);
$node = $this->entityManager()->getTranslationFromContext($node);
- $node_view_controller = new NodeViewController($this->entityManager, $this->renderer);
+ $node_view_controller = new NodeViewController($this->entityManager, $this->renderer, $this->currentUser());
$page = $node_view_controller->view($node);
unset($page['nodes'][$node->id()]['#cache']);
return $page;
diff --git a/core/modules/node/src/Controller/NodeViewController.php b/core/modules/node/src/Controller/NodeViewController.php
index f80e65d27..fce50e144 100644
--- a/core/modules/node/src/Controller/NodeViewController.php
+++ b/core/modules/node/src/Controller/NodeViewController.php
@@ -4,12 +4,50 @@ namespace Drupal\node\Controller;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\Controller\EntityViewController;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Defines a controller to render a single node.
*/
class NodeViewController extends EntityViewController {
+ /**
+ * The current user.
+ *
+ * @var \Drupal\Core\Session\AccountInterface
+ */
+ protected $currentUser;
+
+ /**
+ * Creates an NodeViewController object.
+ *
+ * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+ * The entity manager.
+ * @param \Drupal\Core\Render\RendererInterface $renderer
+ * The renderer service.
+ * @param \Drupal\Core\Session\AccountInterface $current_user
+ * The current user. For backwards compatibility this is optional, however
+ * this will be removed before Drupal 9.0.0.
+ */
+ public function __construct(EntityManagerInterface $entity_manager, RendererInterface $renderer, AccountInterface $current_user = NULL) {
+ parent::__construct($entity_manager, $renderer);
+ $this->currentUser = $current_user ?: \Drupal::currentUser();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('entity.manager'),
+ $container->get('renderer'),
+ $container->get('current_user')
+ );
+ }
+
/**
* {@inheritdoc}
*/
@@ -17,27 +55,44 @@ class NodeViewController extends EntityViewController {
$build = parent::view($node, $view_mode, $langcode);
foreach ($node->uriRelationships() as $rel) {
- // Set the node path as the canonical URL to prevent duplicate content.
- $build['#attached']['html_head_link'][] = array(
- array(
- 'rel' => $rel,
- 'href' => $node->url($rel),
- ),
- TRUE,
- );
+ $url = $node->toUrl($rel);
+ // Add link relationships if the user is authenticated or if the anonymous
+ // user has access. Access checking must be done for anonymous users to
+ // avoid traffic to inaccessible pages from web crawlers. For
+ // authenticated users, showing the links in HTML head does not impact
+ // user experience or security, since the routes are access checked when
+ // visited and only visible via view source. This prevents doing
+ // potentially expensive and hard to cache access checks on every request.
+ // This means that the page will vary by user.permissions. We also rely on
+ // the access checking fallback to ensure the correct cacheability
+ // metadata if we have to check access.
+ if ($this->currentUser->isAuthenticated() || $url->access($this->currentUser)) {
+ // Set the node path as the canonical URL to prevent duplicate content.
+ $build['#attached']['html_head_link'][] = array(
+ array(
+ 'rel' => $rel,
+ 'href' => $url->toString(),
+ ),
+ TRUE,
+ );
+ }
if ($rel == 'canonical') {
// Set the non-aliased canonical path as a default shortlink.
$build['#attached']['html_head_link'][] = array(
array(
'rel' => 'shortlink',
- 'href' => $node->url($rel, array('alias' => TRUE)),
+ 'href' => $url->setOption('alias', TRUE)->toString(),
),
TRUE,
);
}
}
+ // Given this varies by $this->currentUser->isAuthenticated(), add a cache
+ // context based on the anonymous role.
+ $build['#cache']['contexts'][] = 'user.roles:anonymous';
+
return $build;
}
diff --git a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php
index f3e1a9210..3fba70ac4 100644
--- a/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php
+++ b/core/modules/node/src/Plugin/migrate/D6NodeDeriver.php
@@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
+use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -33,7 +34,7 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
- * @var \Drupal\Component\Plugin\PluginManagerInterface
+ * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@@ -49,12 +50,12 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
- * @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
+ * @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
* @param bool $translations
* Whether or not to include translations.
*/
- public function __construct($base_plugin_id, PluginManagerInterface $cck_manager, $translations) {
+ public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager, $translations) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
$this->includeTranslations = $translations;
@@ -128,14 +129,15 @@ class D6NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
- if ($this->cckPluginManager->hasDefinition($info['type'])) {
+ try {
+ $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 6], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
- $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 6], $migration);
+ $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 6], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
- else {
+ catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}
diff --git a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php
index 6efa1c746..bd3d8b9fc 100644
--- a/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php
+++ b/core/modules/node/src/Plugin/migrate/D7NodeDeriver.php
@@ -3,11 +3,12 @@
namespace Drupal\node\Plugin\migrate;
use Drupal\Component\Plugin\Derivative\DeriverBase;
-use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Component\Plugin\Exception\PluginNotFoundException;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrationDeriverTrait;
+use Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
@@ -33,7 +34,7 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
/**
* The CCK plugin manager.
*
- * @var \Drupal\Component\Plugin\PluginManagerInterface
+ * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface
*/
protected $cckPluginManager;
@@ -42,10 +43,10 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
*
* @param string $base_plugin_id
* The base plugin ID for the plugin ID.
- * @param \Drupal\Component\Plugin\PluginManagerInterface $cck_manager
+ * @param \Drupal\migrate_drupal\Plugin\MigrateCckFieldPluginManagerInterface $cck_manager
* The CCK plugin manager.
*/
- public function __construct($base_plugin_id, PluginManagerInterface $cck_manager) {
+ public function __construct($base_plugin_id, MigrateCckFieldPluginManagerInterface $cck_manager) {
$this->basePluginId = $base_plugin_id;
$this->cckPluginManager = $cck_manager;
}
@@ -98,14 +99,15 @@ class D7NodeDeriver extends DeriverBase implements ContainerDeriverInterface {
if (isset($fields[$node_type])) {
foreach ($fields[$node_type] as $field_name => $info) {
$field_type = $info['type'];
- if ($this->cckPluginManager->hasDefinition($field_type)) {
+ try {
+ $plugin_id = $this->cckPluginManager->getPluginIdFromFieldType($field_type, ['core' => 7], $migration);
if (!isset($this->cckPluginCache[$field_type])) {
- $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, ['core' => 7], $migration);
+ $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($plugin_id, ['core' => 7], $migration);
}
$this->cckPluginCache[$field_type]
->processCckFieldValues($migration, $field_name, $info);
}
- else {
+ catch (PluginNotFoundException $ex) {
$migration->setProcessOfProperty($field_name, $field_name);
}
}
diff --git a/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php b/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php
index d5cca9526..d151aadc3 100644
--- a/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php
+++ b/core/modules/node/src/Tests/NodeAccessRebuildNodeGrantsTest.php
@@ -2,6 +2,8 @@
namespace Drupal\node\Tests;
+use Drupal\node\Entity\NodeType;
+
/**
* Ensures that node access rebuild functions work correctly even
* when other modules implements hook_node_grants().
@@ -11,20 +13,27 @@ namespace Drupal\node\Tests;
class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
/**
- * A user to test the rebuild nodes feature.
+ * A user to create nodes that only it has access to.
*
* @var \Drupal\user\UserInterface
*/
protected $webUser;
+ /**
+ * A user to test the rebuild nodes feature which can't access the nodes.
+ *
+ * @var \Drupal\user\UserInterface
+ */
+ protected $adminUser;
+
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
- $admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'bypass node access'));
- $this->drupalLogin($admin_user);
+ $this->adminUser = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
+ $this->drupalLogin($this->adminUser);
$this->webUser = $this->drupalCreateUser();
}
@@ -34,25 +43,54 @@ class NodeAccessRebuildNodeGrantsTest extends NodeTestBase {
*/
public function testNodeAccessRebuildNodeGrants() {
\Drupal::service('module_installer')->install(['node_access_test']);
+ \Drupal::state()->set('node_access_test.private', TRUE);
+ node_access_test_add_field(NodeType::load('page'));
$this->resetAll();
- $node = $this->drupalCreateNode(array(
- 'uid' => $this->webUser->id(),
- ));
+ // Create 30 nodes so that _node_access_rebuild_batch_operation() has to run
+ // more than once.
+ for ($i = 0; $i < 30; $i++) {
+ $nodes[] = $this->drupalCreateNode(array(
+ 'uid' => $this->webUser->id(),
+ 'private' => [['value' => 1]]
+ ));
+ }
+ /** @var \Drupal\node\NodeGrantDatabaseStorageInterface $grant_storage */
+ $grant_storage = \Drupal::service('node.grant_storage');
// Default realm access and node records are present.
- $this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
+ foreach ($nodes as $node) {
+ $this->assertTrue($node->private->value);
+ $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the node author.');
+ $this->assertTrue($grant_storage->access($node, 'view', $this->adminUser)->isAllowed(), 'Prior to rebuilding node access the grant storage returns allowed for the admin user.');
+ }
+
$this->assertEqual(1, \Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is an all realm access record');
$this->assertTrue(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions need to be rebuilt');
// Rebuild permissions.
- $this->drupalGet('admin/reports/status/rebuild');
+ $this->drupalGet('admin/reports/status');
+ $this->clickLink(t('Rebuild permissions'));
$this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
$this->assertText(t('The content access permissions have been rebuilt.'));
- // Test if the rebuild has been successful.
+ // Test if the rebuild by user that cannot bypass node access and does not
+ // have access to the nodes has been successful.
+ $this->assertFalse($this->adminUser->hasPermission('bypass node access'));
$this->assertNull(\Drupal::state()->get('node.node_access_needs_rebuild'), 'Node access permissions have been rebuilt');
- $this->assertTrue(\Drupal::service('node.grant_storage')->access($node, 'view', $this->webUser), 'The expected node access records are present');
+ foreach ($nodes as $node) {
+ $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
+ $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
+ }
+ $this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
+
+ // Test an anonymous node access rebuild from code.
+ $this->drupalLogout();
+ node_access_rebuild();
+ foreach ($nodes as $node) {
+ $this->assertTrue($grant_storage->access($node, 'view', $this->webUser)->isAllowed(), 'After rebuilding node access the grant storage returns allowed for the node author.');
+ $this->assertFalse($grant_storage->access($node, 'view', $this->adminUser)->isForbidden(), 'After rebuilding node access the grant storage returns forbidden for the admin user.');
+ }
$this->assertFalse(\Drupal::service('node.grant_storage')->checkAll($this->webUser), 'There is no all realm access record');
}
diff --git a/core/modules/node/src/Tests/NodeAccessRebuildTest.php b/core/modules/node/src/Tests/NodeAccessRebuildTest.php
deleted file mode 100644
index 187f3a059..000000000
--- a/core/modules/node/src/Tests/NodeAccessRebuildTest.php
+++ /dev/null
@@ -1,36 +0,0 @@
-drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports'));
- $this->drupalLogin($web_user);
- $this->webUser = $web_user;
- }
-
- /**
- * Tests rebuilding the node access permissions table.
- */
- function testNodeAccessRebuild() {
- $this->drupalGet('admin/reports/status');
- $this->clickLink(t('Rebuild permissions'));
- $this->drupalPostForm(NULL, array(), t('Rebuild permissions'));
- $this->assertText(t('Content permissions have been rebuilt.'));
- }
-
-}
diff --git a/core/modules/node/src/Tests/NodeCacheTagsTest.php b/core/modules/node/src/Tests/NodeCacheTagsTest.php
index 990f00b24..66932cce0 100644
--- a/core/modules/node/src/Tests/NodeCacheTagsTest.php
+++ b/core/modules/node/src/Tests/NodeCacheTagsTest.php
@@ -38,6 +38,16 @@ class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
return $node;
}
+ /**
+ * {@inheritdoc}
+ */
+ protected function getDefaultCacheContexts() {
+ $defaults = parent::getDefaultCacheContexts();
+ // @see \Drupal\node\Controller\NodeViewController::view()
+ $defaults[] = 'user.roles:anonymous';
+ return $defaults;
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/core/modules/node/src/Tests/NodeViewTest.php b/core/modules/node/src/Tests/NodeViewTest.php
index 5b2200c10..fae056c6f 100644
--- a/core/modules/node/src/Tests/NodeViewTest.php
+++ b/core/modules/node/src/Tests/NodeViewTest.php
@@ -18,14 +18,49 @@ class NodeViewTest extends NodeTestBase {
$this->drupalGet($node->urlInfo());
+ $result = $this->xpath('//link[@rel = "canonical"]');
+ $this->assertEqual($result[0]['href'], $node->url());
+
+ // Link relations are checked for access for anonymous users.
+ $result = $this->xpath('//link[@rel = "version-history"]');
+ $this->assertFalse($result, 'Version history not present for anonymous users without access.');
+
+ $result = $this->xpath('//link[@rel = "edit-form"]');
+ $this->assertFalse($result, 'Edit form not present for anonymous users without access.');
+
+ $this->drupalLogin($this->createUser(['access content']));
+ $this->drupalGet($node->urlInfo());
+
+ $result = $this->xpath('//link[@rel = "canonical"]');
+ $this->assertEqual($result[0]['href'], $node->url());
+
+ // Link relations are present regardless of access for authenticated users.
$result = $this->xpath('//link[@rel = "version-history"]');
$this->assertEqual($result[0]['href'], $node->url('version-history'));
$result = $this->xpath('//link[@rel = "edit-form"]');
$this->assertEqual($result[0]['href'], $node->url('edit-form'));
+ // Give anonymous users access to edit the node. Do this through the UI to
+ // ensure caches are handled properly.
+ $this->drupalLogin($this->rootUser);
+ $edit = [
+ 'anonymous[edit own ' . $node->bundle() . ' content]' => TRUE
+ ];
+ $this->drupalPostForm('admin/people/permissions', $edit, 'Save permissions');
+ $this->drupalLogout();
+
+ // Anonymous user's should now see the edit-form link but not the
+ // version-history link.
+ $this->drupalGet($node->urlInfo());
$result = $this->xpath('//link[@rel = "canonical"]');
$this->assertEqual($result[0]['href'], $node->url());
+
+ $result = $this->xpath('//link[@rel = "version-history"]');
+ $this->assertFalse($result, 'Version history not present for anonymous users without access.');
+
+ $result = $this->xpath('//link[@rel = "edit-form"]');
+ $this->assertEqual($result[0]['href'], $node->url('edit-form'));
}
/**
diff --git a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php
index cb752576d..404e35810 100644
--- a/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php
+++ b/core/modules/node/tests/src/Kernel/Migrate/d7/MigrateNodeTest.php
@@ -47,7 +47,7 @@ class MigrateNodeTest extends MigrateDrupal7TestBase {
'd7_taxonomy_vocabulary',
'd7_field',
'd7_field_instance',
- 'd7_node:test_content_type',
+ 'd7_node',
'd7_node:article',
]);
}
diff --git a/core/modules/simpletest/simpletest.api.php b/core/modules/simpletest/simpletest.api.php
index e5251b78b..d1b5f442c 100644
--- a/core/modules/simpletest/simpletest.api.php
+++ b/core/modules/simpletest/simpletest.api.php
@@ -50,7 +50,7 @@ function hook_test_group_finished() {
* $results The results of the test as gathered by
* \Drupal\simpletest\WebTestBase.
*
- * @see \Drupal\simpletest\WebTestBase->results()
+ * @see \Drupal\simpletest\WebTestBase::results()
*/
function hook_test_finished($results) {
}
diff --git a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
index 4d1906e76..4cd379abc 100644
--- a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
+++ b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
@@ -33,6 +33,11 @@ class BrowserTestBaseTest extends BrowserTestBase {
// Test page contains some text.
$this->assertSession()->pageTextContains('Test page text.');
+ // Check that returned plain text is correct.
+ $text = $this->getTextContent();
+ $this->assertContains('Test page text.', $text);
+ $this->assertNotContains('