Update to Drupal 8.2.2. For more information, see https://www.drupal.org/project/drupal/releases/8.2.2

This commit is contained in:
Pantheon Automation 2016-11-02 11:43:31 -07:00 committed by Greg Anderson
parent 23ffed3665
commit 507b45a0ed
378 changed files with 11434 additions and 5542 deletions

View file

@ -66,13 +66,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
*/
protected $counts = array();
/**
* The object currently being constructed.
*
* @var \stdClass
*/
protected $destinationValues;
/**
* The source.
*
@ -80,13 +73,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
*/
protected $source;
/**
* The current data row retrieved from the source.
*
* @var \stdClass
*/
protected $sourceValues;
/**
* The event dispatcher.
*
@ -94,6 +80,15 @@ class MigrateExecutable implements MigrateExecutableInterface {
*/
protected $eventDispatcher;
/**
* Migration message service.
*
* @todo https://www.drupal.org/node/2822663 Make this protected.
*
* @var \Drupal\migrate\MigrateMessageInterface
*/
public $message;
/**
* Constructs a MigrateExecutable and verifies and sets the memory limit.
*
@ -229,7 +224,12 @@ class MigrateExecutable implements MigrateExecutableInterface {
$save = FALSE;
}
catch (MigrateSkipRowException $e) {
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED);
if ($e->getSaveToMap()) {
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
}
if ($message = trim($e->getMessage())) {
$this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
}
$save = FALSE;
}
@ -263,8 +263,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
}
}
// Reset row properties.
unset($sourceValues, $destinationValues);
$this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
// Check for memory exhaustion.

View file

@ -21,15 +21,59 @@ use Drupal\migrate\Row;
interface MigrateDestinationInterface extends PluginInspectionInterface {
/**
* Get the destination IDs.
* Gets the destination IDs.
*
* To support MigrateIdMap maps, derived destination classes should return
* schema field definition(s) corresponding to the primary key of the
* destination being implemented. These are used to construct the destination
* key fields of the map table for a migration using this destination.
* field definition(s) corresponding to the primary key of the destination
* being implemented. These are used to construct the destination key fields
* of the map table for a migration using this destination.
*
* @return array
* An array of IDs.
* @return array[]
* An associative array of field definitions keyed by field ID. Values are
* associative arrays with a structure that contains the field type ('type'
* key). The other keys are the field storage settings as they are returned
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
* composite destination primary key that is defined by an integer and a
* string, the returned value might look like:
* @code
* return [
* 'id' => [
* 'type' => 'integer',
* 'unsigned' => FALSE,
* 'size' => 'big',
* ],
* 'version' => [
* 'type' => 'string',
* 'max_length' => 64,
* 'is_ascii' => TRUE,
* ],
* ];
* @endcode
* If 'type' points to a field plugin with multiple columns and needs to
* refer to a column different than 'value', the key of that column will be
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
* @code
* return [
* 'format' => [
* 'type' => 'text.format',
* ],
* ];
* @endcode
* Additional custom keys/values, that are not part of field storage
* definition, can be passed in definitions:
* @code
* return [
* 'nid' => [
* 'type' => 'integer',
* 'custom_setting' => 'some_value',
* ],
* ];
* @endcode
*
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
*/
public function getIds();

View file

@ -41,8 +41,7 @@ class MigratePluginManager extends DefaultPluginManager implements MigratePlugin
* 'Drupal\Component\Annotation\PluginID'.
*/
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') {
$plugin_interface = isset($plugin_interface_map[$type]) ? $plugin_interface_map[$type] : NULL;
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, $plugin_interface, $annotation);
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, NULL, $annotation);
$this->alterInfo('migrate_' . $type . '_info');
$this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type);
}

View file

@ -49,9 +49,54 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
* prepareRow() or hook_migrate_prepare_row() to rewrite NULL values to
* appropriate empty values (such as '' or 0).
*
* @return array
* Array keyed by source field name, with values being a schema array
* describing the field (such as ['type' => 'string]).
* @return array[]
* An associative array of field definitions keyed by field ID. Values are
* associative arrays with a structure that contains the field type ('type'
* key). The other keys are the field storage settings as they are returned
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
* composite source primary key that is defined by an integer and a
* string, the returned value might look like:
* @code
* return [
* 'id' => [
* 'type' => 'integer',
* 'unsigned' => FALSE,
* 'size' => 'big',
* ],
* 'version' => [
* 'type' => 'string',
* 'max_length' => 64,
* 'is_ascii' => TRUE,
* ],
* ];
* @endcode
* If 'type' points to a field plugin with multiple columns and needs to
* refer to a column different than 'value', the key of that column will be
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
* @code
* return [
* 'format' => [
* 'type' => 'text.format',
* ],
* ];
* @endcode
* Additional custom keys/values, that are not part of field storage
* definition, can be passed in definitions. The most common setting, passed
* along the ID definition, is 'alias' used by SqlBase source plugin:
* @code
* return [
* 'nid' => [
* 'type' => 'integer',
* 'alias' => 'n',
* ],
* ];
* @endcode
*
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
*/
public function getIds();

View file

@ -125,15 +125,13 @@ class EntityContentBase extends Entity {
*/
public function getIds() {
$id_key = $this->getKey('id');
$ids[$id_key]['type'] = 'integer';
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
if ($this->isTranslationDestination()) {
if ($key = $this->getKey('langcode')) {
$ids[$key]['type'] = 'string';
}
else {
if (!$langcode_key = $this->getKey('langcode')) {
throw new MigrateException('This entity type does not support translation.');
}
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
}
return $ids;
@ -263,4 +261,32 @@ class EntityContentBase extends Entity {
}
}
/**
* Gets the field definition from a specific entity base field.
*
* The method takes the field ID as an argument and returns the field storage
* definition to be used in getIds() by querying the destination entity base
* field definition.
*
* @param string $key
* The field ID key.
*
* @return array
* An associative array with a structure that contains the field type, keyed
* as 'type', together with field storage settings as they are returned by
* FieldStorageDefinitionInterface::getSettings().
*
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
*/
protected function getDefinitionFromEntity($key) {
$entity_type_id = static::getEntityTypeId($this->getPluginId());
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
$definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
$field_definition = $definitions[$key];
return [
'type' => $field_definition->getType(),
] + $field_definition->getSettings();
}
}

View file

@ -73,8 +73,7 @@ class EntityRevision extends EntityContentBase {
*/
public function getIds() {
if ($key = $this->getKey('revision')) {
$ids[$key]['type'] = 'integer';
return $ids;
return [$key => $this->getDefinitionFromEntity($key)];
}
throw new MigrateException('This entity type does not support revisions.');
}

View file

@ -441,20 +441,40 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
* Creates schema from an ID definition.
*
* @param array $id_definition
* A field schema definition. Can be SQL schema or a type data
* based schema. In the latter case, the value of type needs to be
* $typed_data_type.$column.
* The definition of the field having the structure as the items returned by
* MigrateSourceInterface or MigrateDestinationInterface::getIds().
*
* @return array
* The schema definition.
* The database schema definition.
*
* @see \Drupal\migrate\Plugin\MigrateSourceInterface::getIds()
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface::getIds()
*/
protected function getFieldSchema(array $id_definition) {
$type_parts = explode('.', $id_definition['type']);
if (count($type_parts) == 1) {
$type_parts[] = 'value';
}
$schema = BaseFieldDefinition::create($type_parts[0])->getColumns();
return $schema[$type_parts[1]];
unset($id_definition['type']);
// Get the field storage definition.
$definition = BaseFieldDefinition::create($type_parts[0]);
// Get a list of setting keys belonging strictly to the field definition.
$default_field_settings = $definition->getSettings();
// Separate field definition settings from custom settings. Custom settings
// are settings passed in $id_definition that are not part of field storage
// definition settings.
$field_settings = array_intersect_key($id_definition, $default_field_settings);
$custom_settings = array_diff_key($id_definition, $default_field_settings);
// Resolve schema from field storage definition settings.
$schema = $definition
->setSettings($field_settings)
->getColumns()[$type_parts[1]];
// Merge back custom settings.
return $schema + $custom_settings;
}
/**

View file

@ -0,0 +1,117 @@
<?php
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use GuzzleHttp\Client;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Downloads a file from a remote location into the local file system.
*
* @MigrateProcessPlugin(
* id = "download"
* )
*/
class Download extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* The file system service.
*
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* The Guzzle HTTP Client service.
*
* @var \GuzzleHttp\Client
*/
protected $httpClient;
/**
* Constructs a download process plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \GuzzleHttp\Client $http_client
* The HTTP client.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, FileSystemInterface $file_system, Client $http_client) {
$configuration += [
'rename' => FALSE,
'guzzle_options' => [],
];
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->fileSystem = $file_system;
$this->httpClient = $http_client;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('file_system'),
$container->get('http_client')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// If we're stubbing a file entity, return a uri of NULL so it will get
// stubbed by the general process.
if ($row->isStub()) {
return NULL;
}
list($source, $destination) = $value;
// Modify the destination filename if necessary.
$replace = !empty($this->configuration['rename']) ?
FILE_EXISTS_RENAME :
FILE_EXISTS_REPLACE;
$final_destination = file_destination($destination, $replace);
// Try opening the file first, to avoid calling file_prepare_directory()
// unnecessarily. We're suppressing fopen() errors because we want to try
// to prepare the directory before we give up and fail.
$destination_stream = @fopen($final_destination, 'w');
if (!$destination_stream) {
// If fopen didn't work, make sure there's a writable directory in place.
$dir = $this->fileSystem->dirname($final_destination);
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
throw new MigrateException("Could not create or write to directory '$dir'");
}
// Let's try that fopen again.
$destination_stream = @fopen($final_destination, 'w');
if (!$destination_stream) {
throw new MigrateException("Could not write to file '$final_destination'");
}
}
// Stream the request body directly to the final destination stream.
$this->configuration['guzzle_options']['sink'] = $destination_stream;
// Make the request. Guzzle throws an exception for anything other than 200.
$this->httpClient->get($source, $this->configuration['guzzle_options']);
return $final_destination;
}
}

View file

@ -14,7 +14,8 @@ use Drupal\migrate\Row;
* @link https://www.drupal.org/node/2152731 Online handbook documentation for extract process plugin @endlink
*
* @MigrateProcessPlugin(
* id = "extract"
* id = "extract",
* handle_multiples = TRUE
* )
*/
class Extract extends ProcessPluginBase {

View file

@ -8,6 +8,7 @@ use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Plugin\MigrateProcessInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
@ -35,6 +36,13 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
*/
protected $fileSystem;
/**
* An instance of the download process plugin.
*
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
*/
protected $downloadPlugin;
/**
* Constructs a file_copy process plugin.
*
@ -48,8 +56,10 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
* The stream wrapper manager service.
* @param \Drupal\Core\File\FileSystemInterface $file_system
* The file system service.
* @param \Drupal\migrate\Plugin\MigrateProcessInterface $download_plugin
* An instance of the download plugin for handling remote URIs.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
$configuration += array(
'move' => FALSE,
'rename' => FALSE,
@ -58,6 +68,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->streamWrapperManager = $stream_wrappers;
$this->fileSystem = $file_system;
$this->downloadPlugin = $download_plugin;
}
/**
@ -69,7 +80,8 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
$plugin_id,
$plugin_definition,
$container->get('stream_wrapper_manager'),
$container->get('file_system')
$container->get('file_system'),
$container->get('plugin.manager.migrate.process')->createInstance('download')
);
}
@ -84,8 +96,14 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
}
list($source, $destination) = $value;
// If the source path or URI represents a remote resource, delegate to the
// download plugin.
if (!$this->isLocalUri($source)) {
return $this->downloadPlugin->transform($value, $migrate_executable, $row, $destination_property);
}
// Ensure the source file exists, if it's a local URI or path.
if ($this->isLocalUri($source) && !file_exists($source)) {
if (!file_exists($source)) {
throw new MigrateException("File '$source' does not exist");
}
@ -128,20 +146,14 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
* File destination on success, FALSE on failure.
*/
protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
if ($this->configuration['move']) {
return file_unmanaged_move($source, $destination, $replace);
}
// Check if there is a destination available for copying. If there isn't,
// it already exists at the destination and the replace flag tells us to not
// replace it. In that case, return the original destination.
if (!($final_destination = file_destination($destination, $replace))) {
return $destination;
}
// We can't use file_unmanaged_copy because it will break with remote Urls.
if (@copy($source, $final_destination)) {
return $final_destination;
}
return FALSE;
$function = 'file_unmanaged_' . ($this->configuration['move'] ? 'move' : 'copy');
return $function($source, $destination, $replace);
}
/**
@ -187,8 +199,6 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
/**
* Determines if the source and destination URIs represent identical paths.
*
* If either URI is a remote stream, will return FALSE.
*
* @param string $source
* The source URI.
* @param string $destination
@ -199,10 +209,7 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
* otherwise FALSE.
*/
protected function isLocationUnchanged($source, $destination) {
if ($this->isLocalUri($source) && $this->isLocalUri($destination)) {
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
}
return FALSE;
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
}
/**
@ -219,6 +226,13 @@ class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterf
*/
protected function isLocalUri($uri) {
$scheme = $this->fileSystem->uriScheme($uri);
// The vfs scheme is vfsStream, which is used in testing. vfsStream is a
// simulated file system that exists only in memory, but should be treated
// as a local resource.
if ($scheme == 'vfs') {
$scheme = FALSE;
}
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
}

View file

@ -207,6 +207,9 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
catch (MigrateSkipRowException $e) {
$skip = TRUE;
$save_to_map = $e->getSaveToMap();
if ($message = trim($e->getMessage())) {
$this->idMap->saveMessage($row->getSourceIdValues(), $message, MigrationInterface::MESSAGE_INFORMATIONAL);
}
}
// We're explicitly skipping this row - keep track in the map table.

View file

@ -91,7 +91,7 @@ class Row {
* @throws \InvalidArgumentException
* Thrown when a source ID property does not exist.
*/
public function __construct(array $values, array $source_ids, $is_stub = FALSE) {
public function __construct(array $values = [], array $source_ids = [], $is_stub = FALSE) {
$this->source = $values;
$this->sourceIds = $source_ids;
$this->isStub = $is_stub;