Drupal 8.0.0 beta 12. More info: https://www.drupal.org/node/2514176

This commit is contained in:
Pantheon Automation 2015-08-17 17:00:26 -07:00 committed by Greg Anderson
commit 9921556621
13277 changed files with 1459781 additions and 0 deletions

View file

@ -0,0 +1,77 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\Derivative\MigrateEntity.
*/
namespace Drupal\migrate\Plugin\Derivative;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MigrateEntity implements ContainerDeriverInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* The entity definitions
*
* @var \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected $entityDefinitions;
/**
* Constructs a MigrateEntity object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
* A list of entity definition objects.
*/
public function __construct(array $entity_definitions) {
$this->entityDefinitions = $entity_definitions;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')->getDefinitions()
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
return $this->derivatives[$derivative_id];
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
$class = is_subclass_of($entity_info->getClass(), 'Drupal\Core\Config\Entity\ConfigEntityInterface') ?
'Drupal\migrate\Plugin\migrate\destination\EntityConfigBase' :
'Drupal\migrate\Plugin\migrate\destination\EntityContentBase';
$this->derivatives[$entity_type] = array(
'id' => "entity:$entity_type",
'class' => $class,
'requirements_met' => 1,
'provider' => $entity_info->getProvider(),
);
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,76 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\Derivative\MigrateEntityRevision.
*/
namespace Drupal\migrate\Plugin\Derivative;
use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
class MigrateEntityRevision implements ContainerDeriverInterface {
/**
* List of derivative definitions.
*
* @var array
*/
protected $derivatives = array();
/**
* The entity definitions
*
* @var \Drupal\Core\Entity\EntityTypeInterface[]
*/
protected $entityDefinitions;
/**
* Constructs a MigrateEntity object.
*
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_definitions
* A list of entity definition objects.
*/
public function __construct(array $entity_definitions) {
$this->entityDefinitions = $entity_definitions;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('entity.manager')->getDefinitions()
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinition($derivative_id, $base_plugin_definition) {
if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
return $this->derivatives[$derivative_id];
}
$this->getDerivativeDefinitions($base_plugin_definition);
return $this->derivatives[$derivative_id];
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions($base_plugin_definition) {
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
if ($entity_info->getKey('revision')) {
$this->derivatives[$entity_type] = array(
'id' => "entity_revision:$entity_type",
'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityRevision',
'requirements_met' => 1,
'provider' => $entity_info->getProvider(),
);
}
}
return $this->derivatives;
}
}

View file

@ -0,0 +1,109 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigrateDestinationInterface.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
/**
* Destinations are responsible for persisting source data into the destination
* Drupal.
*
* @see \Drupal\migrate\Plugin\destination\DestinationBase
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
* @see \Drupal\migrate\Annotation\MigrateDestination
* @see plugin_api
*
* @ingroup migration
*/
interface MigrateDestinationInterface extends PluginInspectionInterface {
/**
* Get 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.
*
* @return array
* An array of ids.
*/
public function getIds();
/**
* Returns an array of destination fields.
*
* Derived classes must implement fields(), returning a list of available
* destination fields.
*
* @todo Review the cases where we need the Migration parameter,
* can we avoid that?
*
* @param \Drupal\migrate\Entity\MigrationInterface $migration
* (optional) the migration containing this destination.
*
* @return array
* - Keys: machine names of the fields
* - Values: Human-friendly descriptions of the fields.
*/
public function fields(MigrationInterface $migration = NULL);
/**
* Allows pre-processing of an import.
*
* Derived classes may implement preImport() to do any processing they need
* done before over all source rows.
*/
public function preImport();
/**
* Allows pre-processing of a rollback.
*/
public function preRollback();
/**
* Allows post-processing of an import.
*
* Derived classes may implement postImport(), to do any processing they need
* done after looping over all source rows.
*/
public function postImport();
/**
* Allows post-processing of a rollback.
*/
public function postRollback();
/**
* Import the row.
*
* Derived classes must implement import(), to construct one new object
* (pre-populated) using ID mappings in the Migration).
*
* @param \Drupal\migrate\Row $row
* The row object.
* @param array $old_destination_id_values
* The old destination ids.
*
* @return mixed
* The entity id or an indication of success.
*/
public function import(Row $row, array $old_destination_id_values = array());
/**
* Delete the specified IDs from the target Drupal.
*
* @param array $destination_identifiers
* The destination ids to delete.
*/
public function rollbackMultiple(array $destination_identifiers);
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigrateDestinationPluginManager.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\migrate\Entity\MigrationInterface;
/**
* Plugin manager for migrate destination plugins.
*
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
* @see \Drupal\migrate\Plugin\destination\DestinationBase
* @see \Drupal\migrate\Annotation\MigrateDestination
* @see plugin_api
*
* @ingroup migration
*/
class MigrateDestinationPluginManager extends MigratePluginManager {
/**
* The theme handler
*
* @var \Drupal\Core\Extension\ThemeHandlerInterface
*/
protected $themeHandler;
/**
* An associative array where the keys are the enabled modules and themes.
*
* @var array
*/
protected $providers;
/**
* {@inheritdoc}
*/
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, EntityManagerInterface $entity_manager, $annotation = 'Drupal\migrate\Annotation\MigrateDestination') {
parent::__construct($type, $namespaces, $cache_backend, $module_handler, $annotation);
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*
* A specific createInstance method is necessary to pass the migration on.
*/
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
if (substr($plugin_id, 0, 7) == 'entity:' && !$this->entityManager->getDefinition(substr($plugin_id, 7), FALSE)) {
$plugin_id = 'null';
}
return parent::createInstance($plugin_id, $configuration, $migration);
}
}

View file

@ -0,0 +1,242 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigrateIdMapInterface.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\migrate\Row;
/**
* Defines an interface for migrate ID mappings.
*
* Migrate ID mappings maintain a relation between source ID and destination ID
* for audit and rollback purposes.
*/
interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
/**
* Codes reflecting the current status of a map row.
*/
const STATUS_IMPORTED = 0;
const STATUS_NEEDS_UPDATE = 1;
const STATUS_IGNORED = 2;
const STATUS_FAILED = 3;
/**
* Codes reflecting how to handle the destination item on rollback.
*/
const ROLLBACK_DELETE = 0;
const ROLLBACK_PRESERVE = 1;
/**
* Saves a mapping from the source identifiers to the destination identifiers.
*
* Called upon import of one row, we record a mapping from the source ID
* to the destination ID. Also may be called, setting the third parameter to
* NEEDS_UPDATE, to signal an existing record should be re-migrated.
*
* @param \Drupal\migrate\Row $row
* The raw source data. We use the ID map derived from the source object
* to get the source identifier values.
* @param array $destination_id_values
* An array of destination identifier values.
* @param int $status
* Status of the source row in the map.
* @param int $rollback_action
* How to handle the destination object on rollback.
*/
public function saveIdMapping(Row $row, array $destination_id_values, $status = self::STATUS_IMPORTED, $rollback_action = self::ROLLBACK_DELETE);
/**
* Saves a message related to a source record in the migration message table.
*
* @param array $source_id_values
* The source identifier values of the record in error.
* @param string $message
* The message to record.
* @param int $level
* Optional message severity (defaults to MESSAGE_ERROR).
*/
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR);
/**
* Prepares to run a full update.
*
* Prepares this migration to run as an update - that is, in addition to
* unmigrated content (source records not in the map table) being imported,
* previously-migrated content will also be updated in place by marking all
* previously-imported content as ready to be re-imported.
*/
public function prepareUpdate();
/**
* Returns the number of processed items in the map.
*
* @return int
* The count of records in the map table.
*/
public function processedCount();
/**
* Returns the number of imported items in the map.
*
* @return int
* The number of imported items.
*/
public function importedCount();
/**
* Returns a count of items which are marked as needing update.
*
* @return int
* The number of items which need updating.
*/
public function updateCount();
/**
* Returns the number of items that failed to import.
*
* @return int
* The number of items that errored out.
*/
public function errorCount();
/**
* Returns the number of messages saved.
*
* @return int
* The number of messages.
*/
public function messageCount();
/**
* Deletes the map and message entries for a given source record.
*
* @param array $source_id_values
* The source identifier values of the record to delete.
* @param bool $messages_only
* TRUE to only delete the migrate messages.
*/
public function delete(array $source_id_values, $messages_only = FALSE);
/**
* Deletes the map and message table entries for a given destination row.
*
* @param array $destination_id_values
* The destination identifier values we should do the deletes for.
*/
public function deleteDestination(array $destination_id_values);
/**
* Deletes the map and message entries for a set of given source records.
*
* @param array $source_id_values
* The identifier values of the sources we should do the deletes for. Each
* array member is an array of identifier values for one source row.
*/
public function deleteBulk(array $source_id_values);
/**
* Clears all messages from the map.
*/
public function clearMessages();
/**
* Retrieves a row from the map table based on source identifier values.
*
* @param array $source_id_values
* The source identifier values of the record to retrieve.
*
* @return array
* The raw row data as an associative array.
*/
public function getRowBySource(array $source_id_values);
/**
* Retrieves a row by the destination identifiers.
*
* @param array $destination_id_values
* The destination identifier values of the record to retrieve.
*
* @return array
* The row(s) of data.
*/
public function getRowByDestination(array $destination_id_values);
/**
* Retrieves an array of map rows marked as needing update.
*
* @param int $count
* The maximum number of rows to return.
*
* @return array
* Array of map row objects that need updating.
*/
public function getRowsNeedingUpdate($count);
/**
* Looks up the source identifier.
*
* Given a (possibly multi-field) destination identifier value, return the
* (possibly multi-field) source identifier value mapped to it.
*
* @param array $destination_id_values
* The destination identifier values of the record.
*
* @return array
* The source identifier values of the record, or NULL on failure.
*/
public function lookupSourceID(array $destination_id_values);
/**
* Looks up the destination identifier.
*
* Given a (possibly multi-field) source identifier value, return the
* (possibly multi-field) destination identifier value it is mapped to.
*
* @param array $source_id_values
* The source identifier values of the record.
*
* @return array
* The destination identifier values of the record, or NULL on failure.
*/
public function lookupDestinationId(array $source_id_values);
/**
* Removes any persistent storage used by this map.
*
* For example, remove the map and message tables.
*/
public function destroy();
/**
* Gets the qualified map table.
*
* @todo Remove this as this is SQL only and so doesn't belong to the interface.
*/
public function getQualifiedMapTableName();
/**
* Sets the migrate message.
*
* @param \Drupal\migrate\MigrateMessageInterface $message
* The message to display.
*/
public function setMessage(MigrateMessageInterface $message);
/**
* Sets a specified record to be updated, if it exists.
*
* @param array $source_id_values
* The source identifier values of the record.
*/
public function setUpdate(array $source_id_values);
}

View file

@ -0,0 +1,89 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigratePluginManager.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\Factory\DefaultFactory;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Plugin\DefaultPluginManager;
use Drupal\migrate\Entity\MigrationInterface;
/**
* Manages migrate plugins.
*
* @see hook_migrate_info_alter()
* @see \Drupal\migrate\Annotation\MigrateSource
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
* @see \Drupal\migrate\Plugin\migrate\process\ProcessPluginBase
* @see plugin_api
*
* @ingroup migration
*/
class MigratePluginManager extends DefaultPluginManager {
/**
* Constructs a MigratePluginManager object.
*
* @param string $type
* The type of the plugin: row, source, process, destination, entity_field,
* id_map.
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
* @param string $annotation
* The annotation class name.
*/
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);
$this->alterInfo('migrate_' . $type . '_info');
$this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type);
}
/**
* {@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);
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
// If the plugin provides a factory method, pass the container to it.
if (is_subclass_of($plugin_class, 'Drupal\Core\Plugin\ContainerFactoryPluginInterface')) {
$plugin = $plugin_class::create(\Drupal::getContainer(), $configuration, $plugin_id, $plugin_definition, $migration);
}
else {
$plugin = new $plugin_class($configuration, $plugin_id, $plugin_definition, $migration);
}
return $plugin;
}
/**
* Helper for the plugin type to interface map.
*
* @return array
* An array map from plugin type to interface.
*/
protected function getPluginInterfaceMap() {
return [
'destination' => 'Drupal\migrate\Plugin\MigrateDestinationInterface',
'process' => 'Drupal\migrate\Plugin\MigrateProcessInterface',
'source' => 'Drupal\migrate\Plugin\MigrateSourceInterface',
'id_map' => 'Drupal\migrate\Plugin\MigrateIdMapInterface',
'entity_field' => 'Drupal\migrate\Plugin\MigrateEntityDestinationFieldInterface',
];
}
}

View file

@ -0,0 +1,62 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigrateProcessInterface.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* An interface for migrate process plugins.
*
* A process plugin can use any number of methods instead of (but not in
* addition to) transform with the same arguments and then the plugin
* configuration needs to provide the name of the method to be called via the
* "method" key. See \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty and
* migrate.migration.d6_field_instance_widget_settings.yml for examples.
*
* @see \Drupal\migrate\Plugin\MigratePluginManager
* @see \Drupal\migrate\ProcessPluginBase
* @see \Drupal\migrate\Annotation\MigrateProcessPlugin
* @see plugin_api
*
* @ingroup migration
*/
interface MigrateProcessInterface extends PluginInspectionInterface {
/**
* Performs the associated process.
*
* @param mixed $value
* The value to be transformed.
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
* The migration in which this process is being executed.
* @param \Drupal\migrate\Row $row
* The row from the source to process. Normally, just transforming the
* value is adequate but very rarely you might need to change two columns
* at the same time or something like that.
* @param string $destination_property
* The destination property currently worked on. This is only used
* together with the $row above.
*
* @return string|array
* The newly transformed value.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property);
/**
* Indicates whether the returned value requires multiple handling.
*
* @return bool
* TRUE when the returned value contains a list of values to be processed.
* For example, when the 'source' property is a string and the value found
* is an array.
*/
public function multiple();
}

View file

@ -0,0 +1,65 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\MigrateSourceInterface.
*/
namespace Drupal\migrate\Plugin;
use Drupal\Component\Plugin\PluginInspectionInterface;
use Drupal\migrate\Row;
/**
* Defines an interface for migrate sources.
*
* @see \Drupal\migrate\Plugin\MigratePluginManager
* @see \Drupal\migrate\Annotation\MigrateSource
* @see \Drupal\migrate\Plugin\migrate\source\SourcePluginBase
* @see plugin_api
*
* @ingroup migration
*/
interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspectionInterface {
/**
* Returns available fields on the source.
*
* @return array
* Available fields in the source, keys are the field machine names as used
* in field mappings, values are descriptions.
*/
public function fields();
/**
* Returns the iterator that will yield the row arrays to be processed.
*
* @return \Iterator
* The iterator object.
*
* @throws \Exception
* Cannot obtain a valid iterator.
*/
public function getIterator();
/**
* Add additional data to the row.
*
* @param \Drupal\Migrate\Row $row
* The row object.
*
* @return bool
* FALSE if this row needs to be skipped.
*/
public function prepareRow(Row $row);
public function __toString();
/**
* Get the source ids.
*
* @return array
* The source ids.
*/
public function getIds();
}

View file

@ -0,0 +1,23 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\RequirementsInterface.
*/
namespace Drupal\migrate\Plugin;
/**
* An interface to check for a migrate plugin requirements.
*/
interface RequirementsInterface {
/**
* Checks if requirements for this plugin are OK.
*
* @throws \Drupal\migrate\Exception\RequirementsException
* Thrown when requirements are not met.
*/
public function checkRequirements();
}

View file

@ -0,0 +1,32 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\SourceEntityInterface.
*/
namespace Drupal\migrate\Plugin;
/**
* Interface for sources providing an entity.
*/
interface SourceEntityInterface {
/**
* Whether this migration has a bundle migration.
*
* @return bool
* TRUE when the bundle_migration key is required.
*/
public function bundleMigrationRequired();
/**
* The entity type id (user, node etc).
*
* This function is used when bundleMigrationRequired() is FALSE.
*
* @return string
* The entity type id.
*/
public function entityTypeId();
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\Book.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "book",
* provider = "book"
* )
*/
class Book extends EntityContentBase {
/**
* {@inheritdoc}
*/
protected static function getEntityTypeId($plugin_id) {
return 'node';
}
/**
* {@inheritdoc}
*/
protected function updateEntity(EntityInterface $entity, Row $row) {
$entity->book = $row->getDestinationProperty('book');
}
}

View file

@ -0,0 +1,71 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\ComponentEntityDisplayBase.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
abstract class ComponentEntityDisplayBase extends DestinationBase {
const MODE_NAME = '';
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$values = array();
// array_intersect_key() won't work because the order is important because
// this is also the return value.
foreach (array_keys($this->getIds()) as $id) {
$values[$id] = $row->getDestinationProperty($id);
}
$entity = $this->getEntity($values['entity_type'], $values['bundle'], $values[static::MODE_NAME]);
if (!$row->getDestinationProperty('hidden')) {
$entity->setComponent($values['field_name'], $row->getDestinationProperty('options') ?: array());
}
else {
$entity->removeComponent($values['field_name']);
}
$entity->save();
return array_values($values);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['entity_type']['type'] = 'string';
$ids['bundle']['type'] = 'string';
$ids[static::MODE_NAME]['type'] = 'string';
$ids['field_name']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
// This is intentionally left empty.
}
/**
* Get the entity.
*
* @param string $entity_type
* The entity type to retrieve.
* @param string $bundle
* The entity bundle.
* @param string $mode
* The display mode.
*
* @return \Drupal\Core\Entity\Display\EntityDisplayInterface
* The entity display object.
*/
protected abstract function getEntity($entity_type, $bundle, $mode);
}

View file

@ -0,0 +1,121 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\Config.
*
* Provides Configuration Management destination plugin.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\Config as ConfigObject;
/**
* Persist data to the config system.
*
* When a property is NULL, the default is used unless the configuration option
* 'store null' is set to TRUE.
*
* @MigrateDestination(
* id = "config"
* )
*/
class Config extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
use DependencyTrait;
/**
* The config object.
*
* @var \Drupal\Core\Config\Config
*/
protected $config;
/**
* Constructs a Config destination object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\migrate\Entity\MigrationInterface $migration
* The migration entity.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The configuration factory.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->config = $config_factory->getEditable($configuration['config_name']);
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('config.factory')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
foreach ($row->getRawDestination() as $key => $value) {
if (isset($value) || !empty($this->configuration['store null'])) {
$this->config->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $key), $value);
}
}
$this->config->save();
return TRUE;
}
/**
* Throw an exception because config can not be rolled back.
*
* @param array $destination_keys
* The array of destination ids to roll back.
*
* @throws \Drupal\migrate\MigrateException
*/
public function rollbackMultiple(array $destination_keys) {
throw new MigrateException('Configuration can not be rolled back');
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
// @todo Dynamically fetch fields using Config Schema API.
}
/**
* {@inheritdoc}
*/
public function getIds() {
return array();
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$provider = explode('.', $this->config->getName(), 2)[0];
$this->addDependency('module', $provider);
return $this->dependencies;
}
}

View file

@ -0,0 +1,118 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\DestinationBase.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Plugin\PluginBase;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Exception\RequirementsException;
use Drupal\migrate\Plugin\MigrateDestinationInterface;
use Drupal\migrate\Plugin\RequirementsInterface;
/**
* Base class for migrate destination classes.
*
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface
* @see \Drupal\migrate\Plugin\MigrateDestinationPluginManager
* @see \Drupal\migrate\Annotation\MigrateDestination
* @see plugin_api
*
* @ingroup migration
*/
abstract class DestinationBase extends PluginBase implements MigrateDestinationInterface, RequirementsInterface {
/**
* The migration.
*
* @var \Drupal\migrate\Entity\MigrationInterface
*/
protected $migration;
/**
* Constructs an entity destination plugin.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param MigrationInterface $migration
* The migration.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
}
/**
* {@inheritdoc}
*/
public function checkRequirements() {
if (empty($this->pluginDefinition['requirements_met'])) {
throw new RequirementsException();
}
}
/**
* Modify the Row before it is imported.
*/
public function preImport() {
// By default we do nothing.
}
/**
* Modify the Row before it is rolled back.
*/
public function preRollback() {
// By default we do nothing.
}
/**
* {@inheritdoc}
*/
public function postImport() {
// By default we do nothing.
}
/**
* {@inheritdoc}
*/
public function postRollback() {
// By default we do nothing.
}
/**
* {@inheritdoc}
*/
public function rollbackMultiple(array $destination_identifiers) {
// By default we do nothing.
}
/**
* {@inheritdoc}
*/
public function getCreated() {
// TODO: Implement getCreated() method.
}
/**
* {@inheritdoc}
*/
public function getUpdated() {
// TODO: Implement getUpdated() method.
}
/**
* {@inheritdoc}
*/
public function resetStats() {
// TODO: Implement resetStats() method.
}
}

View file

@ -0,0 +1,182 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\Entity.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Component\Plugin\DependentPluginInterface;
use Drupal\Core\Entity\DependencyTrait;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateDestination(
* id = "entity",
* deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntity"
* )
*/
abstract class Entity extends DestinationBase implements ContainerFactoryPluginInterface, DependentPluginInterface {
use DependencyTrait;
/**
* The entity storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $storage;
/**
* The list of the bundles of this entity type.
*
* @var array
*/
protected $bundles;
/**
* Construct a new entity.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param MigrationInterface $migration
* The migration.
* @param EntityStorageInterface $storage
* The storage for this entity type.
* @param array $bundles
* The list of bundles this entity type has.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->storage = $storage;
$this->bundles = $bundles;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type_id = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type_id),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type_id))
);
}
/**
* Finds the entity type from configuration or plugin id.
*
* @param string $plugin_id
* The plugin id.
*
* @return string
* The entity type.
*/
protected static function getEntityTypeId($plugin_id) {
// Remove "entity:"
return substr($plugin_id, 7);
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
// TODO: Implement fields() method.
}
/**
* Creates or loads an entity.
*
* @param \Drupal\migrate\Row $row
* The row object.
* @param array $old_destination_id_values
* The old destination ids.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity we're importing into.
*/
protected function getEntity(Row $row, array $old_destination_id_values) {
$entity_id = $old_destination_id_values ? reset($old_destination_id_values) : $this->getEntityId($row);
if (!empty($entity_id) && ($entity = $this->storage->load($entity_id))) {
$this->updateEntity($entity, $row);
}
else {
$values = $row->getDestination();
// Stubs might not have the bundle specified.
if ($row->isStub()) {
$values = $this->processStubValues($values);
}
$entity = $this->storage->create($values);
$entity->enforceIsNew();
}
return $entity;
}
/**
* Get the entity id of the row.
*
* @param \Drupal\migrate\Row $row
* The row of data.
* @return string
* The entity id for the row we're importing.
*/
protected function getEntityId(Row $row) {
return $row->getDestinationProperty($this->getKey('id'));
}
/**
* Process the stub values.
*
* @param array $values
* An array of destination values.
*
* @return array
* The processed stub values.
*/
protected function processStubValues(array $values) {
$values = array_intersect_key($values, $this->getIds());
$bundle_key = $this->getKey('bundle');
if ($bundle_key && !isset($values[$bundle_key])) {
$values[$bundle_key] = reset($this->bundles);
}
return $values;
}
/**
* Returns a specific entity key.
*
* @param string $key
* The name of the entity key to return.
*
* @return string|bool
* The entity key, or FALSE if it does not exist.
*
* @see \Drupal\Core\Entity\EntityTypeInterface::getKeys()
*/
protected function getKey($key) {
return $this->storage->getEntityType()->getKey($key);
}
/**
* {@inheritdoc}
*/
public function calculateDependencies() {
$this->addDependency('module', $this->storage->getEntityType()->getProvider());
return $this->dependencies;
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityBaseFieldOverride.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:base_field_override"
* )
*/
class EntityBaseFieldOverride extends EntityConfigBase {
/**
* {@inheritdoc}
*/
protected function getEntityId(Row $row) {
$entity_type = $row->getDestinationProperty('entity_type');
$bundle = $row->getDestinationProperty('bundle');
$field_name = $row->getDestinationProperty('field_name');
return "$entity_type.$bundle.$field_name";
}
}

View file

@ -0,0 +1,90 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityComment.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\State\StateInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateDestination(
* id = "entity:comment"
* )
*/
class EntityComment extends EntityContentBase {
/**
* The state storage object.
*
* @var \Drupal\Core\State\StateInterface
*/
protected $state;
/**
* Builds an comment entity destination.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param MigrationInterface $migration
* The migration.
* @param EntityStorageInterface $storage
* The storage for this entity type.
* @param array $bundles
* The list of bundles this entity type has.
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
* The migrate plugin manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\State\StateInterface $state
* The state storage object.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StateInterface $state) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
$this->state = $state;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
$container->get('entity.manager'),
$container->get('state')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
if ($row->isStub() && ($state = $this->state->get('comment.maintain_entity_statistics', 0))) {
$this->state->set('comment.maintain_entity_statistics', 0);
}
$return = parent::import($row, $old_destination_id_values);
if ($row->isStub() && $state) {
$this->state->set('comment.maintain_entity_statistics', $state);
}
return $return;
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityCommentType.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:comment_type"
* )
*/
class EntityCommentType extends EntityConfigBase {
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$entity_ids = parent::import($row, $old_destination_id_values);
\Drupal::service('comment.manager')->addBodyField(reset($entity_ids));
return $entity_ids;
}
}

View file

@ -0,0 +1,123 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityConfigBase.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
/**
* Class for importing configuration entities.
*
* This class serves as the import class for most configuration entities.
* It can be necessary to provide a specific entity class if the configuration
* entity has a compound id (see EntityFieldEntity) or it has specific setter
* methods (see EntityDateFormat). When implementing an entity destination for
* the latter case, make sure to add a test not only for importing but also
* for re-importing (if that is supported).
*/
class EntityConfigBase extends Entity {
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
if ($row->isStub()) {
throw new MigrateException('Config entities can not be stubbed.');
}
$ids = $this->getIds();
$id_key = $this->getKey('id');
if (count($ids) > 1) {
// Ids is keyed by the key name so grab the keys.
$id_keys = array_keys($ids);
if (!$row->getDestinationProperty($id_key)) {
// Set the id into the destination in for form "val1.val2.val3".
$row->setDestinationProperty($id_key, $this->generateId($row, $id_keys));
}
}
$entity = $this->getEntity($row, $old_destination_id_values);
$entity->save();
if (count($ids) > 1) {
// This can only be a config entity, content entities have their id key
// and that's it.
$return = array();
foreach ($id_keys as $id_key) {
$return[] = $entity->get($id_key);
}
return $return;
}
return array($entity->id());
}
/**
* {@inheritdoc}
*/
public function getIds() {
$id_key = $this->getKey('id');
$ids[$id_key]['type'] = 'string';
return $ids;
}
/**
* Updates an entity with the contents of a row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to update.
* @param \Drupal\migrate\Row $row
* The row object to update from.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {
foreach ($row->getRawDestination() as $property => $value) {
$this->updateEntityProperty($entity, explode(Row::PROPERTY_SEPARATOR, $property), $value);
}
}
/**
* Updates a (possible nested) entity property with a value.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The config entity.
* @param array $parents
* The array of parents.
* @param string|object $value
* The value to update to.
*/
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
$top_key = array_shift($parents);
$entity_value = $entity->get($top_key);
if (is_array($entity_value)) {
NestedArray::setValue($entity_value, $parents, $value);
}
else {
$entity_value = $value;
}
$entity->set($top_key, $entity_value);
}
/**
* Generate an entity id.
*
* @param \Drupal\migrate\Row $row
* The current row.
* @param array $ids
* The destination ids.
*
* @return string
* The generated entity id.
*/
protected function generateId(Row $row, array $ids) {
$id_values = array();
foreach ($ids as $id) {
$id_values[] = $row->getDestinationProperty($id);
}
return implode('.', $id_values);
}
}

View file

@ -0,0 +1,122 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityContentBase.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\TypedData\TypedDataInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The destination class for all content entities lacking a specific class.
*/
class EntityContentBase extends Entity {
/**
* Entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* Constructs a content entity.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\migrate\Entity\MigrationInterface $migration
* The migration entity.
* @param \Drupal\Core\Entity\EntityStorageInterface $storage
* The storage for this entity type.
* @param array $bundles
* The list of bundles this entity type has.
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
* The plugin manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
$this->entityManager = $entity_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
$container->get('entity.manager')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$entity = $this->getEntity($row, $old_destination_id_values);
return $this->save($entity, $old_destination_id_values);
}
/**
* Save the entity.
*
* @param \Drupal\Core\Entity\ContentEntityInterface $entity
* The content entity.
* @param array $old_destination_id_values
* An array of destination id values.
*
* @return array
* An array containing the entity id.
*/
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
$entity->save();
return array($entity->id());
}
/**
* {@inheritdoc}
*/
public function getIds() {
$id_key = $this->getKey('id');
$ids[$id_key]['type'] = 'integer';
return $ids;
}
/**
* Update an entity with the new values from row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to update.
* @param \Drupal\migrate\Row $row
* The row object to update from.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {
foreach ($row->getDestination() as $field_name => $values) {
$field = $entity->$field_name;
if ($field instanceof TypedDataInterface) {
$field->setValue($values);
}
}
}
}

View file

@ -0,0 +1,34 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityDateFormat.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityInterface;
/**
* @MigrateDestination(
* id = "entity:date_format"
* )
*/
class EntityDateFormat extends EntityConfigBase {
/**
* {@inheritdoc}
*
* @param \Drupal\Core\Datetime\DateFormatInterface $entity
* The date entity.
*/
protected function updateEntityProperty(EntityInterface $entity, array $parents, $value) {
if ($parents[0] == 'pattern') {
$entity->setPattern($value);
}
else {
parent::updateEntityProperty($entity, $parents, $value);
}
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFieldInstance.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:field_config"
* )
*/
class EntityFieldInstance extends EntityConfigBase {
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['entity_type']['type'] = 'string';
$ids['bundle']['type'] = 'string';
$ids['field_name']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFieldStorageConfig.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
/**
* @MigrateDestination(
* id = "entity:field_storage_config"
* )
*/
class EntityFieldStorageConfig extends EntityConfigBase {
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['entity_type']['type'] = 'string';
$ids['field_name']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,245 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityFile.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateException;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Every migration that uses this destination must have an optional
* dependency on the d6_file migration to ensure it runs first.
*
* @MigrateDestination(
* id = "entity:file"
* )
*/
class EntityFile extends EntityContentBase {
/**
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
*/
protected $streamWrapperManager;
/**
* @var \Drupal\Core\File\FileSystemInterface
*/
protected $fileSystem;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
$configuration += array(
'source_base_path' => '',
'source_path_property' => 'filepath',
'destination_path_property' => 'uri',
'move' => FALSE,
'urlencode' => FALSE,
);
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
$this->streamWrapperManager = $stream_wrappers;
$this->fileSystem = $file_system;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
$container->get('entity.manager'),
$container->get('stream_wrapper_manager'),
$container->get('file_system')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$file = $row->getSourceProperty($this->configuration['source_path_property']);
$destination = $row->getDestinationProperty($this->configuration['destination_path_property']);
$source = $this->configuration['source_base_path'] . $file;
// Ensure the source file exists, if it's a local URI or path.
if ($this->isLocalUri($source) && !file_exists($source)) {
throw new MigrateException(SafeMarkup::format('File @source does not exist.', ['@source' => $source]));
}
// If the start and end file is exactly the same, there is nothing to do.
if ($this->isLocationUnchanged($source, $destination)) {
return parent::import($row, $old_destination_id_values);
}
$replace = $this->getOverwriteMode($row);
$success = $this->writeFile($source, $destination, $replace);
if (!$success) {
$dir = $this->getDirectory($destination);
if (file_prepare_directory($dir, FILE_CREATE_DIRECTORY)) {
$success = $this->writeFile($source, $destination, $replace);
}
else {
throw new MigrateException(SafeMarkup::format('Could not create directory @dir', ['@dir' => $dir]));
}
}
if ($success) {
return parent::import($row, $old_destination_id_values);
}
else {
throw new MigrateException(SafeMarkup::format('File %source could not be copied to %destination.', ['%source' => $source, '%destination' => $destination]));
}
}
/**
* Tries to move or copy a file.
*
* @param string $source
* The source path or URI.
* @param string $destination
* The destination path or URI.
* @param integer $replace
* FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
*
* @return boolean
* TRUE on success, FALSE on failure.
*/
protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
if ($this->configuration['move']) {
return (boolean) file_unmanaged_move($source, $destination, $replace);
}
else {
$destination = file_destination($destination, $replace);
$source = $this->urlencode($source);
return @copy($source, $destination);
}
}
/**
* Determines how to handle file conflicts.
*
* @param \Drupal\migrate\Row $row
*
* @return integer
* Either FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME, depending
* on the current configuration.
*/
protected function getOverwriteMode(Row $row) {
if (!empty($this->configuration['rename'])) {
$entity_id = $row->getDestinationProperty($this->getKey('id'));
if ($entity_id && ($entity = $this->storage->load($entity_id))) {
return FILE_EXISTS_RENAME;
}
}
return FILE_EXISTS_REPLACE;
}
/**
* Returns the directory component of a URI or path.
*
* For URIs like public://foo.txt, the full physical path of public://
* will be returned, since a scheme by itself will trip up certain file
* API functions (such as file_prepare_directory()).
*
* @param string $uri
* The URI or path.
*
* @return boolean|string
* The directory component of the path or URI, or FALSE if it could not
* be determined.
*/
protected function getDirectory($uri) {
$dir = $this->fileSystem->dirname($uri);
if (substr($dir, -3) == '://') {
return $this->fileSystem->realpath($dir);
}
else {
return $dir;
}
}
/**
* Returns 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
* The destination URI.
*
* @return boolean
* TRUE if the source and destination URIs refer to the same physical path,
* otherwise FALSE.
*/
protected function isLocationUnchanged($source, $destination) {
if ($this->isLocalUri($source) && $this->isLocalUri($destination)) {
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
}
else {
return FALSE;
}
}
/**
* Returns if the given URI or path is considered local.
*
* A URI or path is considered local if it either has no scheme component,
* or the scheme is implemented by a stream wrapper which extends
* \Drupal\Core\StreamWrapper\LocalStream.
*
* @param string $uri
* The URI or path to test.
*
* @return boolean
*/
protected function isLocalUri($uri) {
$scheme = $this->fileSystem->uriScheme($uri);
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
}
/**
* Urlencode all the components of a remote filename.
*
* @param string $filename
* The filename of the file to be urlencoded.
*
* @return string
* The urlencoded filename.
*/
protected function urlencode($filename) {
// Only apply to a full URL
if ($this->configuration['urlencode'] && strpos($filename, '://')) {
$components = explode('/', $filename);
foreach ($components as $key => $component) {
$components[$key] = rawurlencode($component);
}
$filename = implode('/', $components);
// Actually, we don't want certain characters encoded
$filename = str_replace('%3A', ':', $filename);
$filename = str_replace('%3F', '?', $filename);
$filename = str_replace('%26', '&', $filename);
}
return $filename;
}
}

View file

@ -0,0 +1,31 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityNodeType.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:node_type"
* )
*/
class EntityNodeType extends EntityConfigBase {
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$entity_ids = parent::import($row, $old_destination_id_values);
if ($row->getDestinationProperty('create_body')) {
$node_type = $this->storage->load(reset($entity_ids));
node_add_body_field($node_type, $row->getDestinationProperty('create_body_label'));
}
return $entity_ids;
}
}

View file

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityRevision.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity_revision",
* deriver = "Drupal\migrate\Plugin\Derivative\MigrateEntityRevision"
* )
*/
class EntityRevision extends EntityContentBase {
/**
* {@inheritdoc}
*/
protected static function getEntityTypeId($plugin_id) {
// Remove entity_revision:
return substr($plugin_id, 16);
}
/**
* Get the entity.
*
* @param \Drupal\migrate\Row $row
* The row object.
*
* @return \Drupal\Core\Entity\EntityInterface|false
* The entity or false if it can not be created.
*/
protected function getEntity(Row $row, array $old_destination_id_values) {
$revision_id = $old_destination_id_values ? reset($old_destination_id_values) : $row->getDestinationProperty($this->getKey('revision'));
if (!empty($revision_id) && ($entity = $this->storage->loadRevision($revision_id))) {
$entity->setNewRevision(FALSE);
}
else {
$entity_id = $row->getDestinationProperty($this->getKey('id'));
$entity = $this->storage->load($entity_id);
$entity->enforceIsNew(FALSE);
$entity->setNewRevision(TRUE);
}
$this->updateEntity($entity, $row);
$entity->isDefaultRevision(FALSE);
return $entity;
}
/**
* {@inheritdoc}
*/
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
$entity->save();
return array($entity->getRevisionId());
}
/**
* {@inheritdoc}
*/
public function getIds() {
if ($key = $this->getKey('revision')) {
$ids[$key]['type'] = 'integer';
return $ids;
}
throw new MigrateException('This entity type does not support revisions.');
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntitySearchPage.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityInterface;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:search_page"
* )
*/
class EntitySearchPage extends EntityConfigBase {
/**
* Updates the entity with the contents of a row.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The search page entity.
* @param \Drupal\migrate\Row $row
* The row object to update from.
*/
protected function updateEntity(EntityInterface $entity, Row $row) {
$entity->setPlugin($row->getDestinationProperty('plugin'));
$entity->getPlugin()->setConfiguration($row->getDestinationProperty('configuration'));
}
}

View file

@ -0,0 +1,29 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityTaxonomyTerm.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "entity:taxonomy_term"
* )
*/
class EntityTaxonomyTerm extends EntityContentBase {
/**
* {@inheritdoc}
*/
protected function getEntity(Row $row, array $old_destination_id_values) {
if ($row->isStub()) {
$row->setDestinationProperty('name', $this->t('Stub name for source tid:') . $row->getSourceProperty('tid'));
}
return parent::getEntity($row, $old_destination_id_values);
}
}

View file

@ -0,0 +1,101 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityUser.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Password\PasswordInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigratePassword;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* @MigrateDestination(
* id = "entity:user"
* )
*/
class EntityUser extends EntityContentBase {
/**
* The password service class.
*
* @var \Drupal\Core\Password\PasswordInterface
*/
protected $password;
/**
* Builds an user entity destination.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param MigrationInterface $migration
* The migration.
* @param EntityStorageInterface $storage
* The storage for this entity type.
* @param array $bundles
* The list of bundles this entity type has.
* @param \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager
* The migrate plugin manager.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager service.
* @param \Drupal\Core\Password\PasswordInterface $password
* The password service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, EntityManagerInterface $entity_manager, PasswordInterface $password) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles, $entity_manager);
if (isset($configuration['md5_passwords'])) {
$this->password = $password;
}
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
$entity_type = static::getEntityTypeId($plugin_id);
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage($entity_type),
array_keys($container->get('entity.manager')->getBundleInfo($entity_type)),
$container->get('entity.manager'),
$container->get('password')
);
}
/**
* {@inheritdoc}
* @throws \Drupal\migrate\MigrateException
*/
public function import(Row $row, array $old_destination_id_values = array()) {
if ($this->password) {
if ($this->password instanceof MigratePassword) {
$this->password->enableMd5Prefixing();
}
else {
throw new MigrateException('Password service has been altered by another module, aborting.');
}
}
$ids = parent::import($row, $old_destination_id_values);
if ($this->password) {
$this->password->disableMd5Prefixing();
}
return $ids;
}
}

View file

@ -0,0 +1,26 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\EntityViewMode.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
/**
* @MigrateDestination(
* id = "entity:entity_view_mode"
* )
*/
class EntityViewMode extends EntityConfigBase {
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['targetEntityType']['type'] = 'string';
$ids['mode']['type'] = 'string';
return $ids;
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\Null.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
/**
* @MigrateDestination(
* id = "null",
* requirements_met = false
* )
*/
class Null extends DestinationBase {
/**
* {@inheritdoc}
*/
public function getIds() {
return array();
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
return array();
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityDisplay.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
/**
* This class imports one component of an entity display.
*
* @MigrateDestination(
* id = "component_entity_display"
* )
*/
class PerComponentEntityDisplay extends ComponentEntityDisplayBase {
const MODE_NAME = 'view_mode';
/**
* {@inheritdoc}
*/
protected function getEntity($entity_type, $bundle, $view_mode) {
return entity_get_display($entity_type, $bundle, $view_mode);
}
}

View file

@ -0,0 +1,28 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\PerComponentEntityFormDisplay.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
/**
* This class imports one component of an entity form display.
*
* @MigrateDestination(
* id = "component_entity_form_display"
* )
*/
class PerComponentEntityFormDisplay extends ComponentEntityDisplayBase {
const MODE_NAME = 'form_mode';
/**
* {@inheritdoc}
*/
protected function getEntity($entity_type, $bundle, $form_mode) {
return entity_get_form_display($entity_type, $bundle, $form_mode);
}
}

View file

@ -0,0 +1,97 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\UrlAlias.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\Core\Path\AliasStorage;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
/**
* @MigrateDestination(
* id = "url_alias"
* )
*/
class UrlAlias extends DestinationBase implements ContainerFactoryPluginInterface {
/**
* The alias storage service.
*
* @var \Drupal\Core\Path\AliasStorage $aliasStorage
*/
protected $aliasStorage;
/**
* Constructs an entity destination plugin.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param MigrationInterface $migration
* The migration.
* @param \Drupal\Core\Path\AliasStorage $alias_storage
* The alias storage service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, AliasStorage $alias_storage) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->aliasStorage = $alias_storage;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('path.alias_storage')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$path = $this->aliasStorage->save(
$row->getDestinationProperty('source'),
$row->getDestinationProperty('alias'),
$row->getDestinationProperty('langcode'),
$old_destination_id_values ? $old_destination_id_values[0] : NULL
);
return array($path['pid']);
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['pid']['type'] = 'integer';
return $ids;
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
return [
'pid' => 'The path id',
'source' => 'The source path.',
'alias' => 'The url alias.',
'langcode' => 'The language code for the url.',
];
}
}

View file

@ -0,0 +1,94 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\destination\UserData.
*/
namespace Drupal\migrate\Plugin\migrate\destination;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\user\UserData as UserDataStorage;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
/**
* @MigrateDestination(
* id = "user_data"
* )
*/
class UserData extends DestinationBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\user\UserData
*/
protected $userData;
/**
* Builds an user data entity destination.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\migrate\Entity\MigrationInterface $migration
* The migration.
* @param \Drupal\user\UserData $user_data
* The user data service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, UserDataStorage $user_data) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
$this->userData = $user_data;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('user.data')
);
}
/**
* {@inheritdoc}
*/
public function import(Row $row, array $old_destination_id_values = array()) {
$uid = $row->getDestinationProperty('uid');
$module = $row->getDestinationProperty('module');
$key = $row->getDestinationProperty('key');
$this->userData->set($module, $uid, $key, $row->getDestinationProperty('settings'));
return [$uid, $module, $key];
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['uid']['type'] = 'integer';
$ids['module']['type'] = 'string';
$ids['key']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function fields(MigrationInterface $migration = NULL) {
return [
'uid' => 'The user id.',
'module' => 'The module name responsible for the settings.',
'key' => 'The setting key to save under.',
'settings' => 'The settings to save.',
];
}
}

View file

@ -0,0 +1,793 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\id_map\Sql.
*/
namespace Drupal\migrate\Plugin\migrate\id_map;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Database\Connection;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Plugin\PluginBase;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateMessageInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Row;
/**
* Defines the sql based ID map implementation.
*
* It creates one map and one message table per migration entity to store the
* relevant information.
*
* @PluginID("sql")
*/
class Sql extends PluginBase implements MigrateIdMapInterface {
/**
* The migration map table name.
*
* @var string
*/
protected $mapTableName;
/**
* The message table name.
*
* @var string
*/
protected $messageTableName;
/**
* The migrate message.
*
* @var \Drupal\migrate\MigrateMessageInterface
*/
protected $message;
/**
* The database connection for the map/message tables on the destination.
*
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* @var \Drupal\Core\Database\Query\SelectInterface
*/
protected $query;
/**
* The migration being done.
*
* @var \Drupal\migrate\Entity\MigrationInterface
*/
protected $migration;
/**
* The source ID fields.
*
* @var array
*/
protected $sourceIdFields;
/**
* The destination ID fields.
*
* @var array
*/
protected $destinationIdFields;
/**
* Whether the plugin is already initialized.
*
* @var bool
*/
protected $initialized;
/**
* The result.
*
* @var null
*/
protected $result = NULL;
/**
* The source identifiers.
*
* @var array
*/
protected $sourceIds = array();
/**
* The destination identifiers.
*
* @var array
*/
protected $destinationIds = array();
/**
* The current row.
*
* @var null
*/
protected $currentRow = NULL;
/**
* The current key.
*
* @var array
*/
protected $currentKey = array();
/**
* Constructs an SQL object.
*
* Sets up the tables and builds the maps,
*
* @param array $configuration
* The configuration.
* @param string $plugin_id
* The plugin ID for the migration process to do.
* @param mixed $plugin_definition
* The configuration for the plugin.
* @param \Drupal\migrate\Entity\MigrationInterface $migration
* The migration to do.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
}
/**
* The source ID fields.
*
* @return array
* The source ID fields.
*/
protected function sourceIdFields() {
if (!isset($this->sourceIdFields)) {
// Build the source and destination identifier maps.
$this->sourceIdFields = array();
$count = 1;
foreach ($this->migration->getSourcePlugin()->getIds() as $field => $schema) {
$this->sourceIdFields[$field] = 'sourceid' . $count++;
}
}
return $this->sourceIdFields;
}
/**
* The destination ID fields.
*
* @return array
* The destination ID fields.
*/
protected function destinationIdFields() {
if (!isset($this->destinationIdFields)) {
$this->destinationIdFields = array();
$count = 1;
foreach ($this->migration->getDestinationPlugin()->getIds() as $field => $schema) {
$this->destinationIdFields[$field] = 'destid' . $count++;
}
}
return $this->destinationIdFields;
}
/**
* The name of the database map table.
*
* @return string
* The map table name.
*/
public function mapTableName() {
$this->init();
return $this->mapTableName;
}
/**
* The name of the database message table.
*
* @return string
* The message table name.
*/
public function messageTableName() {
$this->init();
return $this->messageTableName;
}
/**
* Get the fully qualified map table name.
*
* @return string
* The fully qualified map table name.
*/
public function getQualifiedMapTableName() {
return $this->getDatabase()->getFullQualifiedTableName($this->mapTableName);
}
/**
* Gets the database connection.
*
* @return \Drupal\Core\Database\Connection
* The database connection object.
*/
public function getDatabase() {
if (!isset($this->database)) {
$this->database = \Drupal::database();
}
$this->init();
return $this->database;
}
/**
* Initialize the plugin.
*/
protected function init() {
if (!$this->initialized) {
$this->initialized = TRUE;
// Default generated table names, limited to 63 characters.
$machine_name = str_replace(':', '__', $this->migration->id());
$prefix_length = strlen($this->getDatabase()->tablePrefix());
$this->mapTableName = 'migrate_map_' . Unicode::strtolower($machine_name);
$this->mapTableName = Unicode::substr($this->mapTableName, 0, 63 - $prefix_length);
$this->messageTableName = 'migrate_message_' . Unicode::strtolower($machine_name);
$this->messageTableName = Unicode::substr($this->messageTableName, 0, 63 - $prefix_length);
$this->ensureTables();
}
}
/**
* {@inheritdoc}
*/
public function setMessage(MigrateMessageInterface $message) {
$this->message = $message;
}
/**
* Create the map and message tables if they don't already exist.
*/
protected function ensureTables() {
if (!$this->getDatabase()->schema()->tableExists($this->mapTableName)) {
// Generate appropriate schema info for the map and message tables,
// and map from the source field names to the map/msg field names.
$count = 1;
$source_id_schema = array();
$pks = array();
foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) {
$mapkey = 'sourceid' . $count++;
$source_id_schema[$mapkey] = $this->getFieldSchema($id_definition);
// With InnoDB, utf8mb4-based primary keys can't be over 191 characters.
// Use ASCII-based primary keys instead.
if (isset($source_id_schema[$mapkey]['type']) && $source_id_schema[$mapkey]['type'] == 'varchar') {
$source_id_schema[$mapkey]['type'] = 'varchar_ascii';
}
$pks[] = $mapkey;
}
$fields = $source_id_schema;
// Add destination identifiers to map table.
// TODO: How do we discover the destination schema?
$count = 1;
foreach ($this->migration->getDestinationPlugin()->getIds() as $id_definition) {
// Allow dest identifier fields to be NULL (for IGNORED/FAILED
// cases).
$mapkey = 'destid' . $count++;
$fields[$mapkey] = $this->getFieldSchema($id_definition);
$fields[$mapkey]['not null'] = FALSE;
}
$fields['source_row_status'] = array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => MigrateIdMapInterface::STATUS_IMPORTED,
'description' => 'Indicates current status of the source row',
);
$fields['rollback_action'] = array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => MigrateIdMapInterface::ROLLBACK_DELETE,
'description' => 'Flag indicating what to do for this item on rollback',
);
$fields['last_imported'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'UNIX timestamp of the last time this row was imported',
);
$fields['hash'] = array(
'type' => 'varchar',
'length' => '64',
'not null' => FALSE,
'description' => 'Hash of source row data, for detecting changes',
);
$schema = array(
'description' => 'Mappings from source identifier value(s) to destination identifier value(s).',
'fields' => $fields,
);
if ($pks) {
$schema['primary key'] = $pks;
}
$this->getDatabase()->schema()->createTable($this->mapTableName, $schema);
// Now do the message table.
if (!$this->getDatabase()->schema()->tableExists($this->messageTableName())) {
$fields = array();
$fields['msgid'] = array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
);
$fields += $source_id_schema;
$fields['level'] = array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 1,
);
$fields['message'] = array(
'type' => 'text',
'size' => 'medium',
'not null' => TRUE,
);
$schema = array(
'description' => 'Messages generated during a migration process',
'fields' => $fields,
'primary key' => array('msgid'),
);
if ($pks) {
$schema['indexes']['sourcekey'] = $pks;
}
$this->getDatabase()->schema()->createTable($this->messageTableName(), $schema);
}
}
else {
// Add any missing columns to the map table.
if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName,
'rollback_action')) {
$this->getDatabase()->schema()->addField($this->mapTableName,
'rollback_action', array(
'type' => 'int',
'size' => 'tiny',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
'description' => 'Flag indicating what to do for this item on rollback',
));
}
if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, 'hash')) {
$this->getDatabase()->schema()->addField($this->mapTableName, 'hash', array(
'type' => 'varchar',
'length' => '64',
'not null' => FALSE,
'description' => 'Hash of source row data, for detecting changes',
));
}
}
}
/**
* Create 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
* @return array
*/
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]];
}
/**
* {@inheritdoc}
*/
public function getRowBySource(array $source_id_values) {
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map');
foreach ($this->sourceIdFields() as $source_id) {
$query = $query->condition("map.$source_id", array_shift($source_id_values), '=');
}
$result = $query->execute();
return $result->fetchAssoc();
}
/**
* {@inheritdoc}
*/
public function getRowByDestination(array $destination_id_values) {
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map');
foreach ($this->destinationIdFields() as $destination_id) {
$query = $query->condition("map.$destination_id", array_shift($destination_id_values), '=');
}
$result = $query->execute();
return $result->fetchAssoc();
}
/**
* {@inheritdoc}
*/
public function getRowsNeedingUpdate($count) {
$rows = array();
$result = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map')
->condition('source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE)
->range(0, $count)
->execute();
foreach ($result as $row) {
$rows[] = $row;
}
return $rows;
}
/**
* {@inheritdoc}
*/
public function lookupSourceID(array $destination_id) {
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map', $this->sourceIdFields());
foreach ($this->destinationIdFields() as $key_name) {
$query = $query->condition("map.$key_name", array_shift($destination_id), '=');
}
$result = $query->execute();
$source_id = $result->fetchAssoc();
return array_values($source_id ?: array());
}
/**
* {@inheritdoc}
*/
public function lookupDestinationId(array $source_id) {
if (empty($source_id)) {
return array();
}
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map', $this->destinationIdFields());
foreach ($this->sourceIdFields() as $key_name) {
$query = $query->condition("map.$key_name", array_shift($source_id), '=');
}
$result = $query->execute();
$destination_id = $result->fetchAssoc();
return array_values($destination_id ?: array());
}
/**
* {@inheritdoc}
*/
public function saveIdMapping(Row $row, array $destination_id_values, $source_row_status = MigrateIdMapInterface::STATUS_IMPORTED, $rollback_action = MigrateIdMapInterface::ROLLBACK_DELETE) {
// Construct the source key.
$source_id_values = $row->getSourceIdValues();
// Construct the source key and initialize to empty variable keys.
$keys = array();
foreach ($this->sourceIdFields() as $field_name => $key_name) {
// A NULL key value will fail.
if (!isset($source_id_values[$field_name])) {
$this->message->display(t(
'Could not save to map table due to NULL value for key field !field',
array('!field' => $field_name)), 'error');
return;
}
$keys[$key_name] = $source_id_values[$field_name];
}
$fields = array(
'source_row_status' => (int) $source_row_status,
'rollback_action' => (int) $rollback_action,
'hash' => $row->getHash(),
);
$count = 0;
foreach ($destination_id_values as $dest_id) {
$fields['destid' . ++$count] = $dest_id;
}
if ($count && $count != count($this->destinationIdFields())) {
$this->message->display(t('Could not save to map table due to missing destination id values'), 'error');
return;
}
if ($this->migration->get('trackLastImported')) {
$fields['last_imported'] = time();
}
if ($keys) {
$this->getDatabase()->merge($this->mapTableName())
->key($keys)
->fields($fields)
->execute();
}
}
/**
* {@inheritdoc}
*/
public function saveMessage(array $source_id_values, $message, $level = MigrationInterface::MESSAGE_ERROR) {
$count = 1;
foreach ($source_id_values as $id_value) {
$fields['sourceid' . $count++] = $id_value;
// If any key value is not set, we can't save.
if (!isset($id_value)) {
return;
}
}
$fields['level'] = $level;
$fields['message'] = $message;
$this->getDatabase()->insert($this->messageTableName())
->fields($fields)
->execute();
}
/**
* {@inheritdoc}
*/
public function prepareUpdate() {
$this->getDatabase()->update($this->mapTableName())
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE))
->execute();
}
/**
* {@inheritdoc}
*/
public function processedCount() {
return $this->getDatabase()->select($this->mapTableName())
->countQuery()
->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function importedCount() {
return $this->getDatabase()->select($this->mapTableName())
->condition('source_row_status', array(MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE), 'IN')
->countQuery()
->execute()
->fetchField();
}
/**
* {@inheritdoc}
*/
public function updateCount() {
return $this->countHelper(MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
}
/**
* {@inheritdoc}
*/
public function errorCount() {
return $this->countHelper(MigrateIdMapInterface::STATUS_FAILED);
}
/**
* {@inheritdoc}
*/
public function messageCount() {
return $this->countHelper(NULL, $this->messageTableName());
}
/**
* Counts records in a table.
*
* @param $status
* An integer for the source_row_status column.
* @param $table
* The table to work
* @return int
* The number of records.
*/
protected function countHelper($status, $table = NULL) {
$query = $this->getDatabase()->select($table ?: $this->mapTableName());
if (isset($status)) {
$query->condition('source_row_status', $status);
}
return $query->countQuery()->execute()->fetchField();
}
/**
* {@inheritdoc}
*/
public function delete(array $source_id_values, $messages_only = FALSE) {
if (empty($source_id_values)) {
throw new MigrateException('Without source identifier values it is impossible to find the row to delete.');
}
if (!$messages_only) {
$map_query = $this->getDatabase()->delete($this->mapTableName());
}
$message_query = $this->getDatabase()->delete($this->messageTableName());
$count = 1;
foreach ($source_id_values as $id_value) {
if (!$messages_only) {
$map_query->condition('sourceid' . $count, $id_value);
}
$message_query->condition('sourceid' . $count, $id_value);
$count++;
}
if (!$messages_only) {
$map_query->execute();
}
$message_query->execute();
}
/**
* {@inheritdoc}
*/
public function deleteDestination(array $destination_id) {
$map_query = $this->getDatabase()->delete($this->mapTableName());
$message_query = $this->getDatabase()->delete($this->messageTableName());
$source_id = $this->lookupSourceID($destination_id);
if (!empty($source_id)) {
$count = 1;
foreach ($destination_id as $key_value) {
$map_query->condition('destid' . $count, $key_value);
$count++;
}
$map_query->execute();
$count = 1;
foreach ($source_id as $key_value) {
$message_query->condition('sourceid' . $count, $key_value);
$count++;
}
$message_query->execute();
}
}
/**
* {@inheritdoc}
*/
public function setUpdate(array $source_id) {
if (empty($source_id)) {
throw new MigrateException('No source identifiers provided to update.');
}
$query = $this->getDatabase()
->update($this->mapTableName())
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE));
$count = 1;
foreach ($source_id as $key_value) {
$query->condition('sourceid' . $count++, $key_value);
}
$query->execute();
}
/**
* {@inheritdoc}
*/
public function deleteBulk(array $source_id_values) {
// If we have a single-column key, we can shortcut it.
if (count($this->migration->getSourcePlugin()->getIds()) == 1) {
$sourceids = array();
foreach ($source_id_values as $source_id) {
$sourceids[] = $source_id;
}
$this->getDatabase()->delete($this->mapTableName())
->condition('sourceid1', $sourceids, 'IN')
->execute();
$this->getDatabase()->delete($this->messageTableName())
->condition('sourceid1', $sourceids, 'IN')
->execute();
}
else {
foreach ($source_id_values as $source_id) {
$map_query = $this->getDatabase()->delete($this->mapTableName());
$message_query = $this->getDatabase()->delete($this->messageTableName());
$count = 1;
foreach ($source_id as $key_value) {
$map_query->condition('sourceid' . $count, $key_value);
$message_query->condition('sourceid' . $count++, $key_value);
}
$map_query->execute();
$message_query->execute();
}
}
}
/**
* {@inheritdoc}
*/
public function clearMessages() {
$this->getDatabase()->truncate($this->messageTableName())->execute();
}
/**
* {@inheritdoc}
*/
public function destroy() {
$this->getDatabase()->schema()->dropTable($this->mapTableName());
$this->getDatabase()->schema()->dropTable($this->messageTableName());
}
/**
* Implementation of Iterator::rewind().
*
* This is called before beginning a foreach loop.
*
* @todo Support idlist, itemlimit.
*/
public function rewind() {
$this->currentRow = NULL;
$fields = array();
foreach ($this->sourceIdFields() as $field) {
$fields[] = $field;
}
foreach ($this->destinationIdFields() as $field) {
$fields[] = $field;
}
// @todo Make this work.
/*
if (isset($this->options['itemlimit'])) {
$query = $query->range(0, $this->options['itemlimit']);
}
*/
$this->result = $this->getDatabase()->select($this->mapTableName(), 'map')
->fields('map', $fields)
->execute();
$this->next();
}
/**
* Implementation of Iterator::current().
*
* This is called when entering a loop iteration, returning the current row.
*/
public function current() {
return $this->currentRow;
}
/**
* Implementation of Iterator::key().
*
* This is called when entering a loop iteration, returning the key of the
* current row. It must be a scalar - we will serialize to fulfill the
* requirement, but using getCurrentKey() is preferable.
*/
public function key() {
return serialize($this->currentKey);
}
/**
* Implementation of Iterator::next().
*
* This is called at the bottom of the loop implicitly, as well as explicitly
* from rewind().
*/
public function next() {
$this->currentRow = $this->result->fetchAssoc();
$this->currentKey = array();
if ($this->currentRow) {
foreach ($this->sourceIdFields() as $map_field) {
$this->currentKey[$map_field] = $this->currentRow[$map_field];
// Leave only destination fields.
unset($this->currentRow[$map_field]);
}
}
}
/**
* Implementation of Iterator::valid().
*
* This is called at the top of the loop, returning TRUE to process the loop
* and FALSE to terminate it.
*/
public function valid() {
// @todo Check numProcessed against itemlimit.
return $this->currentRow !== FALSE;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Callback.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* This plugin allows source value to be passed to a callback.
*
* The current value is passed to a callable that returns the processed value.
* This plugin allows simple processing of the value, such as strtolower(). The
* callable takes the value as the single mandatory argument. No additional
* arguments can be passed to the callback as this would make the migration YAML
* file too complex.
*
* @MigrateProcessPlugin(
* id = "callback"
* )
*/
class Callback extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_callable($this->configuration['callable'])) {
$value = call_user_func($this->configuration['callable'], $value);
}
return $value;
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Concat.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Component\Utility\SafeMarkup;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Concatenates the strings in the current value.
*
* @MigrateProcessPlugin(
* id = "concat",
* handle_multiples = TRUE
* )
*/
class Concat extends ProcessPluginBase {
/**
* {@inheritdoc}
*
* Concatenates the strings in the current value.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (is_array($value)) {
$delimiter = isset($this->configuration['delimiter']) ? $this->configuration['delimiter'] : '';
return implode($delimiter, $value);
}
else {
throw new MigrateException(sprintf('%s is not an array', SafeMarkup::checkPlain(var_export($value, TRUE))));
}
}
}

View file

@ -0,0 +1,60 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\DedupeBase.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateException;
use Drupal\Component\Utility\Unicode;
/**
* This abstract base contains the dedupe logic.
*
* These plugins avoid duplication at the destination. For example, when
* 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.
*/
abstract class DedupeBase extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$i = 1;
$postfix = isset($this->configuration['postfix']) ? $this->configuration['postfix'] : '';
$start = isset($this->configuration['start']) ? $this->configuration['start'] : 0;
if (!is_int($start)) {
throw new MigrateException('The start position configuration key should be an integer. Omit this key to capture from the beginning of the string.');
}
$length = isset($this->configuration['length']) ? $this->configuration['length'] : NULL;
if (!is_null($length) && !is_int($length)) {
throw new MigrateException('The character length configuration key should be an integer. Omit this key to capture the entire string.');
}
// Use optional start or length to return a portion of deduplicated value.
$value = Unicode::substr($value, $start, $length);
$new_value = $value;
while ($this->exists($new_value)) {
$new_value = $value . $postfix . $i++;
}
return $new_value;
}
/**
* This is a query checking the existence of some value.
*
* @param mixed $value
* The value to check.
*
* @return bool
* TRUE if the value exists.
*/
abstract protected function exists($value);
}

View file

@ -0,0 +1,63 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\DedupeEntity.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Core\Entity\Query\QueryFactory;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Ensures value is not duplicated against an entity field.
*
* @MigrateProcessPlugin(
* id = "dedupe_entity"
* )
*/
class DedupeEntity extends DedupeBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\Core\Entity\Query\QueryFactoryInterface
*/
protected $entityQueryFactory;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, QueryFactory $entity_query_factory) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->entityQueryFactory = $entity_query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.query')
);
}
/**
* {@inheritdoc}
*/
protected function exists($value) {
// Plugins are cached so for every run we need a new query object.
return $this
->entityQueryFactory
->get($this->configuration['entity_type'], 'AND')
->condition($this->configuration['field'], $value)
->count()
->execute();
}
}

View file

@ -0,0 +1,33 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\DefaultValue.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* This plugin sets missing values on the destination.
*
* @MigrateProcessPlugin(
* id = "default_value"
* )
*/
class DefaultValue extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!empty($this->configuration['strict'])) {
return isset($value) ? $value : $this->configuration['default_value'];
}
return $value ?: $this->configuration['default_value'];
}
}

View file

@ -0,0 +1,41 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Extract.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Component\Utility\NestedArray;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* This plugin extracts a value from an array.
*
* @see https://www.drupal.org/node/2152731
*
* @MigrateProcessPlugin(
* id = "extract"
* )
*/
class Extract extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!is_array($value)) {
throw new MigrateException('Input should be an array.');
}
$new_value = NestedArray::getValue($value, $this->configuration['index'], $key_exists);
if (!$key_exists) {
throw new MigrateException('Array index missing, extraction failed.');
}
return $new_value;
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Flatten.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* This plugin flattens the current value.
*
* During some types of processing (e.g. user permission splitting), what was
* 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
*
* @MigrateProcessPlugin(
* id = "flatten",
* handle_multiples = TRUE
* )
*/
class Flatten extends ProcessPluginBase {
/**
* Flatten nested array values to single array values.
*
* For example, array(array(1, 2, array(3, 4))) becomes array(1, 2, 3, 4).
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
return iterator_to_array(new \RecursiveIteratorIterator(new \RecursiveArrayIterator($value)), FALSE);
}
}

View file

@ -0,0 +1,72 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Get.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* This plugin copies from the source to the destination.
*
* @MigrateProcessPlugin(
* id = "get"
* )
*/
class Get extends ProcessPluginBase {
/**
* @var bool
*/
protected $multiple;
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$source = $this->configuration['source'];
$properties = is_string($source) ? array($source) : $source;
$return = array();
foreach ($properties as $property) {
if (empty($property)) {
$return[] = $value;
}
else {
$is_source = TRUE;
if ($property[0] == '@') {
$property = preg_replace_callback('/^(@?)((?:@@)*)([^@]|$)/', function ($matches) use (&$is_source) {
// If there are an odd number of @ in the beginning, it's a
// destination.
$is_source = empty($matches[1]);
// Remove the possible escaping and do not lose the terminating
// non-@ either.
return str_replace('@@', '@', $matches[2]) . $matches[3];
}, $property);
}
if ($is_source) {
$return[] = $row->getSourceProperty($property);
}
else {
$return[] = $row->getDestinationProperty($property);
}
}
}
if (is_string($source)) {
$this->multiple = is_array($return[0]);
return $return[0];
}
return $return;
}
/**
* {@inheritdoc}
*/
public function multiple() {
return $this->multiple;
}
}

View file

@ -0,0 +1,68 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Iterator.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
/**
* This plugin iterates and processes an array.
*
* @see https://www.drupal.org/node/2135345
*
* @MigrateProcessPlugin(
* id = "iterator",
* handle_multiples = TRUE
* )
*/
class Iterator extends ProcessPluginBase {
/**
* Runs a process pipeline on each destination property per list item.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$return = array();
foreach ($value as $key => $new_value) {
$new_row = new Row($new_value, array());
$migrate_executable->processRow($new_row, $this->configuration['process']);
$destination = $new_row->getDestination();
if (array_key_exists('key', $this->configuration)) {
$key = $this->transformKey($key, $migrate_executable, $new_row);
}
$return[$key] = $destination;
}
return $return;
}
/**
* Runs the process pipeline for the current key.
*
* @param string|int $key
* The current key.
* @param \Drupal\migrate\MigrateExecutableInterface $migrate_executable
* The migrate executable helper class.
* @param \Drupal\migrate\Row $row
* The current row after processing.
*
* @return mixed
* The transformed key.
*/
protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
$process = array('key' => $this->configuration['key']);
$migrate_executable->processRow($row, $process, $key);
return $row->getDestinationProperty('key');
}
/**
* {@inheritdoc}
*/
public function multiple() {
return TRUE;
}
}

View file

@ -0,0 +1,75 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\MachineName.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Component\Transliteration\TransliterationInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* This plugin creates a machine name.
*
* The current value gets transliterated, non-alphanumeric characters removed
* and replaced by an underscore and multiple underscores are collapsed into
* one.
*
* @MigrateProcessPlugin(
* id = "machine_name"
* )
*/
class MachineName extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\Component\Transliteration\TransliterationInterface
*/
protected $transliteration;
/**
* Constructs a MachineName plugin.
*
* @param array $configuration
* The plugin configuration.
* @param string $plugin_id
* The plugin ID.
* @param mixed $plugin_definition
* The plugin definition.
* @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
* The transliteration service.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, TransliterationInterface $transliteration) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->transliteration = $transliteration;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('transliteration')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$new_value = $this->transliteration->transliterate($value, LanguageInterface::LANGCODE_DEFAULT, '_');
$new_value = strtolower($new_value);
$new_value = preg_replace('/[^a-z0-9_]+/', '_', $new_value);
return preg_replace('/_+/', '_', $new_value);
}
}

View file

@ -0,0 +1,165 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Migration.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\MigrateSkipRowException;
use Drupal\migrate\Plugin\MigratePluginManager;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Calculates the value of a property based on a previous migration.
*
* @MigrateProcessPlugin(
* id = "migration"
* )
*/
class Migration extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\migrate\Plugin\MigratePluginManager
*/
protected $processPluginManager;
/**
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $migrationStorage;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, MigratePluginManager $process_plugin_manager) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migrationStorage = $storage;
$this->migration = $migration;
$this->processPluginManager = $process_plugin_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('entity.manager')->getStorage('migration'),
$container->get('plugin.manager.migrate.process')
);
}
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$migration_ids = $this->configuration['migration'];
if (!is_array($migration_ids)) {
$migration_ids = array($migration_ids);
}
$scalar = FALSE;
if (!is_array($value)) {
$scalar = TRUE;
$value = array($value);
}
$this->skipOnEmpty($value);
$self = FALSE;
/** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
$migrations = $this->migrationStorage->loadMultiple($migration_ids);
$destination_ids = NULL;
$source_id_values = array();
foreach ($migrations as $migration_id => $migration) {
if ($migration_id == $this->migration->id()) {
$self = TRUE;
}
if (isset($this->configuration['source_ids'][$migration_id])) {
$configuration = array('source' => $this->configuration['source_ids'][$migration_id]);
$source_id_values[$migration_id] = $this->processPluginManager
->createInstance('get', $configuration, $this->migration)
->transform(NULL, $migrate_executable, $row, $destination_property);
}
else {
$source_id_values[$migration_id] = $value;
}
// Break out of the loop as soon as a destination ID is found.
if ($destination_ids = $migration->getIdMap()->lookupDestinationID($source_id_values[$migration_id])) {
break;
}
}
if (!$destination_ids && ($self || isset($this->configuration['stub_id']) || count($migrations) == 1)) {
// If the lookup didn't succeed, figure out which migration will do the
// stubbing.
if ($self) {
$migration = $this->migration;
}
elseif (isset($this->configuration['stub_id'])) {
$migration = $migrations[$this->configuration['stub_id']];
}
else {
$migration = reset($migrations);
}
$destination_plugin = $migration->getDestinationPlugin(TRUE);
// Only keep the process necessary to produce the destination ID.
$process = $migration->get('process');
// We already have the source id values but need to key them for the Row
// constructor.
$source_ids = $migration->getSourcePlugin()->getIds();
$values = array();
foreach (array_keys($source_ids) as $index => $source_id) {
$values[$source_id] = $source_id_values[$migration->id()][$index];
}
$stub_row = new Row($values + $migration->get('source'), $source_ids, TRUE);
// Do a normal migration with the stub row.
$migrate_executable->processRow($stub_row, $process);
$destination_ids = array();
try {
$destination_ids = $destination_plugin->import($stub_row);
}
catch (\Exception $e) {
$migrate_executable->saveMessage($e->getMessage());
}
}
if ($destination_ids) {
if ($scalar) {
if (count($destination_ids) == 1) {
return reset($destination_ids);
}
}
else {
return $destination_ids;
}
}
throw new MigrateSkipRowException();
}
/**
* Skip the migration process entirely if the value is FALSE.
*
* @param mixed $value
* The incoming value to transform.
*
* @throws \Drupal\migrate\MigrateSkipProcessException
*/
protected function skipOnEmpty($value) {
if (!array_filter($value)) {
throw new MigrateSkipProcessException();
}
}
}

View file

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\Route.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* @MigrateProcessPlugin(
* id = "route"
* )
*/
class Route extends ProcessPluginBase implements ContainerFactoryPluginInterface {
/**
* @var \Drupal\Core\Path\PathValidatorInterface
*/
protected $pathValidator;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, PathValidatorInterface $pathValidator) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
$this->pathValidator = $pathValidator;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$migration,
$container->get('path.validator')
);
}
/**
* {@inheritdoc}
*
* Set the destination route information based on the source link_path.
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
list($link_path, $options) = $value;
$extracted = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_path);
$route = array();
if ($extracted) {
if ($extracted->isExternal()) {
$route['route_name'] = null;
$route['route_parameters'] = array();
$route['options'] = $options;
$route['url'] = $extracted->getUri();
}
else {
$route['route_name'] = $extracted->getRouteName();
$route['route_parameters'] = $extracted->getRouteParameters();
$route['options'] = $extracted->getOptions();
if (isset($options['query'])) {
// If the querystring is stored as a string (as in D6), convert it
// into an array.
if (is_string($options['query'])) {
parse_str($options['query'], $old_query);
}
else {
$old_query = $options['query'];
}
$options['query'] = $route['options']['query'] + $old_query;
unset($route['options']['query']);
}
$route['options'] = $route['options'] + $options;
$route['url'] = null;
}
}
return $route;
}
}

View file

@ -0,0 +1,45 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\SkipOnEmpty.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\MigrateSkipProcessException;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip processing or the whole row.
*
* @MigrateProcessPlugin(
* id = "skip_on_empty"
* )
*/
class SkipOnEmpty extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function row($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!$value) {
throw new MigrateSkipRowException();
}
return $value;
}
/**
* {@inheritdoc}
*/
public function process($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!$value) {
throw new MigrateSkipProcessException();
}
return $value;
}
}

View file

@ -0,0 +1,35 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\SkipRowIfNotSet.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateSkipRowException;
/**
* If the source evaluates to empty, we skip the current row.
*
* @MigrateProcessPlugin(
* id = "skip_row_if_not_set",
* handle_multiples = TRUE
* )
*/
class SkipRowIfNotSet extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!isset($value[$this->configuration['index']])) {
throw new MigrateSkipRowException();
}
return $value[$this->configuration['index']];
}
}

View file

@ -0,0 +1,59 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\process\StaticMap.
*/
namespace Drupal\migrate\Plugin\migrate\process;
use Drupal\Component\Utility\NestedArray;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
use Drupal\migrate\MigrateSkipRowException;
/**
* This plugin changes the current value based on a static lookup map.
*
* @see https://www.drupal.org/node/2143521
*
* @MigrateProcessPlugin(
* id = "static_map"
* )
*/
class StaticMap extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$new_value = $value;
if (is_array($value)) {
if (!$value) {
throw new MigrateException('Can not lookup without a value.');
}
}
else {
$new_value = array($value);
}
$new_value = NestedArray::getValue($this->configuration['map'], $new_value, $key_exists);
if (!$key_exists) {
if (isset($this->configuration['default_value'])) {
if (!empty($this->configuration['bypass'])) {
throw new MigrateException('Setting both default_value and bypass is invalid.');
}
return $this->configuration['default_value'];
}
if (empty($this->configuration['bypass'])) {
throw new MigrateSkipRowException();
}
else {
return $value;
}
}
return $new_value;
}
}

View file

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\source\EmptySource.
*/
namespace Drupal\migrate\Plugin\migrate\source;
/**
* Source returning an empty row.
*
* This is generally useful when needing to create a field using a migration..
*
* @MigrateSource(
* id = "empty"
* )
*/
class EmptySource extends SourcePluginBase {
/**
* {@inheritdoc}
*/
public function fields() {
return array(
'id' => t('ID'),
);
}
/**
* {@inheritdoc}
*/
public function initializeIterator() {
return new \ArrayIterator(array(array('id' => '')));
}
public function __toString() {
return '';
}
/**
* {@inheritdoc}
*/
public function getIds() {
$ids['id']['type'] = 'string';
return $ids;
}
/**
* {@inheritdoc}
*/
public function count() {
return 1;
}
}

View file

@ -0,0 +1,458 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\source\SourcePluginBase.
*/
namespace Drupal\migrate\Plugin\migrate\source;
use Drupal\Core\Plugin\PluginBase;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
use Drupal\migrate\Plugin\MigrateSourceInterface;
use Drupal\migrate\Row;
/**
* The base class for all source plugins.
*
* @see \Drupal\migrate\Plugin\MigratePluginManager
* @see \Drupal\migrate\Annotation\MigrateSource
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
* @see plugin_api
*
* @ingroup migration
*/
abstract class SourcePluginBase extends PluginBase implements MigrateSourceInterface {
/**
* @var \Drupal\Core\Extension\ModuleHandlerInterface
*/
protected $moduleHandler;
/**
* @var \Drupal\migrate\Entity\MigrationInterface
*/
protected $migration;
/**
* The name and type of the highwater property in the source.
*
* @var array
*
* @see $originalHighwater
*/
protected $highWaterProperty;
/**
* The current row from the query
*
* @var \Drupal\Migrate\Row
*/
protected $currentRow;
/**
* The primary key of the current row
*
* @var array
*/
protected $currentSourceIds;
/**
* Number of rows intentionally ignored (prepareRow() returned FALSE)
*
* @var int
*/
protected $numIgnored = 0;
/**
* Number of rows we've at least looked at.
*
* @var int
*/
protected $numProcessed = 0;
/**
* The high water mark at the beginning of the import operation.
*
* If the source has a property for tracking changes (like Drupal ha
* node.changed) then this is the highest value of those imported so far.
*
* @var int
*/
protected $originalHighWater;
/**
* List of source IDs to process.
*
* @var array
*/
protected $idList = array();
/**
* Whether this instance should cache the source count.
*
* @var bool
*/
protected $cacheCounts = FALSE;
/**
* Key to use for caching counts.
*
* @var string
*/
protected $cacheKey;
/**
* Whether this instance should not attempt to count the source.
*
* @var bool
*/
protected $skipCount = FALSE;
/**
* If TRUE, we will maintain hashed source rows to determine whether incoming
* data has changed.
*
* @var bool
*/
protected $trackChanges = FALSE;
/**
* By default, next() will directly read the map row and add it to the data
* row. A source plugin implementation may do this itself (in particular, the
* SQL source can incorporate the map table into the query) - if so, it should
* set this TRUE so we don't duplicate the effort.
*
* @var bool
*/
protected $mapRowAdded = FALSE;
/**
* @var \Drupal\Core\Cache\CacheBackendInterface
*/
protected $cache;
/**
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
*/
protected $idMap;
/**
* @var \Iterator
*/
protected $iterator;
// @TODO, find out how to remove this.
// @see https://www.drupal.org/node/2443617
public $migrateExecutable;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->migration = $migration;
// Set up some defaults based on the source configuration.
$this->cacheCounts = !empty($configuration['cache_counts']);
$this->skipCount = !empty($configuration['skip_count']);
$this->cacheKey = !empty($configuration['cache_key']) ? !empty($configuration['cache_key']) : NULL;
$this->trackChanges = !empty($configuration['track_changes']) ? $configuration['track_changes'] : FALSE;
// Pull out the current highwater mark if we have a highwater property.
if ($this->highWaterProperty = $this->migration->get('highWaterProperty')) {
$this->originalHighWater = $this->migration->getHighWater();
}
if ($id_list = $this->migration->get('idlist')) {
$this->idList = $id_list;
}
// Don't allow the use of both highwater and track changes together.
if ($this->highWaterProperty && $this->trackChanges) {
throw new MigrateException('You should either use a highwater mark or track changes not both. They are both designed to solve the same problem');
}
}
/**
* Initialize the iterator with the source data.
*
* @return array
* An array of the data for this source.
*/
protected abstract function initializeIterator();
/**
* Get the module handler.
*
* @return \Drupal\Core\Extension\ModuleHandlerInterface
* The module handler.
*/
protected function getModuleHandler() {
if (!isset($this->moduleHandler)) {
$this->moduleHandler = \Drupal::moduleHandler();
}
return $this->moduleHandler;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$result = TRUE;
$result_hook = $this->getModuleHandler()->invokeAll('migrate_prepare_row', array($row, $this, $this->migration));
$result_named_hook = $this->getModuleHandler()->invokeAll('migrate_' . $this->migration->id() . '_prepare_row', array($row, $this, $this->migration));
// We're explicitly skipping this row - keep track in the map table.
if (($result_hook && in_array(FALSE, $result_hook)) || ($result_named_hook && in_array(FALSE, $result_named_hook))) {
// Make sure we replace any previous messages for this item with any
// new ones.
$id_map = $this->migration->getIdMap();
$id_map->delete($this->currentSourceIds, TRUE);
$this->migrateExecutable->saveQueuedMessages();
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED, $this->migrateExecutable->rollbackAction);
$this->numIgnored++;
$this->currentRow = NULL;
$this->currentSourceIds = NULL;
$result = FALSE;
}
elseif ($this->trackChanges) {
// When tracking changed data, We want to quietly skip (rather than
// "ignore") rows with changes. The caller needs to make that decision,
// so we need to provide them with the necessary information (before and
// after hashes).
$row->rehash();
}
$this->numProcessed++;
return $result;
}
/**
* Returns the iterator that will yield the row arrays to be processed.
*
* @return \Iterator
*/
public function getIterator() {
if (!isset($this->iterator)) {
$this->iterator = $this->initializeIterator();
}
return $this->iterator;
}
/**
* {@inheritdoc}
*/
public function current() {
return $this->currentRow;
}
/**
* Get the iterator key.
*
* Implementation of Iterator::key - called when entering a loop iteration,
* returning the key of the current row. It must be a scalar - we will
* serialize to fulfill the requirement, but using getCurrentIds() is
* preferable.
*/
public function key() {
return serialize($this->currentSourceIds);
}
/**
* Whether the iterator is currently valid.
*
* Implementation of Iterator::valid() - called at the top of the loop,
* returning TRUE to process the loop and FALSE to terminate it
*/
public function valid() {
return isset($this->currentRow);
}
/**
* Rewind the iterator.
*
* Implementation of Iterator::rewind() - subclasses of MigrateSource should
* implement performRewind() to do any class-specific setup for iterating
* source records.
*/
public function rewind() {
$this->idMap = $this->migration->getIdMap();
$this->numProcessed = 0;
$this->numIgnored = 0;
$this->getIterator()->rewind();
$this->next();
}
/**
* {@inheritdoc}
*
* The migration iterates over rows returned by the source plugin. This
* method determines the next row which will be processed and imported into
* the system.
*
* The method tracks the source and destination IDs using the ID map plugin.
*
* This also takes care about highwater support. Highwater allows to reimport
* rows from a previous migration run, which got changed in the meantime.
* This is done by specifying a highwater field, which is compared with the
* last time, the migration got executed (originalHighWater).
*/
public function next() {
$this->currentSourceIds = NULL;
$this->currentRow = NULL;
// In order to find the next row we want to process, we ask the source
// plugin for the next possible row.
while (!isset($this->currentRow) && $this->getIterator()->valid()) {
$row_data = $this->getIterator()->current() + $this->configuration;
$this->getIterator()->next();
$row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->get('destinationIds'));
// Populate the source key for this row.
$this->currentSourceIds = $row->getSourceIdValues();
// Pick up the existing map row, if any, unless getNextRow() did it.
if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentSourceIds))) {
$row->setIdMap($id_map);
}
// In case we have specified an ID list, but the ID given by the source is
// not in there, we skip the row.
$id_in_the_list = $this->idList && in_array(reset($this->currentSourceIds), $this->idList);
if ($this->idList && !$id_in_the_list) {
continue;
}
// Preparing the row gives source plugins the chance to skip.
if ($this->prepareRow($row) === FALSE) {
continue;
}
// Check whether the row needs processing.
// 1. Explicitly specified IDs.
// 2. This row has not been imported yet.
// 3. Explicitly set to update.
// 4. The row is newer than the current highwater mark.
// 5. If no such property exists then try by checking the hash of the row.
if ($id_in_the_list || !$row->getIdMap() || $row->needsUpdate() || $this->aboveHighwater($row) || $this->rowChanged($row) ) {
$this->currentRow = $row->freezeSource();
}
}
}
/**
* Check if the incoming data is newer than what we've previously imported.
*
* @param \Drupal\migrate\Row $row
* The row we're importing.
*
* @return bool
* TRUE if the highwater value in the row is greater than our current value.
*/
protected function aboveHighwater(Row $row) {
return $this->highWaterProperty && $row->getSourceProperty($this->highWaterProperty['name']) > $this->originalHighWater;
}
/**
* Check if the incoming row has changed since our last import.
*
* @param \Drupal\migrate\Row $row
* The row we're importing.
*
* @return bool
* TRUE if the row has changed otherwise FALSE.
*/
protected function rowChanged(Row $row) {
return $this->trackChanges && $row->changed();
}
/**
* Getter for currentSourceIds data member.
*/
public function getCurrentIds() {
return $this->currentSourceIds;
}
/**
* Getter for numIgnored data member.
*/
public function getIgnored() {
return $this->numIgnored;
}
/**
* Getter for numProcessed data member.
*/
public function getProcessed() {
return $this->numProcessed;
}
/**
* Reset numIgnored back to 0.
*/
public function resetStats() {
$this->numIgnored = 0;
}
/**
* Get the source count.
*
* Return a count of available source records, from the cache if appropriate.
* Returns -1 if the source is not countable.
*
* @param bool $refresh
* Whether or not to refresh the count.
*
* @return int
* The count.
*/
public function count($refresh = FALSE) {
if ($this->skipCount) {
return -1;
}
if (!isset($this->cacheKey)) {
$this->cacheKey = hash('sha256', $this->getPluginId());
}
// If a refresh is requested, or we're not caching counts, ask the derived
// class to get the count from the source.
if ($refresh || !$this->cacheCounts) {
$count = $this->getIterator()->count();
$this->getCache()->set($this->cacheKey, $count, 'cache');
}
else {
// Caching is in play, first try to retrieve a cached count.
$cache_object = $this->getCache()->get($this->cacheKey, 'cache');
if (is_object($cache_object)) {
// Success.
$count = $cache_object->data;
}
else {
// No cached count, ask the derived class to count 'em up, and cache
// the result.
$count = $this->getIterator()->count();
$this->getCache()->set($this->cacheKey, $count, 'cache');
}
}
return $count;
}
/**
* Get the cache object.
*
* @return \Drupal\Core\Cache\CacheBackendInterface
* The cache object.
*/
protected function getCache() {
if (!isset($this->cache)) {
$this->cache = \Drupal::cache('migrate');
}
return $this->cache;
}
}

View file

@ -0,0 +1,235 @@
<?php
/**
* @file
* Contains \Drupal\migrate\Plugin\migrate\source\SqlBase.
*/
namespace Drupal\migrate\Plugin\migrate\source;
use Drupal\Core\Database\Database;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Plugin\migrate\id_map\Sql;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Sources whose data may be fetched via DBTNG.
*
* By default, an existing database connection with key 'migrate' and target
* 'default' is used. These may be overridden with explicit 'key' and/or
* 'target' configuration keys. In addition, if the configuration key 'database'
* is present, it is used as a database connection information array to define
* the connection.
*/
abstract class SqlBase extends SourcePluginBase {
/**
* @var \Drupal\Core\Database\Query\SelectInterface
*/
protected $query;
/**
* @var \Drupal\migrate\Entity\MigrationInterface
*/
protected $migration;
/**
* @var \Drupal\Core\Database\Connection
*/
protected $database;
/**
* {@inheritdoc}
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
}
/**
* Print the query string when the object is used a string.
*
* @return string
* The query string.
*/
public function __toString() {
return (string) $this->query;
}
/**
* Get the database connection object.
*
* @return \Drupal\Core\Database\Connection
* The database connection.
*/
public function getDatabase() {
if (!isset($this->database)) {
if (isset($this->configuration['target'])) {
$target = $this->configuration['target'];
}
else {
$target = 'default';
}
if (isset($this->configuration['key'])) {
$key = $this->configuration['key'];
}
else {
$key = 'migrate';
}
if (isset($this->configuration['database'])) {
Database::addConnectionInfo($key, $target, $this->configuration['database']);
}
$this->database = Database::getConnection($target, $key);
}
return $this->database;
}
/**
* Wrapper for database select.
*/
protected function select($table, $alias = NULL, array $options = array()) {
$options['fetch'] = \PDO::FETCH_ASSOC;
return $this->getDatabase()->select($table, $alias, $options);
}
/**
* A helper for adding tags and metadata to the query.
*
* @return \Drupal\Core\Database\Query\SelectInterface
* The query with additional tags and metadata.
*/
protected function prepareQuery() {
$this->query = clone $this->query();
$this->query->addTag('migrate');
$this->query->addTag('migrate_' . $this->migration->id());
$this->query->addMetaData('migration', $this->migration);
return $this->query;
}
/**
* Implementation of MigrateSource::performRewind().
*
* We could simply execute the query and be functionally correct, but
* we will take advantage of the PDO-based API to optimize the query up-front.
*/
protected function initializeIterator() {
$this->prepareQuery();
$high_water_property = $this->migration->get('highWaterProperty');
// Get the key values, for potential use in joining to the map table, or
// enforcing idlist.
$keys = array();
// The rules for determining what conditions to add to the query are as
// follows (applying first applicable rule)
// 1. If idlist is provided, then only process items in that list (AND key
// IN (idlist)). Only applicable with single-value keys.
if ($id_list = $this->migration->get('idlist')) {
$this->query->condition($keys[0], $id_list, 'IN');
}
else {
// 2. If the map is joinable, join it. We will want to accept all rows
// which are either not in the map, or marked in the map as NEEDS_UPDATE.
// Note that if high water fields are in play, we want to accept all rows
// above the high water mark in addition to those selected by the map
// conditions, so we need to OR them together (but AND with any existing
// conditions in the query). So, ultimately the SQL condition will look
// like (original conditions) AND (map IS NULL OR map needs update
// OR above high water).
$conditions = $this->query->orConditionGroup();
$condition_added = FALSE;
if ($this->mapJoinable()) {
// Build the join to the map table. Because the source key could have
// multiple fields, we need to build things up.
$count = 1;
$map_join = '';
$delimiter = '';
foreach ($this->getIds() as $field_name => $field_schema) {
if (isset($field_schema['alias'])) {
$field_name = $field_schema['alias'] . '.' . $field_name;
}
$map_join .= "$delimiter$field_name = map.sourceid" . $count++;
$delimiter = ' AND ';
}
$alias = $this->query->leftJoin($this->migration->getIdMap()->getQualifiedMapTableName(), 'map', $map_join);
$conditions->isNull($alias . '.sourceid1');
$conditions->condition($alias . '.source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
$condition_added = TRUE;
// And as long as we have the map table, add its data to the row.
$n = count($this->getIds());
for ($count = 1; $count <= $n; $count++) {
$map_key = 'sourceid' . $count;
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
}
if ($n = count($this->migration->get('destinationIds'))) {
for ($count = 1; $count <= $n; $count++) {
$map_key = 'destid' . $count++;
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
}
}
$this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
}
// 3. If we are using high water marks, also include rows above the mark.
// But, include all rows if the high water mark is not set.
if (isset($high_water_property['name']) && ($high_water = $this->migration->getHighWater()) !== '') {
if (isset($high_water_property['alias'])) {
$high_water = $high_water_property['alias'] . '.' . $high_water_property['name'];
}
else {
$high_water = $high_water_property['name'];
}
$conditions->condition($high_water, $high_water, '>');
$condition_added = TRUE;
}
if ($condition_added) {
$this->query->condition($conditions);
}
}
return new \IteratorIterator($this->query->execute());
}
/**
* @return \Drupal\Core\Database\Query\SelectInterface
*/
abstract public function query();
/**
* {@inheritdoc}
*/
public function count() {
return $this->query()->countQuery()->execute()->fetchField();
}
/**
* Check if we can join against the map table.
*
* This function specifically catches issues when we're migrating with
* unique sets of credentials for the source and destination database.
*
* @return bool
* TRUE if we can join against the map table otherwise FALSE.
*/
protected function mapJoinable() {
if (!$this->getIds()) {
return FALSE;
}
$id_map = $this->migration->getIdMap();
if (!$id_map instanceof Sql) {
return FALSE;
}
$id_map_database_options = $id_map->getDatabase()->getConnectionOptions();
$source_database_options = $this->getDatabase()->getConnectionOptions();
foreach (array('username', 'password', 'host', 'port', 'namespace', 'driver') as $key) {
if (isset($source_database_options[$key])) {
if ($id_map_database_options[$key] != $source_database_options[$key]) {
return FALSE;
}
}
}
return TRUE;
}
}