updated core and modules
This commit is contained in:
@@ -24,7 +24,7 @@ use Drupal\Component\Annotation\Plugin;
|
||||
*
|
||||
* @Annotation
|
||||
*/
|
||||
class MigrateSource extends Plugin {
|
||||
class MigrateSource extends Plugin implements MultipleProviderAnnotationInterface {
|
||||
|
||||
/**
|
||||
* A unique identifier for the process plugin.
|
||||
@@ -66,4 +66,34 @@ class MigrateSource extends Plugin {
|
||||
*/
|
||||
public $minimum_version;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProvider() {
|
||||
if (isset($this->definition['provider'])) {
|
||||
return is_array($this->definition['provider']) ? reset($this->definition['provider']) : $this->definition['provider'];
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getProviders() {
|
||||
if (isset($this->definition['provider'])) {
|
||||
// Ensure that we return an array even if
|
||||
// \Drupal\Component\Annotation\AnnotationInterface::setProvider() has
|
||||
// been called.
|
||||
return (array) $this->definition['provider'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setProviders(array $providers) {
|
||||
$this->definition['provider'] = $providers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Annotation;
|
||||
|
||||
use Drupal\Component\Annotation\AnnotationInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for classed annotations with multiple providers.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source plugins
|
||||
* have more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
interface MultipleProviderAnnotationInterface extends AnnotationInterface {
|
||||
|
||||
/**
|
||||
* Gets the name of the provider of the annotated class.
|
||||
*
|
||||
* @return string
|
||||
* The provider of the annotation. If there are multiple providers the first
|
||||
* is returned.
|
||||
*/
|
||||
public function getProvider();
|
||||
|
||||
/**
|
||||
* Gets the provider names of the annotated class.
|
||||
*
|
||||
* @return string[]
|
||||
* The providers of the annotation.
|
||||
*/
|
||||
public function getProviders();
|
||||
|
||||
/**
|
||||
* Sets the provider names of the annotated class.
|
||||
*
|
||||
* @param string[] $providers
|
||||
* The providers of the annotation.
|
||||
*/
|
||||
public function setProviders(array $providers);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Symfony\Component\EventDispatcher\Event as SymfonyEvent;
|
||||
|
||||
class EventBase extends SymfonyEvent {
|
||||
|
||||
/**
|
||||
* The migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The current message service.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/**
|
||||
* Constructs a Migrate event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration being run.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The Migrate message service.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message) {
|
||||
$this->migration = $migration;
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration being run.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message using the Migrate message service.
|
||||
*
|
||||
* @param string $message
|
||||
* The message to log.
|
||||
* @param string $type
|
||||
* The type of message, for example: status or warning.
|
||||
*/
|
||||
public function logMessage($message, $type = 'status') {
|
||||
$this->message->display($message, $type);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* Interface for plugins that react to pre- or post-import events.
|
||||
*/
|
||||
interface ImportAwareInterface {
|
||||
|
||||
/**
|
||||
* Performs pre-import tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The pre-import event object.
|
||||
*/
|
||||
public function preImport(MigrateImportEvent $event);
|
||||
|
||||
/**
|
||||
* Performs post-import tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The post-import event object.
|
||||
*/
|
||||
public function postImport(MigrateImportEvent $event);
|
||||
|
||||
}
|
||||
@@ -81,7 +81,7 @@ final class MigrateEvents {
|
||||
*
|
||||
* This event allows modules to perform an action whenever a specific item
|
||||
* is about to be saved by the destination plugin. The event listener method
|
||||
* receives a \Drupal\migrate\Event\MigratePreSaveEvent instance.
|
||||
* receives a \Drupal\migrate\Event\MigratePreRowSaveEvent instance.
|
||||
*
|
||||
* @Event
|
||||
*
|
||||
|
||||
@@ -2,39 +2,7 @@
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a pre- or post-import event for event listeners.
|
||||
*/
|
||||
class MigrateImportEvent extends Event {
|
||||
|
||||
/**
|
||||
* Migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Constructs an import event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration) {
|
||||
$this->migration = $migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration entity involved.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
}
|
||||
class MigrateImportEvent extends EventBase {}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
@@ -10,18 +11,27 @@ use Drupal\migrate\Row;
|
||||
*/
|
||||
class MigratePostRowSaveEvent extends MigratePreRowSaveEvent {
|
||||
|
||||
/**
|
||||
* The row's destination ID.
|
||||
*
|
||||
* @var array|bool
|
||||
*/
|
||||
protected $destinationIdValues = [];
|
||||
|
||||
/**
|
||||
* Constructs a post-save event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The message interface.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* Row object.
|
||||
* @param array|bool $destination_id_values
|
||||
* Values represent the destination ID.
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, Row $row, $destination_id_values) {
|
||||
parent::__construct($migration, $row);
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, Row $row, $destination_id_values) {
|
||||
parent::__construct($migration, $message, $row);
|
||||
$this->destinationIdValues = $destination_id_values;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Wraps a pre-save event for event listeners.
|
||||
*/
|
||||
class MigratePreRowSaveEvent extends Event {
|
||||
class MigratePreRowSaveEvent extends EventBase {
|
||||
|
||||
/**
|
||||
* Row object.
|
||||
@@ -18,34 +18,20 @@ class MigratePreRowSaveEvent extends Event {
|
||||
*/
|
||||
protected $row;
|
||||
|
||||
/**
|
||||
* Migration entity.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* Constructs a pre-save event object.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* Migration entity.
|
||||
* @param \Drupal\migrate\MigrateMessageInterface $message
|
||||
* The current migrate message service.
|
||||
* @param \Drupal\migrate\Row $row
|
||||
*/
|
||||
public function __construct(MigrationInterface $migration, Row $row) {
|
||||
$this->migration = $migration;
|
||||
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message, Row $row) {
|
||||
parent::__construct($migration, $message);
|
||||
$this->row = $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the migration entity.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrationInterface
|
||||
* The migration entity being imported.
|
||||
*/
|
||||
public function getMigration() {
|
||||
return $this->migration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the row object.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Event;
|
||||
|
||||
/**
|
||||
* Interface for plugins that react to pre- or post-rollback events.
|
||||
*/
|
||||
interface RollbackAwareInterface {
|
||||
|
||||
/**
|
||||
* Performs pre-rollback tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The pre-rollback event object.
|
||||
*/
|
||||
public function preRollback(MigrateRollbackEvent $event);
|
||||
|
||||
/**
|
||||
* Performs post-rollback tasks.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The post-rollback event object.
|
||||
*/
|
||||
public function postRollback(MigrateRollbackEvent $event);
|
||||
|
||||
}
|
||||
@@ -56,7 +56,7 @@ class RequirementsException extends \RuntimeException {
|
||||
$output = '';
|
||||
foreach ($this->requirements as $requirement_type => $requirements) {
|
||||
if (!is_array($requirements)) {
|
||||
$requirements = array($requirements);
|
||||
$requirements = [$requirements];
|
||||
}
|
||||
|
||||
foreach ($requirements as $value) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Utility\Bytes;
|
||||
use Drupal\Core\Utility\Error;
|
||||
use Drupal\Core\StringTranslation\StringTranslationTrait;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
@@ -64,14 +65,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $counts = array();
|
||||
|
||||
/**
|
||||
* The object currently being constructed.
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $destinationValues;
|
||||
protected $counts = [];
|
||||
|
||||
/**
|
||||
* The source.
|
||||
@@ -80,13 +74,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* The current data row retrieved from the source.
|
||||
*
|
||||
* @var \stdClass
|
||||
*/
|
||||
protected $sourceValues;
|
||||
|
||||
/**
|
||||
* The event dispatcher.
|
||||
*
|
||||
@@ -94,6 +81,15 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
*/
|
||||
protected $eventDispatcher;
|
||||
|
||||
/**
|
||||
* Migration message service.
|
||||
*
|
||||
* @todo https://www.drupal.org/node/2822663 Make this protected.
|
||||
*
|
||||
* @var \Drupal\migrate\MigrateMessageInterface
|
||||
*/
|
||||
public $message;
|
||||
|
||||
/**
|
||||
* Constructs a MigrateExecutable and verifies and sets the memory limit.
|
||||
*
|
||||
@@ -117,23 +113,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
$this->memoryLimit = PHP_INT_MAX;
|
||||
}
|
||||
else {
|
||||
if (!is_numeric($limit)) {
|
||||
$last = strtolower(substr($limit, -1));
|
||||
switch ($last) {
|
||||
case 'g':
|
||||
$limit *= 1024;
|
||||
case 'm':
|
||||
$limit *= 1024;
|
||||
case 'k':
|
||||
$limit *= 1024;
|
||||
break;
|
||||
default:
|
||||
$limit = PHP_INT_MAX;
|
||||
$this->message->display($this->t('Invalid PHP memory_limit @limit, setting to unlimited.',
|
||||
array('@limit' => $limit)));
|
||||
}
|
||||
}
|
||||
$this->memoryLimit = $limit;
|
||||
$this->memoryLimit = Bytes::toInt($limit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,13 +151,13 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
// Only begin the import operation if the migration is currently idle.
|
||||
if ($this->migration->getStatus() !== MigrationInterface::STATUS_IDLE) {
|
||||
$this->message->display($this->t('Migration @id is busy with another operation: @status',
|
||||
array(
|
||||
[
|
||||
'@id' => $this->migration->id(),
|
||||
'@status' => $this->t($this->migration->getStatusLabel()),
|
||||
)), 'error');
|
||||
]), 'error');
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration));
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
||||
|
||||
// Knock off migration if the requirements haven't been met.
|
||||
try {
|
||||
@@ -185,11 +165,17 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
}
|
||||
catch (RequirementsException $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration @id did not meet the requirements. @message @requirements', array(
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
'@requirements' => $e->getRequirementsString(),
|
||||
)), 'error');
|
||||
$this->t(
|
||||
'Migration @id did not meet the requirements. @message @requirements',
|
||||
[
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
'@requirements' => $e->getRequirementsString(),
|
||||
]
|
||||
),
|
||||
'error'
|
||||
);
|
||||
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
@@ -203,7 +189,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: @e', array('@e' => $e->getMessage())), 'error');
|
||||
$this->t('Migration failed with source plugin exception: @e', ['@e' => $e->getMessage()]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
@@ -218,20 +204,25 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
$save = TRUE;
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, array(), $e->getStatus());
|
||||
$this->migration->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
$save = FALSE;
|
||||
}
|
||||
catch (MigrateSkipRowException $e) {
|
||||
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED);
|
||||
if ($e->getSaveToMap()) {
|
||||
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
}
|
||||
if ($message = trim($e->getMessage())) {
|
||||
$this->saveMessage($message, MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
}
|
||||
$save = FALSE;
|
||||
}
|
||||
|
||||
if ($save) {
|
||||
try {
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $row));
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::PRE_ROW_SAVE, new MigratePreRowSaveEvent($this->migration, $this->message, $row));
|
||||
$destination_id_values = $destination->import($row, $id_map->lookupDestinationId($this->sourceIdValues));
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $row, $destination_id_values));
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROW_SAVE, new MigratePostRowSaveEvent($this->migration, $this->message, $row, $destination_id_values));
|
||||
if ($destination_id_values) {
|
||||
// We do not save an idMap entry for config.
|
||||
if ($destination_id_values !== TRUE) {
|
||||
@@ -239,7 +230,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED);
|
||||
$id_map->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
||||
if (!$id_map->messageCount()) {
|
||||
$message = $this->t('New object was not saved, no error provided');
|
||||
$this->saveMessage($message);
|
||||
@@ -248,20 +239,15 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
}
|
||||
}
|
||||
catch (MigrateException $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, array(), $e->getStatus());
|
||||
$this->migration->getIdMap()->saveIdMapping($row, [], $e->getStatus());
|
||||
$this->saveMessage($e->getMessage(), $e->getLevel());
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$this->migration->getIdMap()->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED);
|
||||
$this->migration->getIdMap()->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_FAILED);
|
||||
$this->handleException($e);
|
||||
}
|
||||
}
|
||||
if ($high_water_property = $this->migration->getHighWaterProperty()) {
|
||||
$this->migration->saveHighWater($row->getSourceProperty($high_water_property['name']));
|
||||
}
|
||||
|
||||
// Reset row properties.
|
||||
unset($sourceValues, $destinationValues);
|
||||
$this->sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
// Check for memory exhaustion.
|
||||
@@ -282,13 +268,13 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
catch (\Exception $e) {
|
||||
$this->message->display(
|
||||
$this->t('Migration failed with source plugin exception: @e',
|
||||
array('@e' => $e->getMessage())), 'error');
|
||||
['@e' => $e->getMessage()]), 'error');
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_IMPORT, new MigrateImportEvent($this->migration));
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_IMPORT, new MigrateImportEvent($this->migration, $this->message));
|
||||
$this->migration->setStatus(MigrationInterface::STATUS_IDLE);
|
||||
return $return;
|
||||
}
|
||||
@@ -329,6 +315,12 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
// We're now done with this row, so remove it from the map.
|
||||
$id_map->deleteDestination($destination_key);
|
||||
}
|
||||
else {
|
||||
// If there is no destination key the import probably failed and we can
|
||||
// remove the row without further action.
|
||||
$source_key = $id_map->currentSource();
|
||||
$id_map->delete($source_key);
|
||||
}
|
||||
|
||||
// Check for memory exhaustion.
|
||||
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
|
||||
@@ -342,10 +334,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// If rollback completed successfully, reset the high water mark.
|
||||
if ($return == MigrationInterface::RESULT_COMPLETED) {
|
||||
$this->migration->saveHighWater(NULL);
|
||||
}
|
||||
|
||||
// Notify modules that rollback attempt was complete.
|
||||
$this->getEventDispatcher()->dispatch(MigrateEvents::POST_ROLLBACK, new MigrateRollbackEvent($this->migration));
|
||||
@@ -368,9 +356,9 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
// plugin) and in this case the current value needs to be iterated
|
||||
// and each scalar separately transformed.
|
||||
if ($multiple && !$definition['handle_multiples']) {
|
||||
$new_value = array();
|
||||
$new_value = [];
|
||||
if (!is_array($value)) {
|
||||
throw new MigrateException(sprintf('Pipeline failed for destination %s: %s got instead of an array,', $destination, $value));
|
||||
throw new MigrateException(sprintf('Pipeline failed at %s plugin for destination %s: %s received instead of an array,', $plugin->getPluginId(), $destination, $value));
|
||||
}
|
||||
$break = FALSE;
|
||||
foreach ($value as $scalar_value) {
|
||||
@@ -395,7 +383,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
$value = NULL;
|
||||
break;
|
||||
}
|
||||
$multiple = $multiple || $plugin->multiple();
|
||||
$multiple = $plugin->multiple();
|
||||
}
|
||||
}
|
||||
// No plugins or no value means do not set.
|
||||
@@ -470,30 +458,44 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
||||
}
|
||||
if ($pct_memory > $threshold) {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
|
||||
array('@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit))),
|
||||
'warning');
|
||||
$this->t(
|
||||
'Memory usage is @usage (@pct% of limit @limit), reclaiming memory.',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
$usage = $this->attemptMemoryReclaim();
|
||||
$pct_memory = $usage / $this->memoryLimit;
|
||||
// Use a lower threshold - we don't want to be in a situation where we keep
|
||||
// coming back here and trimming a tiny amount
|
||||
if ($pct_memory > (0.90 * $threshold)) {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
|
||||
array('@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit))),
|
||||
'warning');
|
||||
$this->t(
|
||||
'Memory usage is now @usage (@pct% of limit @limit), not enough reclaimed, starting new batch',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'warning'
|
||||
);
|
||||
return TRUE;
|
||||
}
|
||||
else {
|
||||
$this->message->display(
|
||||
$this->t('Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
|
||||
array('@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit))),
|
||||
$this->t(
|
||||
'Memory usage is now @usage (@pct% of limit @limit), reclaimed enough, continuing',
|
||||
[
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
]
|
||||
),
|
||||
'warning');
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ class MigrateMessage implements MigrateMessageInterface {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $map = array(
|
||||
protected $map = [
|
||||
'status' => RfcLogLevel::INFO,
|
||||
'error' => RfcLogLevel::ERROR,
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
|
||||
@@ -12,7 +12,7 @@ class MigrateEntity implements ContainerDeriverInterface {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity definitions
|
||||
@@ -59,12 +59,12 @@ class MigrateEntity implements ContainerDeriverInterface {
|
||||
$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(
|
||||
$this->derivatives[$entity_type] = [
|
||||
'id' => "entity:$entity_type",
|
||||
'class' => $class,
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
);
|
||||
];
|
||||
}
|
||||
return $this->derivatives;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class MigrateEntityRevision implements ContainerDeriverInterface {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $derivatives = array();
|
||||
protected $derivatives = [];
|
||||
|
||||
/**
|
||||
* The entity definitions
|
||||
@@ -57,12 +57,12 @@ class MigrateEntityRevision implements ContainerDeriverInterface {
|
||||
public function getDerivativeDefinitions($base_plugin_definition) {
|
||||
foreach ($this->entityDefinitions as $entity_type => $entity_info) {
|
||||
if ($entity_info->getKey('revision')) {
|
||||
$this->derivatives[$entity_type] = array(
|
||||
$this->derivatives[$entity_type] = [
|
||||
'id' => "entity_revision:$entity_type",
|
||||
'class' => 'Drupal\migrate\Plugin\migrate\destination\EntityRevision',
|
||||
'requirements_met' => 1,
|
||||
'provider' => $entity_info->getProvider(),
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
return $this->derivatives;
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Doctrine\Common\Annotations\AnnotationRegistry;
|
||||
use Doctrine\Common\Reflection\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
use Drupal\Component\Annotation\AnnotationInterface;
|
||||
use Drupal\Component\Annotation\Reflection\MockFileFinder;
|
||||
use Drupal\Component\ClassFinder\ClassFinder;
|
||||
use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
|
||||
use Drupal\migrate\Annotation\MultipleProviderAnnotationInterface;
|
||||
|
||||
/**
|
||||
* Determines providers based on a class's and its parent's namespaces.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class AnnotatedClassDiscoveryAutomatedProviders extends AnnotatedClassDiscovery {
|
||||
|
||||
/**
|
||||
* A utility object that can use active autoloaders to find files for classes.
|
||||
*
|
||||
* @var \Doctrine\Common\Reflection\ClassFinderInterface
|
||||
*/
|
||||
protected $finder;
|
||||
|
||||
/**
|
||||
* Constructs an AnnotatedClassDiscoveryAutomatedProviders object.
|
||||
*
|
||||
* @param string $subdir
|
||||
* Either the plugin's subdirectory, for example 'Plugin/views/filter', or
|
||||
* empty string if plugins are located at the top level of the namespace.
|
||||
* @param \Traversable $root_namespaces
|
||||
* An object that implements \Traversable which contains the root paths
|
||||
* keyed by the corresponding namespace to look for plugin implementations.
|
||||
* If $subdir is not an empty string, it will be appended to each namespace.
|
||||
* @param string $plugin_definition_annotation_name
|
||||
* The name of the annotation that contains the plugin definition.
|
||||
* Defaults to 'Drupal\Component\Annotation\Plugin'.
|
||||
* @param string[] $annotation_namespaces
|
||||
* Additional namespaces to scan for annotation definitions.
|
||||
*/
|
||||
public function __construct($subdir, \Traversable $root_namespaces, $plugin_definition_annotation_name = 'Drupal\Component\Annotation\Plugin', array $annotation_namespaces = []) {
|
||||
parent::__construct($subdir, $root_namespaces, $plugin_definition_annotation_name, $annotation_namespaces);
|
||||
$this->finder = new ClassFinder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function prepareAnnotationDefinition(AnnotationInterface $annotation, $class, BaseStaticReflectionParser $parser = NULL) {
|
||||
if (!($annotation instanceof MultipleProviderAnnotationInterface)) {
|
||||
throw new \LogicException('AnnotatedClassDiscoveryAutomatedProviders annotations must implement \Drupal\migrate\Annotation\MultipleProviderAnnotationInterface');
|
||||
}
|
||||
$annotation->setClass($class);
|
||||
$providers = $annotation->getProviders();
|
||||
// Loop through all the parent classes and add their providers (which we
|
||||
// infer by parsing their namespaces) to the $providers array.
|
||||
do {
|
||||
$providers[] = $this->getProviderFromNamespace($parser->getNamespaceName());
|
||||
} while ($parser = StaticReflectionParser::getParentParser($parser, $this->finder));
|
||||
$providers = array_unique(array_filter($providers, function ($provider) {
|
||||
return $provider && $provider !== 'component';
|
||||
}));
|
||||
$annotation->setProviders($providers);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
$definitions = [];
|
||||
|
||||
$reader = $this->getAnnotationReader();
|
||||
|
||||
// Clear the annotation loaders of any previous annotation classes.
|
||||
AnnotationRegistry::reset();
|
||||
// Register the namespaces of classes that can be used for annotations.
|
||||
AnnotationRegistry::registerLoader('class_exists');
|
||||
|
||||
// Search for classes within all PSR-0 namespace locations.
|
||||
foreach ($this->getPluginNamespaces() as $namespace => $dirs) {
|
||||
foreach ($dirs as $dir) {
|
||||
if (file_exists($dir)) {
|
||||
$iterator = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS)
|
||||
);
|
||||
foreach ($iterator as $fileinfo) {
|
||||
if ($fileinfo->getExtension() == 'php') {
|
||||
if ($cached = $this->fileCache->get($fileinfo->getPathName())) {
|
||||
if (isset($cached['id'])) {
|
||||
// Explicitly unserialize this to create a new object instance.
|
||||
$definitions[$cached['id']] = unserialize($cached['content']);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$sub_path = $iterator->getSubIterator()->getSubPath();
|
||||
$sub_path = $sub_path ? str_replace(DIRECTORY_SEPARATOR, '\\', $sub_path) . '\\' : '';
|
||||
$class = $namespace . '\\' . $sub_path . $fileinfo->getBasename('.php');
|
||||
|
||||
// The filename is already known, so there is no need to find the
|
||||
// file. However, StaticReflectionParser needs a finder, so use a
|
||||
// mock version.
|
||||
$finder = MockFileFinder::create($fileinfo->getPathName());
|
||||
$parser = new BaseStaticReflectionParser($class, $finder, FALSE);
|
||||
|
||||
/** @var $annotation \Drupal\Component\Annotation\AnnotationInterface */
|
||||
if ($annotation = $reader->getClassAnnotation($parser->getReflectionClass(), $this->pluginDefinitionAnnotationName)) {
|
||||
$this->prepareAnnotationDefinition($annotation, $class, $parser);
|
||||
|
||||
$id = $annotation->getId();
|
||||
$content = $annotation->get();
|
||||
$definitions[$id] = $content;
|
||||
// Explicitly serialize this to create a new object instance.
|
||||
$this->fileCache->set($fileinfo->getPathName(), ['id' => $id, 'content' => serialize($content)]);
|
||||
}
|
||||
else {
|
||||
// Store a NULL object, so the file is not reparsed again.
|
||||
$this->fileCache->set($fileinfo->getPathName(), [NULL]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't let annotation loaders pile up.
|
||||
AnnotationRegistry::reset();
|
||||
|
||||
return $definitions;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* Remove plugin definitions with non-existing providers.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class ProviderFilterDecorator implements DiscoveryInterface {
|
||||
|
||||
use DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* The Discovery object being decorated.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* A callable for testing if a provider exists.
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
protected $providerExists;
|
||||
|
||||
/**
|
||||
* Constructs a InheritProviderDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
|
||||
* The object implementing DiscoveryInterface that is being decorated.
|
||||
* @param callable $provider_exists
|
||||
* A callable, gets passed a provider name, should return TRUE if the
|
||||
* provider exists and FALSE if not.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated, callable $provider_exists) {
|
||||
$this->decorated = $decorated;
|
||||
$this->providerExists = $provider_exists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes plugin definitions with non-existing providers.
|
||||
*
|
||||
* @param mixed[] $definitions
|
||||
* An array of plugin definitions (empty array if no definitions were
|
||||
* found). Keys are plugin IDs.
|
||||
* @param callable $provider_exists
|
||||
* A callable, gets passed a provider name, should return TRUE if the
|
||||
* provider exists and FALSE if not.
|
||||
*
|
||||
* @return array|\mixed[]
|
||||
* An array of plugin definitions. If a definition is an array and has a
|
||||
* provider key that provider is guaranteed to exist.
|
||||
*/
|
||||
public static function filterDefinitions(array $definitions, callable $provider_exists) {
|
||||
// Besides what the caller accepts, we also accept core or component.
|
||||
$provider_exists = function ($provider) use ($provider_exists) {
|
||||
return in_array($provider, ['core', 'component']) || $provider_exists($provider);
|
||||
};
|
||||
return array_filter($definitions, function ($definition) use ($provider_exists) {
|
||||
// Plugin definitions can be objects (for example, Typed Data) those will
|
||||
// become empty array here and cause no problems.
|
||||
$definition = (array) $definition + ['provider' => []];
|
||||
// There can be one or many providers, handle them as multiple always.
|
||||
$providers = (array) $definition['provider'];
|
||||
return count($providers) == count(array_filter($providers, $provider_exists));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
return static::filterDefinitions($this->decorated->getDefinitions(), $this->providerExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the decorated object.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to call on the decorated object.
|
||||
* @param array $args
|
||||
* Call arguments.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value from the method on the decorated object.
|
||||
*/
|
||||
public function __call($method, array $args) {
|
||||
return call_user_func_array([$this->decorated, $method], $args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\Discovery;
|
||||
|
||||
use Doctrine\Common\Reflection\StaticReflectionParser as BaseStaticReflectionParser;
|
||||
|
||||
/**
|
||||
* Allows getting the reflection parser for the parent class.
|
||||
*
|
||||
* @internal
|
||||
* This is a temporary solution to the fact that migration source plugins have
|
||||
* more than one provider. This functionality will be moved to core in
|
||||
* https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
class StaticReflectionParser extends BaseStaticReflectionParser {
|
||||
|
||||
/**
|
||||
* If the current class extends another, get the parser for the latter.
|
||||
*
|
||||
* @param \Doctrine\Common\Reflection\StaticReflectionParser $parser
|
||||
* The current static parser.
|
||||
* @param $finder
|
||||
* The class finder. Must implement
|
||||
* \Doctrine\Common\Reflection\ClassFinderInterface, but can do so
|
||||
* implicitly (i.e., implements the interface's methods but not the actual
|
||||
* interface).
|
||||
*
|
||||
* @return static|null
|
||||
* The static parser for the parent if there's a parent class or NULL.
|
||||
*/
|
||||
public static function getParentParser(BaseStaticReflectionParser $parser, $finder) {
|
||||
// Ensure the class has been parsed before accessing the parentClassName
|
||||
// property.
|
||||
$parser->parse();
|
||||
if ($parser->parentClassName) {
|
||||
return new static($parser->parentClassName, $finder, $parser->classAnnotationOptimize);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,15 +21,59 @@ use Drupal\migrate\Row;
|
||||
interface MigrateDestinationInterface extends PluginInspectionInterface {
|
||||
|
||||
/**
|
||||
* Get the destination IDs.
|
||||
* Gets the destination IDs.
|
||||
*
|
||||
* To support MigrateIdMap maps, derived destination classes should return
|
||||
* schema field definition(s) corresponding to the primary key of the
|
||||
* destination being implemented. These are used to construct the destination
|
||||
* key fields of the map table for a migration using this destination.
|
||||
* field definition(s) corresponding to the primary key of the destination
|
||||
* being implemented. These are used to construct the destination key fields
|
||||
* of the map table for a migration using this destination.
|
||||
*
|
||||
* @return array
|
||||
* An array of IDs.
|
||||
* @return array[]
|
||||
* An associative array of field definitions keyed by field ID. Values are
|
||||
* associative arrays with a structure that contains the field type ('type'
|
||||
* key). The other keys are the field storage settings as they are returned
|
||||
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
|
||||
* composite destination primary key that is defined by an integer and a
|
||||
* string, the returned value might look like:
|
||||
* @code
|
||||
* return [
|
||||
* 'id' => [
|
||||
* 'type' => 'integer',
|
||||
* 'unsigned' => FALSE,
|
||||
* 'size' => 'big',
|
||||
* ],
|
||||
* 'version' => [
|
||||
* 'type' => 'string',
|
||||
* 'max_length' => 64,
|
||||
* 'is_ascii' => TRUE,
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* If 'type' points to a field plugin with multiple columns and needs to
|
||||
* refer to a column different than 'value', the key of that column will be
|
||||
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
|
||||
* @code
|
||||
* return [
|
||||
* 'format' => [
|
||||
* 'type' => 'text.format',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* Additional custom keys/values, that are not part of field storage
|
||||
* definition, can be passed in definitions:
|
||||
* @code
|
||||
* return [
|
||||
* 'nid' => [
|
||||
* 'type' => 'integer',
|
||||
* 'custom_setting' => 'some_value',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
|
||||
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
@@ -39,11 +83,8 @@ interface MigrateDestinationInterface extends PluginInspectionInterface {
|
||||
* 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? To be resolved with https://www.drupal.org/node/2543568.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* (optional) The migration containing this destination. Defaults to NULL.
|
||||
* Unused, will be removed before Drupal 9.0.x. Defaults to NULL.
|
||||
*
|
||||
* @return array
|
||||
* - Keys: machine names of the fields
|
||||
@@ -65,7 +106,7 @@ interface MigrateDestinationInterface extends PluginInspectionInterface {
|
||||
* @return mixed
|
||||
* The entity ID or an indication of success.
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array());
|
||||
public function import(Row $row, array $old_destination_id_values = []);
|
||||
|
||||
/**
|
||||
* Delete the specified destination object from the target Drupal.
|
||||
|
||||
@@ -54,7 +54,7 @@ class MigrateDestinationPluginManager extends MigratePluginManager {
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
|
||||
public function createInstance($plugin_id, array $configuration = [], MigrationInterface $migration = NULL) {
|
||||
if (substr($plugin_id, 0, 7) == 'entity:' && !$this->entityManager->getDefinition(substr($plugin_id, 7), FALSE)) {
|
||||
$plugin_id = 'null';
|
||||
}
|
||||
|
||||
@@ -243,6 +243,14 @@ interface MigrateIdMapInterface extends \Iterator, PluginInspectionInterface {
|
||||
*/
|
||||
public function currentDestination();
|
||||
|
||||
/**
|
||||
* Looks up the source identifier(s) currently being iterated.
|
||||
*
|
||||
* @return array
|
||||
* The source identifier values of the record, or NULL on failure.
|
||||
*/
|
||||
public function currentSource();
|
||||
|
||||
/**
|
||||
* Removes any persistent storage used by this map.
|
||||
*
|
||||
|
||||
@@ -21,7 +21,7 @@ use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigratePluginManager extends DefaultPluginManager {
|
||||
class MigratePluginManager extends DefaultPluginManager implements MigratePluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Constructs a MigratePluginManager object.
|
||||
@@ -41,18 +41,15 @@ class MigratePluginManager extends DefaultPluginManager {
|
||||
* 'Drupal\Component\Annotation\PluginID'.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, $annotation = 'Drupal\Component\Annotation\PluginID') {
|
||||
$plugin_interface = isset($plugin_interface_map[$type]) ? $plugin_interface_map[$type] : NULL;
|
||||
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, $plugin_interface, $annotation);
|
||||
parent::__construct("Plugin/migrate/$type", $namespaces, $module_handler, NULL, $annotation);
|
||||
$this->alterInfo('migrate_' . $type . '_info');
|
||||
$this->setCacheBackend($cache_backend, 'migrate_plugins_' . $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array(), MigrationInterface $migration = NULL) {
|
||||
public function createInstance($plugin_id, array $configuration = [], 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.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginManagerInterface;
|
||||
|
||||
interface MigratePluginManagerInterface extends PluginManagerInterface {
|
||||
|
||||
/**
|
||||
* Creates a pre-configured instance of a migration plugin.
|
||||
*
|
||||
* A specific createInstance method is necessary to pass the migration on.
|
||||
*
|
||||
* @param string $plugin_id
|
||||
* The ID of the plugin being instantiated.
|
||||
* @param array $configuration
|
||||
* An array of configuration relevant to the plugin instance.
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration context in which the plugin will run.
|
||||
*
|
||||
* @return object
|
||||
* A fully configured plugin instance.
|
||||
*
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* If the instance cannot be created, such as if the ID is invalid.
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = [], MigrationInterface $migration = NULL);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
@@ -48,9 +49,54 @@ interface MigrateSourceInterface extends \Countable, \Iterator, PluginInspection
|
||||
* prepareRow() or hook_migrate_prepare_row() to rewrite NULL values to
|
||||
* appropriate empty values (such as '' or 0).
|
||||
*
|
||||
* @return array
|
||||
* Array keyed by source field name, with values being a schema array
|
||||
* describing the field (such as ['type' => 'string]).
|
||||
* @return array[]
|
||||
* An associative array of field definitions keyed by field ID. Values are
|
||||
* associative arrays with a structure that contains the field type ('type'
|
||||
* key). The other keys are the field storage settings as they are returned
|
||||
* by FieldStorageDefinitionInterface::getSettings(). As an example, for a
|
||||
* composite source primary key that is defined by an integer and a
|
||||
* string, the returned value might look like:
|
||||
* @code
|
||||
* return [
|
||||
* 'id' => [
|
||||
* 'type' => 'integer',
|
||||
* 'unsigned' => FALSE,
|
||||
* 'size' => 'big',
|
||||
* ],
|
||||
* 'version' => [
|
||||
* 'type' => 'string',
|
||||
* 'max_length' => 64,
|
||||
* 'is_ascii' => TRUE,
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* If 'type' points to a field plugin with multiple columns and needs to
|
||||
* refer to a column different than 'value', the key of that column will be
|
||||
* appended as a suffix to the plugin name, separated by dot ('.'). Example:
|
||||
* @code
|
||||
* return [
|
||||
* 'format' => [
|
||||
* 'type' => 'text.format',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
* Additional custom keys/values, that are not part of field storage
|
||||
* definition, can be passed in definitions. The most common setting, passed
|
||||
* along the ID definition, is 'alias' used by SqlBase source plugin:
|
||||
* @code
|
||||
* return [
|
||||
* 'nid' => [
|
||||
* 'type' => 'integer',
|
||||
* 'alias' => 'n',
|
||||
* ],
|
||||
* ];
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem
|
||||
* @see \Drupal\Core\Field\Plugin\Field\FieldType\StringItem
|
||||
* @see \Drupal\text\Plugin\Field\FieldType\TextItem
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase
|
||||
*/
|
||||
public function getIds();
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\migrate\Plugin\Discovery\AnnotatedClassDiscoveryAutomatedProviders;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
|
||||
|
||||
/**
|
||||
* Plugin manager for migrate source plugins.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* @see \Drupal\migrate\Plugin\source\SourcePluginBase
|
||||
* @see \Drupal\migrate\Annotation\MigrateSource
|
||||
* @see plugin_api
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
class MigrateSourcePluginManager extends MigratePluginManager {
|
||||
|
||||
/**
|
||||
* MigrateSourcePluginManager constructor.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public function __construct($type, \Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
|
||||
parent::__construct($type, $namespaces, $cache_backend, $module_handler, 'Drupal\migrate\Annotation\MigrateSource');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getDiscovery() {
|
||||
if (!$this->discovery) {
|
||||
$discovery = new AnnotatedClassDiscoveryAutomatedProviders($this->subdir, $this->namespaces, $this->pluginDefinitionAnnotationName, $this->additionalAnnotationNamespaces);
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds plugin definitions.
|
||||
*
|
||||
* @return array
|
||||
* List of definitions to store in cache.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source
|
||||
* plugins have more than one provider. This functionality will be moved to
|
||||
* core in https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
protected function findDefinitions() {
|
||||
$definitions = $this->getDiscovery()->getDefinitions();
|
||||
foreach ($definitions as $plugin_id => &$definition) {
|
||||
$this->processDefinition($definition, $plugin_id);
|
||||
}
|
||||
$this->alterDefinitions($definitions);
|
||||
return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
|
||||
return $this->providerExists($provider);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -125,27 +125,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
*/
|
||||
protected $destinationIds = [];
|
||||
|
||||
/**
|
||||
* Information on the property used as the high watermark.
|
||||
*
|
||||
* Array of 'name' & (optional) db 'alias' properties used for high watermark.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $highWaterProperty;
|
||||
|
||||
/**
|
||||
* Indicate whether the primary system of record for this migration is the
|
||||
* source, or the destination (Drupal). In the source case, migration of
|
||||
* an existing object will completely replace the Drupal object with data from
|
||||
* the source side. In the destination case, the existing Drupal object will
|
||||
* be loaded, then changes from the source applied; also, rollback will not be
|
||||
* supported.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $systemOfRecord = self::SOURCE;
|
||||
|
||||
/**
|
||||
* Specify value of source_row_status for current map row. Usually set by
|
||||
* MigrateFieldHandler implementations.
|
||||
@@ -154,11 +133,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
*/
|
||||
protected $sourceRowStatus = MigrateIdMapInterface::STATUS_IMPORTED;
|
||||
|
||||
/**
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $highWaterStorage;
|
||||
|
||||
/**
|
||||
* Track time of last import if TRUE.
|
||||
*
|
||||
@@ -173,6 +147,13 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
*/
|
||||
protected $requirements = [];
|
||||
|
||||
/**
|
||||
* An optional list of tags, used by the plugin manager for filtering.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migration_tags = [];
|
||||
|
||||
/**
|
||||
* These migrations, if run, must be executed before this migration.
|
||||
*
|
||||
@@ -268,16 +249,16 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
* The plugin definition.
|
||||
* @param \Drupal\migrate\Plugin\MigrationPluginManagerInterface $migration_plugin_manager
|
||||
* The migration plugin manager.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $source_plugin_manager
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $source_plugin_manager
|
||||
* The source migration plugin manager.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $process_plugin_manager
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $process_plugin_manager
|
||||
* The process migration plugin manager.
|
||||
* @param \Drupal\migrate\Plugin\MigrateDestinationPluginManager $destination_plugin_manager
|
||||
* The destination migration plugin manager.
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManager $idmap_plugin_manager
|
||||
* @param \Drupal\migrate\Plugin\MigratePluginManagerInterface $idmap_plugin_manager
|
||||
* The ID map migration plugin manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $source_plugin_manager, MigratePluginManager $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManager $idmap_plugin_manager) {
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $source_plugin_manager, MigratePluginManagerInterface $process_plugin_manager, MigrateDestinationPluginManager $destination_plugin_manager, MigratePluginManagerInterface $idmap_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$this->sourcePluginManager = $source_plugin_manager;
|
||||
@@ -285,7 +266,7 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
$this->destinationPluginManager = $destination_plugin_manager;
|
||||
$this->idMapPluginManager = $idmap_plugin_manager;
|
||||
|
||||
foreach ($plugin_definition as $key => $value) {
|
||||
foreach (NestedArray::mergeDeep($plugin_definition, $configuration) as $key => $value) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
@@ -365,9 +346,9 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
}
|
||||
$index = serialize($process);
|
||||
if (!isset($this->processPlugins[$index])) {
|
||||
$this->processPlugins[$index] = array();
|
||||
$this->processPlugins[$index] = [];
|
||||
foreach ($this->getProcessNormalized($process) as $property => $configurations) {
|
||||
$this->processPlugins[$index][$property] = array();
|
||||
$this->processPlugins[$index][$property] = [];
|
||||
foreach ($configurations as $configuration) {
|
||||
if (isset($configuration['source'])) {
|
||||
$this->processPlugins[$index][$property][] = $this->processPluginManager->createInstance('get', $configuration, $this);
|
||||
@@ -395,16 +376,16 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
* The normalized process configuration.
|
||||
*/
|
||||
protected function getProcessNormalized(array $process) {
|
||||
$normalized_configurations = array();
|
||||
$normalized_configurations = [];
|
||||
foreach ($process as $destination => $configuration) {
|
||||
if (is_string($configuration)) {
|
||||
$configuration = array(
|
||||
$configuration = [
|
||||
'plugin' => 'get',
|
||||
'source' => $configuration,
|
||||
);
|
||||
];
|
||||
}
|
||||
if (isset($configuration['plugin'])) {
|
||||
$configuration = array($configuration);
|
||||
$configuration = [$configuration];
|
||||
}
|
||||
$normalized_configurations[$destination] = $configuration;
|
||||
}
|
||||
@@ -436,33 +417,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
return $this->idMapPlugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the high water storage object.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* The storage object.
|
||||
*/
|
||||
protected function getHighWaterStorage() {
|
||||
if (!isset($this->highWaterStorage)) {
|
||||
$this->highWaterStorage = \Drupal::keyValue('migrate:high_water');
|
||||
}
|
||||
return $this->highWaterStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighWater() {
|
||||
return $this->getHighWaterStorage()->get($this->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function saveHighWater($high_water) {
|
||||
$this->getHighWaterStorage()->set($this->id(), $high_water);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -628,21 +582,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSystemOfRecord() {
|
||||
return $this->systemOfRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setSystemOfRecord($system_of_record) {
|
||||
$this->systemOfRecord = $system_of_record;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -662,7 +601,30 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationDependencies() {
|
||||
return ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
|
||||
$this->migration_dependencies = ($this->migration_dependencies ?: []) + ['required' => [], 'optional' => []];
|
||||
$this->migration_dependencies['optional'] = array_unique(array_merge($this->migration_dependencies['optional'], $this->findMigrationDependencies($this->process)));
|
||||
return $this->migration_dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find migration dependencies from the migration and the iterator plugins.
|
||||
*
|
||||
* @param $process
|
||||
* @return array
|
||||
*/
|
||||
protected function findMigrationDependencies($process) {
|
||||
$return = [];
|
||||
foreach ($this->getProcessNormalized($process) as $process_pipeline) {
|
||||
foreach ($process_pipeline as $plugin_configuration) {
|
||||
if ($plugin_configuration['plugin'] == 'migration') {
|
||||
$return = array_merge($return, (array) $plugin_configuration['migration']);
|
||||
}
|
||||
if ($plugin_configuration['plugin'] == 'iterator') {
|
||||
$return = array_merge($return, $this->findMigrationDependencies($plugin_configuration['process']));
|
||||
}
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -692,13 +654,6 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getHighWaterProperty() {
|
||||
return $this->highWaterProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -713,4 +668,11 @@ class Migration extends PluginBase implements MigrationInterface, RequirementsIn
|
||||
return $this->destinationIds;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMigrationTags() {
|
||||
return $this->migration_tags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -10,16 +10,6 @@ use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
*/
|
||||
interface MigrationInterface extends PluginInspectionInterface, DerivativeInspectionInterface {
|
||||
|
||||
/**
|
||||
* A constant used for systemOfRecord.
|
||||
*/
|
||||
const SOURCE = 'source';
|
||||
|
||||
/**
|
||||
* A constant used for systemOfRecord.
|
||||
*/
|
||||
const DESTINATION = 'destination';
|
||||
|
||||
/**
|
||||
* The migration is currently not running.
|
||||
*/
|
||||
@@ -152,26 +142,6 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
|
||||
*/
|
||||
public function getIdMap();
|
||||
|
||||
/**
|
||||
* The current value of the high water mark.
|
||||
*
|
||||
* The high water mark defines a timestamp stating the time the import was last
|
||||
* run. If the mark is set, only content with a higher timestamp will be
|
||||
* imported.
|
||||
*
|
||||
* @return int
|
||||
* A Unix timestamp representing the high water mark.
|
||||
*/
|
||||
public function getHighWater();
|
||||
|
||||
/**
|
||||
* Save the new high water mark.
|
||||
*
|
||||
* @param int $high_water
|
||||
* The high water timestamp.
|
||||
*/
|
||||
public function saveHighWater($high_water);
|
||||
|
||||
/**
|
||||
* Check if all source rows from this migration have been processed.
|
||||
*
|
||||
@@ -283,24 +253,6 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
|
||||
*/
|
||||
public function mergeProcessOfProperty($property, array $process_of_property);
|
||||
|
||||
/**
|
||||
* Get the current system of record of the migration.
|
||||
*
|
||||
* @return string
|
||||
* The current system of record of the migration.
|
||||
*/
|
||||
public function getSystemOfRecord();
|
||||
|
||||
/**
|
||||
* Set the system of record for the migration.
|
||||
*
|
||||
* @param string $system_of_record
|
||||
* The system of record of the migration.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function setSystemOfRecord($system_of_record);
|
||||
|
||||
/**
|
||||
* Checks if the migration should track time of last import.
|
||||
*
|
||||
@@ -343,18 +295,6 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
|
||||
*/
|
||||
public function getSourceConfiguration();
|
||||
|
||||
/**
|
||||
* Get information on the property used as the high watermark.
|
||||
*
|
||||
* Array of 'name' & (optional) db 'alias' properties used for high watermark.
|
||||
*
|
||||
* @see Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()
|
||||
*
|
||||
* @return array
|
||||
* The property used as the high watermark.
|
||||
*/
|
||||
public function getHighWaterProperty();
|
||||
|
||||
/**
|
||||
* If true, track time of last import.
|
||||
*
|
||||
@@ -374,4 +314,12 @@ interface MigrationInterface extends PluginInspectionInterface, DerivativeInspec
|
||||
*/
|
||||
public function getDestinationIds();
|
||||
|
||||
/**
|
||||
* The migration tags.
|
||||
*
|
||||
* @return array
|
||||
* Migration tags.
|
||||
*/
|
||||
public function getMigrationTags();
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\Core\Plugin\DefaultPluginManager;
|
||||
use Drupal\Core\Plugin\Discovery\ContainerDerivativeDiscoveryDecorator;
|
||||
use Drupal\migrate\Plugin\Discovery\ProviderFilterDecorator;
|
||||
use Drupal\Core\Plugin\Discovery\YamlDirectoryDiscovery;
|
||||
use Drupal\Core\Plugin\Factory\ContainerFactory;
|
||||
use Drupal\migrate\MigrateBuildDependencyInterface;
|
||||
@@ -23,9 +24,9 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaults = array(
|
||||
protected $defaults = [
|
||||
'class' => '\Drupal\migrate\Plugin\Migration',
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* The interface the plugins should implement.
|
||||
@@ -54,7 +55,7 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
public function __construct(ModuleHandlerInterface $module_handler, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager) {
|
||||
$this->factory = new ContainerFactory($this, $this->pluginInterface);
|
||||
$this->alterInfo('migration_plugins');
|
||||
$this->setCacheBackend($cache_backend, 'migration_plugins', array('migration_plugins'));
|
||||
$this->setCacheBackend($cache_backend, 'migration_plugins', ['migration_plugins']);
|
||||
$this->moduleHandler = $module_handler;
|
||||
}
|
||||
|
||||
@@ -68,7 +69,15 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
}, $this->moduleHandler->getModuleDirectories());
|
||||
|
||||
$yaml_discovery = new YamlDirectoryDiscovery($directories, 'migrate');
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($yaml_discovery);
|
||||
// This gets rid of migrations which try to use a non-existent source
|
||||
// plugin. The common case for this is if the source plugin has, or
|
||||
// specifies, a non-existent provider.
|
||||
$only_with_source_discovery = new NoSourcePluginDecorator($yaml_discovery);
|
||||
// This gets rid of migrations with explicit providers set if one of the
|
||||
// providers do not exist before we try to use a potentially non-existing
|
||||
// deriver. This is a rare case.
|
||||
$filtered_discovery = new ProviderFilterDecorator($only_with_source_discovery, [$this->moduleHandler, 'moduleExists']);
|
||||
$this->discovery = new ContainerDerivativeDiscoveryDecorator($filtered_discovery);
|
||||
}
|
||||
return $this->discovery;
|
||||
}
|
||||
@@ -76,15 +85,15 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array()) {
|
||||
$instances = $this->createInstances([$plugin_id], $configuration);
|
||||
public function createInstance($plugin_id, array $configuration = []) {
|
||||
$instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
|
||||
return reset($instances);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstances($migration_id, array $configuration = array()) {
|
||||
public function createInstances($migration_id, array $configuration = []) {
|
||||
if (empty($migration_id)) {
|
||||
$migration_id = array_keys($this->getDefinitions());
|
||||
}
|
||||
@@ -213,9 +222,9 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
* The dynamic ID mapping.
|
||||
*/
|
||||
protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
|
||||
$dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : array($dependency);
|
||||
$dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : [$dependency];
|
||||
if (!isset($graph[$id]['edges'])) {
|
||||
$graph[$id]['edges'] = array();
|
||||
$graph[$id]['edges'] = [];
|
||||
}
|
||||
$graph[$id]['edges'] += array_combine($dependencies, $dependencies);
|
||||
}
|
||||
@@ -228,4 +237,25 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
||||
return Migration::create(\Drupal::getContainer(), [], $id, $definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds plugin definitions.
|
||||
*
|
||||
* @return array
|
||||
* List of definitions to store in cache.
|
||||
*
|
||||
* @todo This is a temporary solution to the fact that migration source
|
||||
* plugins have more than one provider. This functionality will be moved to
|
||||
* core in https://www.drupal.org/node/2786355.
|
||||
*/
|
||||
protected function findDefinitions() {
|
||||
$definitions = $this->getDiscovery()->getDefinitions();
|
||||
foreach ($definitions as $plugin_id => &$definition) {
|
||||
$this->processDefinition($definition, $plugin_id);
|
||||
}
|
||||
$this->alterDefinitions($definitions);
|
||||
return ProviderFilterDecorator::filterDefinitions($definitions, function ($provider) {
|
||||
return $this->providerExists($provider);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ interface MigrationPluginManagerInterface extends PluginManagerInterface {
|
||||
* @throws \Drupal\Component\Plugin\Exception\PluginException
|
||||
* If an instance cannot be created, such as if the ID is invalid.
|
||||
*/
|
||||
public function createInstances($id, array $configuration = array());
|
||||
public function createInstances($id, array $configuration = []);
|
||||
|
||||
/**
|
||||
* Creates a stub migration plugin from a definition array.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
|
||||
use Drupal\Component\Plugin\Discovery\DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* Remove definitions which refer to a non-existing source plugin.
|
||||
*/
|
||||
class NoSourcePluginDecorator implements DiscoveryInterface {
|
||||
|
||||
use DiscoveryTrait;
|
||||
|
||||
/**
|
||||
* The Discovery object being decorated.
|
||||
*
|
||||
* @var \Drupal\Component\Plugin\Discovery\DiscoveryInterface
|
||||
*/
|
||||
protected $decorated;
|
||||
|
||||
/**
|
||||
* Constructs a NoSourcePluginDecorator object.
|
||||
*
|
||||
* @param \Drupal\Component\Plugin\Discovery\DiscoveryInterface $decorated
|
||||
* The object implementing DiscoveryInterface that is being decorated.
|
||||
*/
|
||||
public function __construct(DiscoveryInterface $decorated) {
|
||||
$this->decorated = $decorated;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDefinitions() {
|
||||
/** @var \Drupal\Component\Plugin\PluginManagerInterface $source_plugin_manager */
|
||||
$source_plugin_manager = \Drupal::service('plugin.manager.migrate.source');
|
||||
return array_filter($this->decorated->getDefinitions(), function (array $definition) use ($source_plugin_manager) {
|
||||
return $source_plugin_manager->hasDefinition($definition['source']['plugin']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the decorated object.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to call on the decorated object.
|
||||
* @param array $args
|
||||
* Call arguments.
|
||||
*
|
||||
* @return mixed
|
||||
* The return value from the method on the decorated object.
|
||||
*/
|
||||
public function __call($method, array $args) {
|
||||
return call_user_func_array([$this->decorated, $method], $args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\migrate\Event\ImportAwareInterface;
|
||||
use Drupal\migrate\Event\MigrateEvents;
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
use Drupal\migrate\Event\MigrateRollbackEvent;
|
||||
use Drupal\migrate\Event\RollbackAwareInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Event subscriber to forward Migrate events to source and destination plugins.
|
||||
*/
|
||||
class PluginEventSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Tries to invoke event handling methods on source and destination plugins.
|
||||
*
|
||||
* @param string $method
|
||||
* The method to invoke.
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent|\Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The event that has triggered the invocation.
|
||||
* @param string $plugin_interface
|
||||
* The interface which plugins must implement in order to be invoked.
|
||||
*/
|
||||
protected function invoke($method, $event, $plugin_interface) {
|
||||
$migration = $event->getMigration();
|
||||
|
||||
$source = $migration->getSourcePlugin();
|
||||
if ($source instanceof $plugin_interface) {
|
||||
call_user_func([$source, $method], $event);
|
||||
}
|
||||
|
||||
$destination = $migration->getDestinationPlugin();
|
||||
if ($destination instanceof $plugin_interface) {
|
||||
call_user_func([$destination, $method], $event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards pre-import events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The import event.
|
||||
*/
|
||||
public function preImport(MigrateImportEvent $event) {
|
||||
$this->invoke('preImport', $event, ImportAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards post-import events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateImportEvent $event
|
||||
* The import event.
|
||||
*/
|
||||
public function postImport(MigrateImportEvent $event) {
|
||||
$this->invoke('postImport', $event, ImportAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards pre-rollback events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The rollback event.
|
||||
*/
|
||||
public function preRollback(MigrateRollbackEvent $event) {
|
||||
$this->invoke('preRollback', $event, RollbackAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forwards post-rollback events to the source and destination plugins.
|
||||
*
|
||||
* @param \Drupal\migrate\Event\MigrateRollbackEvent $event
|
||||
* The rollback event.
|
||||
*/
|
||||
public function postRollback(MigrateRollbackEvent $event) {
|
||||
$this->invoke('postRollback', $event, RollbackAwareInterface::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function getSubscribedEvents() {
|
||||
$events = [];
|
||||
$events[MigrateEvents::PRE_IMPORT][] = ['preImport'];
|
||||
$events[MigrateEvents::POST_IMPORT][] = ['postImport'];
|
||||
$events[MigrateEvents::PRE_ROLLBACK][] = ['preRollback'];
|
||||
$events[MigrateEvents::POST_ROLLBACK][] = ['postRollback'];
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,8 +15,8 @@ abstract class ComponentEntityDisplayBase extends DestinationBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
$values = array();
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$values = [];
|
||||
// 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) {
|
||||
@@ -24,7 +24,7 @@ abstract class ComponentEntityDisplayBase extends DestinationBase {
|
||||
}
|
||||
$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());
|
||||
$entity->setComponent($values['field_name'], $row->getDestinationProperty('options') ?: []);
|
||||
}
|
||||
else {
|
||||
$entity->removeComponent($values['field_name']);
|
||||
|
||||
@@ -14,10 +14,56 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
/**
|
||||
* Provides Configuration Management destination plugin.
|
||||
*
|
||||
* Persist data to the config system.
|
||||
* Persists data to the config system.
|
||||
*
|
||||
* When a property is NULL, the default is used unless the configuration option
|
||||
* 'store null' is set to TRUE.
|
||||
* Available configuration keys:
|
||||
* - store null: (optional) Boolean, if TRUE, when a property is NULL, NULL is
|
||||
* stored, otherwise the default is used. Defaults to FALSE.
|
||||
* - translations: (optional) Boolean, if TRUE, the destination will be
|
||||
* associated with the langcode provided by the source plugin. Defaults to
|
||||
* FALSE.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - config_name: The machine name of the config.
|
||||
* - langcode: (optional) The language code of the config.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: variable
|
||||
* variables:
|
||||
* - node_admin_theme
|
||||
* process:
|
||||
* use_admin_theme: node_admin_theme
|
||||
* destination:
|
||||
* plugin: config
|
||||
* config_name: node.settings
|
||||
* @endcode
|
||||
*
|
||||
* This will add the value of the variable "node_admin_theme" to the config with
|
||||
* the machine name "node.settings" as "node.settings.use_admin_theme".
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: i18n_variable
|
||||
* variables:
|
||||
* - site_offline_message
|
||||
* process:
|
||||
* langcode: language
|
||||
* message: site_offline_message
|
||||
* destination:
|
||||
* plugin: config
|
||||
* config_name: system.maintenance
|
||||
* translations: true
|
||||
* @endcode
|
||||
*
|
||||
* This will add the value of the variable "site_offline_message" to the config
|
||||
* with the machine name "system.maintenance" as "system.maintenance.message",
|
||||
* coupled with the relevant langcode as obtained from the "i18n_variable"
|
||||
* source plugin.
|
||||
*
|
||||
* @see \Drupal\migrate_drupal\Plugin\migrate\source\d6\i18nVariable
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "config"
|
||||
@@ -54,13 +100,16 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
||||
* The migration entity.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
* @param \Drupal\Core\Language\ConfigurableLanguageManagerInterface $language_manager
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, ConfigFactoryInterface $config_factory, LanguageManagerInterface $language_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration);
|
||||
$this->config = $config_factory->getEditable($configuration['config_name']);
|
||||
$this->language_manager = $language_manager;
|
||||
if ($this->isTranslationDestination()) {
|
||||
$this->supportsRollback = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +129,8 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
if ($row->hasDestinationProperty('langcode')) {
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$this->config = $this->language_manager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $this->config->getName());
|
||||
}
|
||||
|
||||
@@ -91,7 +140,11 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
||||
}
|
||||
}
|
||||
$this->config->save();
|
||||
return [$this->config->getName()];
|
||||
$ids[] = $this->config->getName();
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids[] = $row->getDestinationProperty('langcode');
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,6 +159,9 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
||||
*/
|
||||
public function getIds() {
|
||||
$ids['config_name']['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
@@ -118,4 +174,25 @@ class Config extends DestinationBase implements ContainerFactoryPluginInterface,
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this destination is for translations.
|
||||
*
|
||||
* @return bool
|
||||
* Whether this destination is for translations.
|
||||
*/
|
||||
protected function isTranslationDestination() {
|
||||
return !empty($this->configuration['translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
$language = $destination_identifier['langcode'];
|
||||
$config = $this->language_manager->getLanguageConfigOverride($language, $this->config->getName());
|
||||
$config->delete();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,27 +3,126 @@
|
||||
namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\language\ConfigurableLanguageManager;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class for importing configuration entities.
|
||||
* Base destination 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).
|
||||
* Available configuration keys:
|
||||
* - translations: (optional) Boolean, if TRUE, the destination will be
|
||||
* associated with the langcode provided by the source plugin. Defaults to
|
||||
* FALSE.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_block_custom
|
||||
* process:
|
||||
* id: bid
|
||||
* info: info
|
||||
* langcode: language
|
||||
* body: body
|
||||
* destination:
|
||||
* plugin: entity:block
|
||||
* @endcode
|
||||
*
|
||||
* This will save the migrated, processed row as a block config entity.
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d6_i18n_profile_field
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* bundle: user
|
||||
* process:
|
||||
* langcode: language
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: 'constants/bundle'
|
||||
* field_name: name
|
||||
* ...
|
||||
* translation: translation
|
||||
* destination:
|
||||
* plugin: entity:field_config
|
||||
* translations: true
|
||||
* @endcode
|
||||
*
|
||||
* Because the translations configuration is set to "true", this will save the
|
||||
* migrated, processed row to a "field_config" entity associated with the
|
||||
* designated langcode.
|
||||
*/
|
||||
class EntityConfigBase extends Entity {
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The configuration factory.
|
||||
*
|
||||
* @var \Drupal\Core\Config\ConfigFactoryInterface;
|
||||
*/
|
||||
protected $configFactory;
|
||||
|
||||
/**
|
||||
* 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 \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration.
|
||||
* @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\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The configuration factory.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityStorageInterface $storage, array $bundles, LanguageManagerInterface $language_manager, ConfigFactoryInterface $config_factory) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition, $migration, $storage, $bundles);
|
||||
$this->languageManager = $language_manager;
|
||||
$this->configFactory = $config_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
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)),
|
||||
$container->get('language_manager'),
|
||||
$container->get('config.factory')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
if ($row->isStub()) {
|
||||
throw new MigrateException('Config entities can not be stubbed.');
|
||||
}
|
||||
@@ -39,17 +138,37 @@ class EntityConfigBase extends Entity {
|
||||
}
|
||||
}
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
$entity->save();
|
||||
// Translations are already saved in updateEntity by configuration override.
|
||||
if (!$this->isTranslationDestination()) {
|
||||
$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();
|
||||
$return = [];
|
||||
foreach ($id_keys as $id_key) {
|
||||
$return[] = $entity->get($id_key);
|
||||
if (($this->isTranslationDestination()) && ($id_key == 'langcode')) {
|
||||
// Config entities do not have a language property, get the language
|
||||
// code from the destination.
|
||||
$return[] = $row->getDestinationProperty($id_key);
|
||||
}
|
||||
else {
|
||||
$return[] = $entity->get($id_key);
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
return array($entity->id());
|
||||
return [$entity->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether this destination is for translations.
|
||||
*
|
||||
* @return bool
|
||||
* Whether this destination is for translations.
|
||||
*/
|
||||
protected function isTranslationDestination() {
|
||||
return !empty($this->configuration['translations']);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +177,9 @@ class EntityConfigBase extends Entity {
|
||||
public function getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
@@ -70,11 +192,28 @@ class EntityConfigBase extends Entity {
|
||||
* 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);
|
||||
// This is a translation if the language in the active config does not
|
||||
// match the language of this row.
|
||||
$translation = FALSE;
|
||||
if ($row->hasDestinationProperty('langcode') && $this->languageManager instanceof ConfigurableLanguageManager) {
|
||||
$config = $entity->getConfigDependencyName();
|
||||
$langcode = $this->configFactory->get('langcode');
|
||||
if ($langcode != $row->getDestinationProperty('langcode')) {
|
||||
$translation = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
$this->setRollbackAction($row->getIdMap());
|
||||
if ($translation) {
|
||||
$config_override = $this->languageManager->getLanguageConfigOverride($row->getDestinationProperty('langcode'), $config);
|
||||
$config_override->set(str_replace(Row::PROPERTY_SEPARATOR, '.', $row->getDestinationProperty('property')), $row->getDestinationProperty('translation'));
|
||||
$config_override->save();
|
||||
}
|
||||
else {
|
||||
foreach ($row->getRawDestination() as $property => $value) {
|
||||
$this->updateEntityProperty($entity, explode(Row::PROPERTY_SEPARATOR, $property), $value);
|
||||
}
|
||||
$this->setRollbackAction($row->getIdMap());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,11 +250,41 @@ class EntityConfigBase extends Entity {
|
||||
* The generated entity ID.
|
||||
*/
|
||||
protected function generateId(Row $row, array $ids) {
|
||||
$id_values = array();
|
||||
$id_values = [];
|
||||
foreach ($ids as $id) {
|
||||
if ($this->isTranslationDestination() && $id == 'langcode') {
|
||||
continue;
|
||||
}
|
||||
$id_values[] = $row->getDestinationProperty($id);
|
||||
}
|
||||
return implode('.', $id_values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
if ($this->isTranslationDestination()) {
|
||||
// The entity id does not include the langcode.
|
||||
$id_values = [];
|
||||
foreach ($destination_identifier as $key => $value) {
|
||||
if ($this->isTranslationDestination() && $key == 'langcode') {
|
||||
continue;
|
||||
}
|
||||
$id_values[] = $value;
|
||||
}
|
||||
$entity_id = implode('.', $id_values);
|
||||
$language = $destination_identifier['langcode'];
|
||||
|
||||
$config = $this->storage->load($entity_id)->getConfigDependencyName();
|
||||
$config_override = $this->languageManager->getLanguageConfigOverride($language, $config);
|
||||
// Rollback the translation.
|
||||
$config_override->delete();
|
||||
}
|
||||
else {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class EntityContentBase extends Entity {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
$this->rollbackAction = MigrateIdMapInterface::ROLLBACK_DELETE;
|
||||
$entity = $this->getEntity($row, $old_destination_id_values);
|
||||
if (!$entity) {
|
||||
@@ -105,9 +105,9 @@ class EntityContentBase extends Entity {
|
||||
* @return array
|
||||
* An array containing the entity ID.
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
$entity->save();
|
||||
return array($entity->id());
|
||||
return [$entity->id()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,15 +125,13 @@ class EntityContentBase extends Entity {
|
||||
*/
|
||||
public function getIds() {
|
||||
$id_key = $this->getKey('id');
|
||||
$ids[$id_key]['type'] = 'integer';
|
||||
$ids[$id_key] = $this->getDefinitionFromEntity($id_key);
|
||||
|
||||
if ($this->isTranslationDestination()) {
|
||||
if ($key = $this->getKey('langcode')) {
|
||||
$ids[$key]['type'] = 'string';
|
||||
}
|
||||
else {
|
||||
if (!$langcode_key = $this->getKey('langcode')) {
|
||||
throw new MigrateException('This entity type does not support translation.');
|
||||
}
|
||||
$ids[$langcode_key] = $this->getDefinitionFromEntity($langcode_key);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
@@ -147,7 +145,7 @@ class EntityContentBase extends Entity {
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row object to update from.
|
||||
*
|
||||
* @return NULL|\Drupal\Core\Entity\EntityInterface
|
||||
* @return \Drupal\Core\Entity\EntityInterface|null
|
||||
* An updated entity, or NULL if it's the same as the one passed in.
|
||||
*/
|
||||
protected function updateEntity(EntityInterface $entity, Row $row) {
|
||||
@@ -263,4 +261,32 @@ class EntityContentBase extends Entity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field definition from a specific entity base field.
|
||||
*
|
||||
* The method takes the field ID as an argument and returns the field storage
|
||||
* definition to be used in getIds() by querying the destination entity base
|
||||
* field definition.
|
||||
*
|
||||
* @param string $key
|
||||
* The field ID key.
|
||||
*
|
||||
* @return array
|
||||
* An associative array with a structure that contains the field type, keyed
|
||||
* as 'type', together with field storage settings as they are returned by
|
||||
* FieldStorageDefinitionInterface::getSettings().
|
||||
*
|
||||
* @see \Drupal\Core\Field\FieldStorageDefinitionInterface::getSettings()
|
||||
*/
|
||||
protected function getDefinitionFromEntity($key) {
|
||||
$entity_type_id = static::getEntityTypeId($this->getPluginId());
|
||||
/** @var \Drupal\Core\Field\FieldStorageDefinitionInterface[] $definitions */
|
||||
$definitions = $this->entityManager->getBaseFieldDefinitions($entity_type_id);
|
||||
$field_definition = $definitions[$key];
|
||||
|
||||
return [
|
||||
'type' => $field_definition->getType(),
|
||||
] + $field_definition->getSettings();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,6 +18,9 @@ class EntityFieldInstance extends EntityConfigBase {
|
||||
$ids['entity_type']['type'] = 'string';
|
||||
$ids['bundle']['type'] = 'string';
|
||||
$ids['field_name']['type'] = 'string';
|
||||
if ($this->isTranslationDestination()) {
|
||||
$ids['langcode']['type'] = 'string';
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,4 +20,12 @@ class EntityFieldStorageConfig extends EntityConfigBase {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,9 +63,9 @@ class EntityRevision extends EntityContentBase {
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = array()) {
|
||||
protected function save(ContentEntityInterface $entity, array $old_destination_id_values = []) {
|
||||
$entity->save();
|
||||
return array($entity->getRevisionId());
|
||||
return [$entity->getRevisionId()];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +73,7 @@ class EntityRevision extends EntityContentBase {
|
||||
*/
|
||||
public function getIds() {
|
||||
if ($key = $this->getKey('revision')) {
|
||||
$ids[$key]['type'] = 'integer';
|
||||
return $ids;
|
||||
return [$key => $this->getDefinitionFromEntity($key)];
|
||||
}
|
||||
throw new MigrateException('This entity type does not support revisions.');
|
||||
}
|
||||
|
||||
@@ -5,6 +5,25 @@ namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
/**
|
||||
* Provides entity view mode destination plugin.
|
||||
*
|
||||
* See EntityConfigBase for the available configuration options.
|
||||
* @see \Drupal\migrate\Plugin\migrate\destination\EntityConfigBase
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: d7_view_mode
|
||||
* process:
|
||||
* mode: view_mode
|
||||
* label: view_mode
|
||||
* targetEntityType: entity_type
|
||||
* destination:
|
||||
* plugin: entity:entity_view_mode
|
||||
* @endcode
|
||||
*
|
||||
* This will add the results of the process ("mode", "label" and
|
||||
* "targetEntityType") to an "entity_view_mode" entity.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "entity:entity_view_mode"
|
||||
* )
|
||||
@@ -20,4 +39,12 @@ class EntityViewMode extends EntityConfigBase {
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(array $destination_identifier) {
|
||||
$destination_identifier = implode('.', $destination_identifier);
|
||||
parent::rollback([$destination_identifier]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,20 +19,20 @@ class NullDestination extends DestinationBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields(MigrationInterface $migration = NULL) {
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function import(Row $row, array $old_destination_id_values = array()) {
|
||||
public function import(Row $row, array $old_destination_id_values = []) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,45 @@ namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
/**
|
||||
* This class imports one component of an entity display.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - entity_type: The entity type ID.
|
||||
* - bundle: The entity bundle.
|
||||
* - view_mode: The machine name of the view mode.
|
||||
* - field_name: The machine name of the field to be imported into the display.
|
||||
* - options: (optional) An array of options for displaying the field in this
|
||||
* view mode.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* constants:
|
||||
* entity_type: user
|
||||
* bundle: user
|
||||
* view_mode: default
|
||||
* field_name: user_picture
|
||||
* type: image
|
||||
* options:
|
||||
* label: hidden
|
||||
* settings:
|
||||
* image_style: ''
|
||||
* image_link: content
|
||||
* process:
|
||||
* entity_type: 'constants/entity_type'
|
||||
* bundle: 'constants/bundle'
|
||||
* view_mode: 'constants/view_mode'
|
||||
* field_name: 'constants/field_name'
|
||||
* type: 'constants/type'
|
||||
* options: 'constants/options'
|
||||
* 'options/type': '@type'
|
||||
* destination:
|
||||
* plugin: component_entity_display
|
||||
* @endcode
|
||||
*
|
||||
* This will add the "user_picture" image field to the "default" view mode of
|
||||
* the "user" bundle of the "user" entity type with options as defined by the
|
||||
* "options" constant, for example the label will be hidden.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "component_entity_display"
|
||||
* )
|
||||
|
||||
@@ -5,6 +5,38 @@ namespace Drupal\migrate\Plugin\migrate\destination;
|
||||
/**
|
||||
* This class imports one component of an entity form display.
|
||||
*
|
||||
* Destination properties expected in the imported row:
|
||||
* - entity_type: The entity type ID.
|
||||
* - bundle: The entity bundle.
|
||||
* - form_mode: The machine name of the form mode.
|
||||
* - field_name: The machine name of the field to be imported into the display.
|
||||
* - options: (optional) An array of options for displaying the field in this
|
||||
* form mode.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* constants:
|
||||
* entity_type: node
|
||||
* field_name: comment
|
||||
* form_mode: default
|
||||
* options:
|
||||
* type: comment_default
|
||||
* weight: 20
|
||||
* process:
|
||||
* entity_type: 'constants/entity_type'
|
||||
* field_name: 'constants/field_name'
|
||||
* form_mode: 'constants/form_mode'
|
||||
* options: 'constants/options'
|
||||
* bundle: node_type
|
||||
* destination:
|
||||
* plugin: component_entity_form_display
|
||||
* @endcode
|
||||
*
|
||||
* This will add a "comment" field on the "default" form mode of the "node"
|
||||
* entity type with options defined by the "options" constant.
|
||||
*
|
||||
* @MigrateDestination(
|
||||
* id = "component_entity_form_display"
|
||||
* )
|
||||
|
||||
@@ -115,14 +115,14 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = array();
|
||||
protected $sourceIds = [];
|
||||
|
||||
/**
|
||||
* The destination identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destinationIds = array();
|
||||
protected $destinationIds = [];
|
||||
|
||||
/**
|
||||
* The current row.
|
||||
@@ -136,7 +136,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $currentKey = array();
|
||||
protected $currentKey = [];
|
||||
|
||||
/**
|
||||
* Constructs an SQL object.
|
||||
@@ -174,7 +174,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
/**
|
||||
* Retrieves the hash of the source identifier values.
|
||||
*
|
||||
* It is public only for testing purposes.
|
||||
* @internal
|
||||
*
|
||||
* @param array $source_id_values
|
||||
* The source identifiers
|
||||
@@ -208,7 +208,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
protected function sourceIdFields() {
|
||||
if (!isset($this->sourceIdFields)) {
|
||||
// Build the source and destination identifier maps.
|
||||
$this->sourceIdFields = array();
|
||||
$this->sourceIdFields = [];
|
||||
$count = 1;
|
||||
foreach ($this->migration->getSourcePlugin()->getIds() as $field => $schema) {
|
||||
$this->sourceIdFields[$field] = 'sourceid' . $count++;
|
||||
@@ -225,7 +225,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
protected function destinationIdFields() {
|
||||
if (!isset($this->destinationIdFields)) {
|
||||
$this->destinationIdFields = array();
|
||||
$this->destinationIdFields = [];
|
||||
$count = 1;
|
||||
foreach ($this->migration->getDestinationPlugin()->getIds() as $field => $schema) {
|
||||
$this->destinationIdFields[$field] = 'destid' . $count++;
|
||||
@@ -312,19 +312,21 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
// 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();
|
||||
$source_id_schema = [];
|
||||
$indexes = [];
|
||||
foreach ($this->migration->getSourcePlugin()->getIds() as $id_definition) {
|
||||
$mapkey = 'sourceid' . $count++;
|
||||
$indexes['source'][] = $mapkey;
|
||||
$source_id_schema[$mapkey] = $this->getFieldSchema($id_definition);
|
||||
$source_id_schema[$mapkey]['not null'] = TRUE;
|
||||
}
|
||||
|
||||
$source_ids_hash[static::SOURCE_IDS_HASH] = array(
|
||||
$source_ids_hash[static::SOURCE_IDS_HASH] = [
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Hash of source ids. Used as primary key',
|
||||
);
|
||||
];
|
||||
$fields = $source_ids_hash + $source_id_schema;
|
||||
|
||||
// Add destination identifiers to map table.
|
||||
@@ -336,68 +338,69 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
$fields[$mapkey] = $this->getFieldSchema($id_definition);
|
||||
$fields[$mapkey]['not null'] = FALSE;
|
||||
}
|
||||
$fields['source_row_status'] = array(
|
||||
$fields['source_row_status'] = [
|
||||
'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(
|
||||
];
|
||||
$fields['rollback_action'] = [
|
||||
'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(
|
||||
];
|
||||
$fields['last_imported'] = [
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 0,
|
||||
'description' => 'UNIX timestamp of the last time this row was imported',
|
||||
);
|
||||
$fields['hash'] = array(
|
||||
];
|
||||
$fields['hash'] = [
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => FALSE,
|
||||
'description' => 'Hash of source row data, for detecting changes',
|
||||
);
|
||||
$schema = array(
|
||||
];
|
||||
$schema = [
|
||||
'description' => 'Mappings from source identifier value(s) to destination identifier value(s).',
|
||||
'fields' => $fields,
|
||||
'primary key' => array(static::SOURCE_IDS_HASH),
|
||||
);
|
||||
'primary key' => [static::SOURCE_IDS_HASH],
|
||||
'indexes' => $indexes,
|
||||
];
|
||||
$this->getDatabase()->schema()->createTable($this->mapTableName, $schema);
|
||||
|
||||
// Now do the message table.
|
||||
if (!$this->getDatabase()->schema()->tableExists($this->messageTableName())) {
|
||||
$fields = array();
|
||||
$fields['msgid'] = array(
|
||||
$fields = [];
|
||||
$fields['msgid'] = [
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
);
|
||||
];
|
||||
$fields += $source_ids_hash;
|
||||
|
||||
$fields['level'] = array(
|
||||
$fields['level'] = [
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
'default' => 1,
|
||||
);
|
||||
$fields['message'] = array(
|
||||
];
|
||||
$fields['message'] = [
|
||||
'type' => 'text',
|
||||
'size' => 'medium',
|
||||
'not null' => TRUE,
|
||||
);
|
||||
$schema = array(
|
||||
];
|
||||
$schema = [
|
||||
'description' => 'Messages generated during a migration process',
|
||||
'fields' => $fields,
|
||||
'primary key' => array('msgid'),
|
||||
);
|
||||
'primary key' => ['msgid'],
|
||||
];
|
||||
$this->getDatabase()->schema()->createTable($this->messageTableName(), $schema);
|
||||
}
|
||||
}
|
||||
@@ -406,33 +409,33 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
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',
|
||||
)
|
||||
]
|
||||
);
|
||||
}
|
||||
if (!$this->getDatabase()->schema()->fieldExists($this->mapTableName, static::SOURCE_IDS_HASH)) {
|
||||
$this->getDatabase()->schema()->addField($this->mapTableName, static::SOURCE_IDS_HASH, array(
|
||||
$this->getDatabase()->schema()->addField($this->mapTableName, static::SOURCE_IDS_HASH, [
|
||||
'type' => 'varchar',
|
||||
'length' => '64',
|
||||
'not null' => TRUE,
|
||||
'description' => 'Hash of source ids. Used as primary key',
|
||||
));
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -441,20 +444,40 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
* Creates schema from an ID definition.
|
||||
*
|
||||
* @param array $id_definition
|
||||
* A field schema definition. Can be SQL schema or a type data
|
||||
* based schema. In the latter case, the value of type needs to be
|
||||
* $typed_data_type.$column.
|
||||
* The definition of the field having the structure as the items returned by
|
||||
* MigrateSourceInterface or MigrateDestinationInterface::getIds().
|
||||
*
|
||||
* @return array
|
||||
* The schema definition.
|
||||
* The database schema definition.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface::getIds()
|
||||
* @see \Drupal\migrate\Plugin\MigrateDestinationInterface::getIds()
|
||||
*/
|
||||
protected function getFieldSchema(array $id_definition) {
|
||||
$type_parts = explode('.', $id_definition['type']);
|
||||
if (count($type_parts) == 1) {
|
||||
$type_parts[] = 'value';
|
||||
}
|
||||
$schema = BaseFieldDefinition::create($type_parts[0])->getColumns();
|
||||
return $schema[$type_parts[1]];
|
||||
unset($id_definition['type']);
|
||||
|
||||
// Get the field storage definition.
|
||||
$definition = BaseFieldDefinition::create($type_parts[0]);
|
||||
|
||||
// Get a list of setting keys belonging strictly to the field definition.
|
||||
$default_field_settings = $definition->getSettings();
|
||||
// Separate field definition settings from custom settings. Custom settings
|
||||
// are settings passed in $id_definition that are not part of field storage
|
||||
// definition settings.
|
||||
$field_settings = array_intersect_key($id_definition, $default_field_settings);
|
||||
$custom_settings = array_diff_key($id_definition, $default_field_settings);
|
||||
|
||||
// Resolve schema from field storage definition settings.
|
||||
$schema = $definition
|
||||
->setSettings($field_settings)
|
||||
->getColumns()[$type_parts[1]];
|
||||
|
||||
// Merge back custom settings.
|
||||
return $schema + $custom_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,7 +485,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function getRowBySource(array $source_id_values) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map');
|
||||
->fields('map');
|
||||
$query->condition(static::SOURCE_IDS_HASH, $this->getSourceIDsHash($source_id_values));
|
||||
$result = $query->execute();
|
||||
return $result->fetchAssoc();
|
||||
@@ -473,7 +496,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function getRowByDestination(array $destination_id_values) {
|
||||
$query = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map');
|
||||
->fields('map');
|
||||
foreach ($this->destinationIdFields() as $field_name => $destination_id) {
|
||||
$query->condition("map.$destination_id", $destination_id_values[$field_name], '=');
|
||||
}
|
||||
@@ -485,12 +508,12 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getRowsNeedingUpdate($count) {
|
||||
$rows = array();
|
||||
$rows = [];
|
||||
$result = $this->getDatabase()->select($this->mapTableName(), 'map')
|
||||
->fields('map')
|
||||
->condition('source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE)
|
||||
->range(0, $count)
|
||||
->execute();
|
||||
->fields('map')
|
||||
->condition('source_row_status', MigrateIdMapInterface::STATUS_NEEDS_UPDATE)
|
||||
->range(0, $count)
|
||||
->execute();
|
||||
foreach ($result as $row) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
@@ -518,7 +541,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function lookupDestinationId(array $source_id_values) {
|
||||
$results = $this->lookupDestinationIds($source_id_values);
|
||||
return $results ? reset($results) : array();
|
||||
return $results ? reset($results) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,7 +549,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function lookupDestinationIds(array $source_id_values) {
|
||||
if (empty($source_id_values)) {
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
// Canonicalize the keys into a hash of DB-field => value.
|
||||
@@ -582,7 +605,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
if (!isset($source_id_values[$field_name])) {
|
||||
$this->message->display($this->t(
|
||||
'Did not save to map table due to NULL value for key field @field',
|
||||
array('@field' => $field_name)), 'error');
|
||||
['@field' => $field_name]), 'error');
|
||||
return;
|
||||
}
|
||||
$fields[$key_name] = $source_id_values[$field_name];
|
||||
@@ -592,11 +615,11 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
return;
|
||||
}
|
||||
|
||||
$fields += array(
|
||||
$fields += [
|
||||
'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;
|
||||
@@ -660,7 +683,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function prepareUpdate() {
|
||||
$this->getDatabase()->update($this->mapTableName())
|
||||
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE))
|
||||
->fields(['source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE])
|
||||
->execute();
|
||||
}
|
||||
|
||||
@@ -679,7 +702,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function importedCount() {
|
||||
return $this->getDatabase()->select($this->mapTableName())
|
||||
->condition('source_row_status', array(MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE), 'IN')
|
||||
->condition('source_row_status', [MigrateIdMapInterface::STATUS_IMPORTED, MigrateIdMapInterface::STATUS_NEEDS_UPDATE], 'IN')
|
||||
->countQuery()
|
||||
->execute()
|
||||
->fetchField();
|
||||
@@ -774,7 +797,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
$query = $this->getDatabase()
|
||||
->update($this->mapTableName())
|
||||
->fields(array('source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE));
|
||||
->fields(['source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE]);
|
||||
|
||||
foreach ($this->sourceIdFields() as $field_name => $source_id) {
|
||||
$query->condition($source_id, $source_id_values[$field_name]);
|
||||
@@ -798,13 +821,13 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::rewind().
|
||||
* Implementation of \Iterator::rewind().
|
||||
*
|
||||
* This is called before beginning a foreach loop.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->currentRow = NULL;
|
||||
$fields = array();
|
||||
$fields = [];
|
||||
foreach ($this->sourceIdFields() as $field) {
|
||||
$fields[] = $field;
|
||||
}
|
||||
@@ -819,7 +842,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::current().
|
||||
* Implementation of \Iterator::current().
|
||||
*
|
||||
* This is called when entering a loop iteration, returning the current row.
|
||||
*/
|
||||
@@ -828,7 +851,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::key().
|
||||
* 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
|
||||
@@ -843,9 +866,11 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
*/
|
||||
public function currentDestination() {
|
||||
if ($this->valid()) {
|
||||
$result = array();
|
||||
$result = [];
|
||||
foreach ($this->destinationIdFields() as $destination_field_name => $idmap_field_name) {
|
||||
$result[$destination_field_name] = $this->currentRow[$idmap_field_name];
|
||||
if (!is_null($this->currentRow[$idmap_field_name])) {
|
||||
$result[$destination_field_name] = $this->currentRow[$idmap_field_name];
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
@@ -855,14 +880,30 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::next().
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function currentSource() {
|
||||
if ($this->valid()) {
|
||||
$result = [];
|
||||
foreach ($this->sourceIdFields() as $field_name => $source_id) {
|
||||
$result[$field_name] = $this->currentKey[$source_id];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
$this->currentKey = [];
|
||||
if ($this->currentRow) {
|
||||
foreach ($this->sourceIdFields() as $map_field) {
|
||||
$this->currentKey[$map_field] = $this->currentRow[$map_field];
|
||||
@@ -873,7 +914,7 @@ class Sql extends PluginBase implements MigrateIdMapInterface, ContainerFactoryP
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of Iterator::valid().
|
||||
* Implementation of \Iterator::valid().
|
||||
*
|
||||
* This is called at the top of the loop, returning TRUE to process the loop
|
||||
* and FALSE to terminate it.
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Builds an array based on the key and value configuration.
|
||||
*
|
||||
* The array_build plugin builds a single associative array by extracting keys
|
||||
* and values from each array in the input value, which is expected to be an
|
||||
* array of arrays. The keys of the returned array will be determined by the
|
||||
* 'key' configuration option, and the values will be determined by the 'value'
|
||||
* option.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - key: The key used to lookup a value in the source arrays to be used as
|
||||
* a key in the destination array.
|
||||
* - value: The key used to lookup a value in the source arrays to be used as
|
||||
* a value in the destination array.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* Consider the migration of language negotiation by domain.
|
||||
* The source is an array of all the languages:
|
||||
*
|
||||
* @code
|
||||
* languages: Array
|
||||
* (
|
||||
* [0] => Array
|
||||
* (
|
||||
* [language] => en
|
||||
* ...
|
||||
* [domain] => http://example.com
|
||||
* )
|
||||
* [1] => Array
|
||||
* (
|
||||
* [language] => fr
|
||||
* ...
|
||||
* [domain] => http://fr.example.com
|
||||
* )
|
||||
* ...
|
||||
* @endcode
|
||||
*
|
||||
* The destination should be an array of all the domains keyed by their
|
||||
* language code:
|
||||
*
|
||||
* @code
|
||||
* domains: Array
|
||||
* (
|
||||
* [en] => http://example.com
|
||||
* [fr] => http://fr.example.com
|
||||
* ...
|
||||
* @endcode
|
||||
*
|
||||
* The array_build process plugin would be used like this:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* domains:
|
||||
* plugin: array_build
|
||||
* key: language
|
||||
* value: domain
|
||||
* source: languages
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "array_build",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class ArrayBuild extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$new_value = [];
|
||||
|
||||
foreach ((array) $value as $old_key => $old_value) {
|
||||
// Checks that $old_value is an array.
|
||||
if (!is_array($old_value)) {
|
||||
throw new MigrateException("The input should be an array of arrays");
|
||||
}
|
||||
|
||||
// Checks that the key exists.
|
||||
if (!array_key_exists($this->configuration['key'], $old_value)) {
|
||||
throw new MigrateException("The key '" . $this->configuration['key'] . "' does not exist");
|
||||
}
|
||||
|
||||
// Checks that the value exists.
|
||||
if (!array_key_exists($this->configuration['value'], $old_value)) {
|
||||
throw new MigrateException("The key '" . $this->configuration['value'] . "' does not exist");
|
||||
}
|
||||
|
||||
$new_value[$old_value[$this->configuration['key']]] = $old_value[$this->configuration['value']];
|
||||
}
|
||||
|
||||
return $new_value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,13 +7,38 @@ use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin allows source value to be passed to a callback.
|
||||
* Passes the source value 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.
|
||||
* The callback process plugin allows simple processing of the value, such as
|
||||
* strtolower(). The callable takes the source value as the single mandatory
|
||||
* argument. No additional arguments can be passed to the callback.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - callable: The name of the callable method.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* destination_field:
|
||||
* plugin: callback
|
||||
* callable: strtolower
|
||||
* source: source_field
|
||||
* @endcode
|
||||
*
|
||||
* An example where the callable is a static method in a class:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* destination_field:
|
||||
* plugin: callback
|
||||
* callable:
|
||||
* - '\Drupal\Component\Utility\Unicode'
|
||||
* - strtolower
|
||||
* source: source_field
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "callback"
|
||||
|
||||
@@ -8,7 +8,48 @@ use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Concatenates the strings in the current value.
|
||||
* Concatenates a set of strings.
|
||||
*
|
||||
* The concat plugin is used to concatenate strings. For example, imploding a
|
||||
* set of strings into a single string.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - delimiter: (optional) A delimiter, or glue string, to insert between the
|
||||
* strings.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - foo
|
||||
* - bar
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_text_field to the concatenation of the 'foo' and 'bar'
|
||||
* source values. For example, if the 'foo' property is "wambooli" and the 'bar'
|
||||
* property is "pastafazoul", new_text_field will be "wamboolipastafazoul".
|
||||
*
|
||||
* You can also specify a delimiter.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: concat
|
||||
* source:
|
||||
* - foo
|
||||
* - bar
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_text_field to the concatenation of the 'foo' source value,
|
||||
* the delimiter and the 'bar' source value. For example, using the values above
|
||||
* and "/" as the delimiter, if the 'foo' property is "wambooli" and the 'bar'
|
||||
* property is "pastafazoul", new_text_field will be "wambooli/pastafazoul".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "concat",
|
||||
@@ -19,8 +60,6 @@ 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)) {
|
||||
|
||||
@@ -2,11 +2,8 @@
|
||||
|
||||
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;
|
||||
@trigger_error('The ' . __NAMESPACE__ . ' \DedupeEntityBase is deprecated in
|
||||
Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . ' \MakeUniqueEntityFieldBase', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* This abstract base contains the dedupe logic.
|
||||
@@ -15,41 +12,11 @@ use Drupal\Component\Utility\Unicode;
|
||||
* creating filter format names, the current value is checked against the
|
||||
* existing filter format names and if it exists, a numeric postfix is added
|
||||
* and incremented until a unique value is created.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2345929 Online handbook documentation for dedupebase process plugin @endlink
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase instead.
|
||||
*/
|
||||
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);
|
||||
|
||||
abstract class DedupeBase extends MakeUniqueBase {
|
||||
}
|
||||
|
||||
@@ -2,59 +2,22 @@
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@trigger_error('The ' . __NAMESPACE__ . ' \DedupeEntity is deprecated in
|
||||
Drupal 8.4.x and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . ' \MakeUniqueEntityField', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Ensures value is not duplicated against an entity field.
|
||||
*
|
||||
* If the 'migrated' configuration value is true, an entity will only be
|
||||
* considered a duplicate if it was migrated by the current migration.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2135325 Online handbook documentation for dedupe_entity process plugin @endlink
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "dedupe_entity"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.4.x and will be removed in Drupal 9.0.x. Use
|
||||
* \Drupal\migrate\Plugin\migrate\process\MakeUniqueEntityField instead.
|
||||
*/
|
||||
class DedupeEntity extends DedupeBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity query factory.
|
||||
*
|
||||
* @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();
|
||||
}
|
||||
|
||||
}
|
||||
class DedupeEntity extends MakeUniqueEntityField { }
|
||||
|
||||
@@ -7,7 +7,38 @@ use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin sets missing values on the destination.
|
||||
* Returns a given default value if the input is empty.
|
||||
*
|
||||
* The default_value process plugin provides the ability to set a fixed default
|
||||
* value. The plugin returns a default value if the input value is considered
|
||||
* empty (NULL, FALSE, 0, '0', an empty string, or an empty array). The strict
|
||||
* configuration key can be used to set the default only when the incoming
|
||||
* value is NULL.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - default_value: The fixed default value to apply.
|
||||
* - strict: (optional) Use strict value checking. Defaults to false.
|
||||
* - FALSE: Apply default when input value is empty().
|
||||
* - TRUE: Apply default when input value is NULL.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* -
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* source: author
|
||||
* -
|
||||
* plugin: default_value
|
||||
* default_value: 44
|
||||
* @endcode
|
||||
*
|
||||
* This will look up the source value of author in the users migration and if
|
||||
* not found, set the destination property uid to 44.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "default_value"
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use GuzzleHttp\Client;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Downloads a file from a HTTP(S) remote location into the local file system.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - source URL, e.g. 'http://www.example.com/img/foo.img'
|
||||
* - destination URI, e.g. 'public://images/foo.img'
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - rename: (optional) If set, a unique destination URI is generated. If not
|
||||
* set, the destination URI will be overwritten if it exists.
|
||||
* - guzzle_options: (optional)
|
||||
* @link http://docs.guzzlephp.org/en/latest/request-options.html Array of request options for Guzzle. @endlink
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* plugin: download
|
||||
* source:
|
||||
* - source_url
|
||||
* - destination_uri
|
||||
* @endcode
|
||||
*
|
||||
* This will download source_url to destination_uri.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* plugin: download
|
||||
* source:
|
||||
* - source_url
|
||||
* - destination_uri
|
||||
* rename: true
|
||||
* @endcode
|
||||
*
|
||||
* This will download source_url to destination_uri and ensure that the
|
||||
* destination URI is unique. If a file with the same name exists at the
|
||||
* destination, a numbered suffix like '_0' will be appended to make it unique.
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "download"
|
||||
* )
|
||||
*/
|
||||
class Download extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* The Guzzle HTTP Client service.
|
||||
*
|
||||
* @var \GuzzleHttp\Client
|
||||
*/
|
||||
protected $httpClient;
|
||||
|
||||
/**
|
||||
* Constructs a download process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file system service.
|
||||
* @param \GuzzleHttp\Client $http_client
|
||||
* The HTTP client.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, FileSystemInterface $file_system, Client $http_client) {
|
||||
$configuration += [
|
||||
'rename' => FALSE,
|
||||
'guzzle_options' => [],
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->fileSystem = $file_system;
|
||||
$this->httpClient = $http_client;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('file_system'),
|
||||
$container->get('http_client')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// If we're stubbing a file entity, return a uri of NULL so it will get
|
||||
// stubbed by the general process.
|
||||
if ($row->isStub()) {
|
||||
return NULL;
|
||||
}
|
||||
list($source, $destination) = $value;
|
||||
|
||||
// Modify the destination filename if necessary.
|
||||
$replace = !empty($this->configuration['rename']) ?
|
||||
FILE_EXISTS_RENAME :
|
||||
FILE_EXISTS_REPLACE;
|
||||
$final_destination = file_destination($destination, $replace);
|
||||
|
||||
// Try opening the file first, to avoid calling file_prepare_directory()
|
||||
// unnecessarily. We're suppressing fopen() errors because we want to try
|
||||
// to prepare the directory before we give up and fail.
|
||||
$destination_stream = @fopen($final_destination, 'w');
|
||||
if (!$destination_stream) {
|
||||
// If fopen didn't work, make sure there's a writable directory in place.
|
||||
$dir = $this->fileSystem->dirname($final_destination);
|
||||
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
throw new MigrateException("Could not create or write to directory '$dir'");
|
||||
}
|
||||
// Let's try that fopen again.
|
||||
$destination_stream = @fopen($final_destination, 'w');
|
||||
if (!$destination_stream) {
|
||||
throw new MigrateException("Could not write to file '$final_destination'");
|
||||
}
|
||||
}
|
||||
|
||||
// Stream the request body directly to the final destination stream.
|
||||
$this->configuration['guzzle_options']['sink'] = $destination_stream;
|
||||
|
||||
try {
|
||||
// Make the request. Guzzle throws an exception for anything but 200.
|
||||
$this->httpClient->get($source, $this->configuration['guzzle_options']);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
throw new MigrateException("{$e->getMessage()} ($source)");
|
||||
}
|
||||
|
||||
return $final_destination;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This plugin checks if a given entity exists.
|
||||
*
|
||||
* Example usage with configuration:
|
||||
* @code
|
||||
* field_tags:
|
||||
* plugin: entity_exists
|
||||
* source: tid
|
||||
* entity_type: taxonomy_term
|
||||
* @endcode
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "entity_exists"
|
||||
* )
|
||||
*/
|
||||
class EntityExists extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity storage.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityStorageInterface
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* EntityExists constructor.
|
||||
*
|
||||
* @param array $configuration
|
||||
* A configuration array containing information about the plugin instance.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin implementation definition.
|
||||
* @param $storage
|
||||
* The entity storage.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityStorageInterface $storage) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->storage = $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,
|
||||
$container->get('entity_type.manager')->getStorage($configuration['entity_type'])
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_array($value)) {
|
||||
$value = reset($value);
|
||||
}
|
||||
|
||||
$entity = $this->storage->load($value);
|
||||
if ($entity instanceof EntityInterface) {
|
||||
return $entity->id();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,84 @@ use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin explodes a delimited string into an array of values.
|
||||
* Splits the source string into an array of strings, using a delimiter.
|
||||
*
|
||||
* This plugin creates an array of strings by splitting the source parameter on
|
||||
* boundaries formed by the delimiter.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The source string.
|
||||
* - limit: (optional)
|
||||
* - If limit is set and positive, the returned array will contain a maximum
|
||||
* of limit elements with the last element containing the rest of string.
|
||||
* - If limit is set and negative, all components except the last -limit are
|
||||
* returned.
|
||||
* - If the limit parameter is zero, then this is treated as 1.
|
||||
* - delimiter: The boundary string.
|
||||
* - strict: (optional) When this boolean is TRUE, the source should be strictly
|
||||
* a string. If FALSE is passed, the source value is casted to a string before
|
||||
* being split. Also, in this case, the values casting to empty strings are
|
||||
* converted to empty arrays, instead of an array with a single empty string
|
||||
* item ['']. Defaults to TRUE.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* If foo is "node/1", then bar will be ['node', '1']. The PHP equivalent of
|
||||
* this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', $foo);
|
||||
* @endcode
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* limit: 1
|
||||
* delimiter: /
|
||||
* @endcode
|
||||
*
|
||||
* If foo is "node/1/edit", then bar will be ['node', '1/edit']. The PHP
|
||||
* equivalent of this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', $foo, 1);
|
||||
* @endcode
|
||||
*
|
||||
* If the 'strict' configuration is set to FALSE, the input value is casted to a
|
||||
* string before being spilt:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: explode
|
||||
* source: foo
|
||||
* delimiter: /
|
||||
* strict: false
|
||||
* @endcode
|
||||
*
|
||||
* If foo is 123 (as integer), then bar will be ['123']. If foo is TRUE, then
|
||||
* bar will be ['1']. The PHP equivalent of this would be:
|
||||
*
|
||||
* @code
|
||||
* $bar = explode('/', (string) 123);
|
||||
* $bar = explode('/', (string) TRUE);
|
||||
* @endcode
|
||||
*
|
||||
* If the 'strict' configuration is set to FALSE, the source value casting to
|
||||
* an empty string are converted to an empty array. For example, with the last
|
||||
* configuration, if foo is '', NULL or FALSE, then bar will be [].
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "explode"
|
||||
@@ -20,18 +97,29 @@ class Explode extends ProcessPluginBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (is_string($value)) {
|
||||
if (!empty($this->configuration['delimiter'])) {
|
||||
$limit = isset($this->configuration['limit']) ? $this->configuration['limit'] : PHP_INT_MAX;
|
||||
return explode($this->configuration['delimiter'], $value, $limit);
|
||||
}
|
||||
else {
|
||||
throw new MigrateException('delimiter is empty');
|
||||
}
|
||||
if (empty($this->configuration['delimiter'])) {
|
||||
throw new MigrateException('delimiter is empty');
|
||||
}
|
||||
else {
|
||||
|
||||
$strict = array_key_exists('strict', $this->configuration) ? $this->configuration['strict'] : TRUE;
|
||||
if ($strict && !is_string($value)) {
|
||||
throw new MigrateException(sprintf('%s is not a string', var_export($value, TRUE)));
|
||||
}
|
||||
elseif (!$strict) {
|
||||
// Check if the incoming value can cast to a string.
|
||||
$original = $value;
|
||||
if (!is_string($original) && ($original != ($value = @strval($value)))) {
|
||||
throw new MigrateException(sprintf('%s cannot be casted to a string', var_export($original, TRUE)));
|
||||
}
|
||||
// Empty strings should be exploded to empty arrays.
|
||||
if ($value === '') {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
$limit = isset($this->configuration['limit']) ? $this->configuration['limit'] : PHP_INT_MAX;
|
||||
|
||||
return explode($this->configuration['delimiter'], $value, $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,12 +9,56 @@ use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin extracts a value from an array.
|
||||
* Extracts a value from an array.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2152731
|
||||
* The extract process plugin is used to pull data from an input array, which
|
||||
* may have multiple levels. One use case is extracting data from field arrays
|
||||
* in previous versions of Drupal. For instance, in Drupal 7, a field array
|
||||
* would be indexed first by language, then by delta, then finally a key such as
|
||||
* 'value'.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The input value - must be an array.
|
||||
* - index: The array of keys to access the value.
|
||||
* - default: (optional) A default value to assign to the destination if the
|
||||
* key does not exist.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: extract
|
||||
* source: some_text_field
|
||||
* index:
|
||||
* - und
|
||||
* - 0
|
||||
* - value
|
||||
* @endcode
|
||||
*
|
||||
* The PHP equivalent of this would be:
|
||||
* @code
|
||||
* $destination['new_text_field'] = $source['some_text_field']['und'][0]['value'];
|
||||
* @endcode
|
||||
* If a default value is specified, it will be returned if the index does not
|
||||
* exist in the input array.
|
||||
*
|
||||
* @code
|
||||
* plugin: extract
|
||||
* source: some_text_field
|
||||
* default: 'Default title'
|
||||
* index:
|
||||
* - title
|
||||
* @endcode
|
||||
*
|
||||
* If $source['some_text_field']['title'] doesn't exist, then the plugin will
|
||||
* return "Default title".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "extract"
|
||||
* id = "extract",
|
||||
* handle_multiples = TRUE
|
||||
* )
|
||||
*/
|
||||
class Extract extends ProcessPluginBase {
|
||||
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\File\FileSystemInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\StreamWrapper\LocalStream;
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrateProcessInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Copies or moves a local file from one place into another.
|
||||
*
|
||||
* The file can be moved, reused, or set to be automatically renamed if a
|
||||
* duplicate exists.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - source: The source path or URI, e.g. '/path/to/foo.txt' or
|
||||
* 'public://bar.txt'.
|
||||
* - destination: The destination path or URI, e.g. '/path/to/bar.txt' or
|
||||
* 'public://foo.txt'.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - move: (optional) Boolean, if TRUE, move the file, otherwise copy the file.
|
||||
* Defaults to FALSE.
|
||||
* - rename: (optional) Boolean, if TRUE, rename the file by appending a number
|
||||
* until the name is unique. Defaults to FALSE.
|
||||
* - reuse: (optional) Boolean, if TRUE, reuse the current file in its existing
|
||||
* location rather than move/copy/rename the file. Defaults to FALSE.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* path_to_file:
|
||||
* plugin: file_copy
|
||||
* source: /path/to/file.png
|
||||
* destination: /new/path/to/file.png
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "file_copy"
|
||||
* )
|
||||
*/
|
||||
class FileCopy extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The stream wrapper manager service.
|
||||
*
|
||||
* @var \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
|
||||
*/
|
||||
protected $streamWrapperManager;
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* An instance of the download process plugin.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
protected $downloadPlugin;
|
||||
|
||||
/**
|
||||
* Constructs a file_copy process plugin.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The plugin configuration.
|
||||
* @param string $plugin_id
|
||||
* The plugin ID.
|
||||
* @param mixed $plugin_definition
|
||||
* The plugin definition.
|
||||
* @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $stream_wrappers
|
||||
* The stream wrapper manager service.
|
||||
* @param \Drupal\Core\File\FileSystemInterface $file_system
|
||||
* The file system service.
|
||||
* @param \Drupal\migrate\Plugin\MigrateProcessInterface $download_plugin
|
||||
* An instance of the download plugin for handling remote URIs.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system, MigrateProcessInterface $download_plugin) {
|
||||
$configuration += [
|
||||
'move' => FALSE,
|
||||
'rename' => FALSE,
|
||||
'reuse' => FALSE,
|
||||
];
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
$this->fileSystem = $file_system;
|
||||
$this->downloadPlugin = $download_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
|
||||
return new static(
|
||||
$configuration,
|
||||
$plugin_id,
|
||||
$plugin_definition,
|
||||
$container->get('stream_wrapper_manager'),
|
||||
$container->get('file_system'),
|
||||
$container->get('plugin.manager.migrate.process')->createInstance('download')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// If we're stubbing a file entity, return a URI of NULL so it will get
|
||||
// stubbed by the general process.
|
||||
if ($row->isStub()) {
|
||||
return NULL;
|
||||
}
|
||||
list($source, $destination) = $value;
|
||||
|
||||
// If the source path or URI represents a remote resource, delegate to the
|
||||
// download plugin.
|
||||
if (!$this->isLocalUri($source)) {
|
||||
return $this->downloadPlugin->transform($value, $migrate_executable, $row, $destination_property);
|
||||
}
|
||||
|
||||
// Ensure the source file exists, if it's a local URI or path.
|
||||
if (!file_exists($source)) {
|
||||
throw new MigrateException("File '$source' does not exist");
|
||||
}
|
||||
|
||||
// If the start and end file is exactly the same, there is nothing to do.
|
||||
if ($this->isLocationUnchanged($source, $destination)) {
|
||||
return $destination;
|
||||
}
|
||||
|
||||
// Check if a writable directory exists, and if not try to create it.
|
||||
$dir = $this->getDirectory($destination);
|
||||
// If the directory exists and is writable, avoid file_prepare_directory()
|
||||
// call and write the file to destination.
|
||||
if (!is_dir($dir) || !is_writable($dir)) {
|
||||
if (!file_prepare_directory($dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
|
||||
throw new MigrateException("Could not create or write to directory '$dir'");
|
||||
}
|
||||
}
|
||||
|
||||
$final_destination = $this->writeFile($source, $destination, $this->getOverwriteMode());
|
||||
if ($final_destination) {
|
||||
return $final_destination;
|
||||
}
|
||||
throw new MigrateException("File $source could not be copied to $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 int $replace
|
||||
* (optional) FILE_EXISTS_REPLACE (default) or FILE_EXISTS_RENAME.
|
||||
*
|
||||
* @return string|bool
|
||||
* File destination on success, FALSE on failure.
|
||||
*/
|
||||
protected function writeFile($source, $destination, $replace = FILE_EXISTS_REPLACE) {
|
||||
// Check if there is a destination available for copying. If there isn't,
|
||||
// it already exists at the destination and the replace flag tells us to not
|
||||
// replace it. In that case, return the original destination.
|
||||
if (!($final_destination = file_destination($destination, $replace))) {
|
||||
return $destination;
|
||||
}
|
||||
$function = 'file_unmanaged_' . ($this->configuration['move'] ? 'move' : 'copy');
|
||||
return $function($source, $destination, $replace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines how to handle file conflicts.
|
||||
*
|
||||
* @return int
|
||||
* FILE_EXISTS_REPLACE (default), FILE_EXISTS_RENAME, or FILE_EXISTS_ERROR
|
||||
* depending on the current configuration.
|
||||
*/
|
||||
protected function getOverwriteMode() {
|
||||
if (!empty($this->configuration['rename'])) {
|
||||
return FILE_EXISTS_RENAME;
|
||||
}
|
||||
if (!empty($this->configuration['reuse'])) {
|
||||
return FILE_EXISTS_ERROR;
|
||||
}
|
||||
|
||||
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 string|false
|
||||
* 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);
|
||||
}
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the source and destination URIs represent identical paths.
|
||||
*
|
||||
* @param string $source
|
||||
* The source URI.
|
||||
* @param string $destination
|
||||
* The destination URI.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if the source and destination URIs refer to the same physical path,
|
||||
* otherwise FALSE.
|
||||
*/
|
||||
protected function isLocationUnchanged($source, $destination) {
|
||||
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines 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 bool
|
||||
*/
|
||||
protected function isLocalUri($uri) {
|
||||
$scheme = $this->fileSystem->uriScheme($uri);
|
||||
|
||||
// The vfs scheme is vfsStream, which is used in testing. vfsStream is a
|
||||
// simulated file system that exists only in memory, but should be treated
|
||||
// as a local resource.
|
||||
if ($scheme == 'vfs') {
|
||||
$scheme = FALSE;
|
||||
}
|
||||
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,13 +6,34 @@ use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin flattens the current value.
|
||||
* Flattens the source 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.
|
||||
* The flatten process plugin converts a nested array into a flat array. For
|
||||
* example [[1, 2, [3, 4]], [5], 6] becomes [1, 2, 3, 4, 5, 6]. During some
|
||||
* types of processing (e.g. user permission splitting), what was once a
|
||||
* one-dimensional array gets transformed into a multidimensional array. This
|
||||
* plugin will flatten them back down to one-dimensional arrays again.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2154215
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* tags:
|
||||
* -
|
||||
* plugin: default_value
|
||||
* source: foo
|
||||
* default_value: [bar, [qux, quux]]
|
||||
* -
|
||||
* plugin: flatten
|
||||
* @endcode
|
||||
*
|
||||
* In this example, the default_value process returns [bar, [qux, quux]] (given
|
||||
* a NULL value of foo). At this point, Migrate would try to import two
|
||||
* items: bar and [qux, quux]. The latter is not a valid one and won't be
|
||||
* imported. We need to pass the values through the flatten processor to obtain
|
||||
* a three items array [bar, qux, quux], suitable for import.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "flatten",
|
||||
@@ -24,7 +45,7 @@ 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).
|
||||
* For example, [[1, 2, [3, 4]]] becomes [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);
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Component\Datetime\DateTimePlus;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Converts date/datetime from one format to another.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - from_format: The source format string as accepted by
|
||||
* @link http://php.net/manual/datetime.createfromformat.php \DateTime::createFromFormat. @endlink
|
||||
* - to_format: The destination format.
|
||||
* - timezone: String identifying the required time zone, see
|
||||
* DateTimePlus::__construct().
|
||||
* - settings: keyed array of settings, see DateTimePlus::__construct().
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Example usage for date only fields (DATETIME_DATE_STORAGE_FORMAT):
|
||||
* @code
|
||||
* process:
|
||||
* field_date:
|
||||
* plugin: format_date
|
||||
* from_format: 'm/d/Y'
|
||||
* to_format: 'Y-m-d'
|
||||
* source: event_date
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '01/05/1955' the transformed value would be
|
||||
* 1955-01-05.
|
||||
*
|
||||
* Example usage for datetime fields (DATETIME_DATETIME_STORAGE_FORMAT):
|
||||
* @code
|
||||
* process:
|
||||
* field_time:
|
||||
* plugin: format_date
|
||||
* from_format: 'm/d/Y H:i:s'
|
||||
* to_format: 'Y-m-d\TH:i:s'
|
||||
* source: event_time
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '01/05/1955 10:43:22' the transformed value would be
|
||||
* 1955-01-05T10:43:22.
|
||||
*
|
||||
* Example usage for datetime fields with a timezone and settings:
|
||||
* @code
|
||||
* process:
|
||||
* field_time:
|
||||
* plugin: format_date
|
||||
* from_format: 'Y-m-d\TH:i:sO'
|
||||
* to_format: 'Y-m-d\TH:i:s'
|
||||
* timezone: 'America/Managua'
|
||||
* settings:
|
||||
* validate_format: false
|
||||
* source: event_time
|
||||
* @endcode
|
||||
*
|
||||
* If the source value was '2004-12-19T10:19:42-0600' the transformed value
|
||||
* would be 2004-12-19T10:19:42.
|
||||
*
|
||||
* @see \DateTime::createFromFormat()
|
||||
* @see \Drupal\Component\Datetime\DateTimePlus::__construct()
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "format_date"
|
||||
* )
|
||||
*/
|
||||
class FormatDate extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (empty($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Validate the configuration.
|
||||
if (empty($this->configuration['from_format'])) {
|
||||
throw new MigrateException('Format date plugin is missing from_format configuration.');
|
||||
}
|
||||
if (empty($this->configuration['to_format'])) {
|
||||
throw new MigrateException('Format date plugin is missing to_format configuration.');
|
||||
}
|
||||
|
||||
$fromFormat = $this->configuration['from_format'];
|
||||
$toFormat = $this->configuration['to_format'];
|
||||
$timezone = isset($this->configuration['timezone']) ? $this->configuration['timezone'] : NULL;
|
||||
$settings = isset($this->configuration['settings']) ? $this->configuration['settings'] : [];
|
||||
|
||||
// Attempts to transform the supplied date using the defined input format.
|
||||
// DateTimePlus::createFromFormat can throw exceptions, so we need to
|
||||
// explicitly check for problems.
|
||||
try {
|
||||
$transformed = DateTimePlus::createFromFormat($fromFormat, $value, $timezone, $settings)->format($toFormat);
|
||||
}
|
||||
catch (\InvalidArgumentException $e) {
|
||||
throw new MigrateException(sprintf('Format date plugin could not transform "%s" using the format "%s". Error: %s', $value, $fromFormat, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
catch (\UnexpectedValueException $e) {
|
||||
throw new MigrateException(sprintf('Format date plugin could not transform "%s" using the format "%s". Error: %s', $value, $fromFormat, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
return $transformed;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,7 +7,87 @@ use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* This plugin copies from the source to the destination.
|
||||
* Gets the source value.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: Source property.
|
||||
*
|
||||
* The get plugin returns the value of the property given by the "source"
|
||||
* configuration key.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* This copies the source value of foo to the destination property "bar".
|
||||
*
|
||||
* Since get is the default process plugin, it can be shorthanded like this:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar: foo
|
||||
* @endcode
|
||||
*
|
||||
* get also supports a list of source properties.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source:
|
||||
* - foo1
|
||||
* - foo2
|
||||
* @endcode
|
||||
*
|
||||
* This copies the array of source values [foo1, foo2] to the destination
|
||||
* property "bar".
|
||||
*
|
||||
* If the list of source properties contains an empty element then the current
|
||||
* value will be used. This makes it impossible to reach a source property with
|
||||
* an empty string as its name.
|
||||
*
|
||||
* get also supports copying destination values. These are indicated by a
|
||||
* starting @ sign. Values using @ must be wrapped in quotes.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* foo:
|
||||
* plugin: machine_name
|
||||
* source: baz
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: '@foo'
|
||||
* @endcode
|
||||
*
|
||||
* This will simply copy the destination value of foo to the destination
|
||||
* property bar. foo configuration is included for illustration purposes.
|
||||
*
|
||||
* Because of this, if your source or destination property actually starts with
|
||||
* a @ you need to double those starting characters up. This means that if a
|
||||
* destination property happens to start with a @ and you want to refer it,
|
||||
* you'll need to start with three @ characters -- one to indicate the
|
||||
* destination and two for escaping the real @.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* @foo:
|
||||
* plugin: machine_name
|
||||
* source: baz
|
||||
* bar:
|
||||
* plugin: get
|
||||
* source: '@@@foo'
|
||||
* @endcode
|
||||
*
|
||||
* This should occur extremely rarely.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "get"
|
||||
@@ -27,8 +107,8 @@ class Get extends ProcessPluginBase {
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
$source = $this->configuration['source'];
|
||||
$properties = is_string($source) ? array($source) : $source;
|
||||
$return = array();
|
||||
$properties = is_string($source) ? [$source] : $source;
|
||||
$return = [];
|
||||
foreach ($properties as $property) {
|
||||
if ($property || (string) $property === '0') {
|
||||
$is_source = TRUE;
|
||||
|
||||
@@ -9,7 +9,7 @@ use Drupal\migrate\Row;
|
||||
/**
|
||||
* This plugin iterates and processes an array.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2135345
|
||||
* @link https://www.drupal.org/node/2135345 Online handbook documentation for iterator process plugin @endlink
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "iterator",
|
||||
@@ -22,15 +22,17 @@ 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 = [];
|
||||
if (!is_null($value)) {
|
||||
foreach ($value as $key => $new_value) {
|
||||
$new_row = new Row($new_value, []);
|
||||
$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[$key] = $destination;
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
@@ -49,7 +51,7 @@ class Iterator extends ProcessPluginBase {
|
||||
* The transformed key.
|
||||
*/
|
||||
protected function transformKey($key, MigrateExecutableInterface $migrate_executable, Row $row) {
|
||||
$process = array('key' => $this->configuration['key']);
|
||||
$process = ['key' => $this->configuration['key']];
|
||||
$migrate_executable->processRow($row, $process, $key);
|
||||
return $row->getDestinationProperty('key');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
|
||||
/**
|
||||
* Logs values without changing them.
|
||||
*
|
||||
* The log plugin will log the values that are being processed by other plugins.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: log
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "log"
|
||||
* )
|
||||
*/
|
||||
class Log extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// Log the value.
|
||||
$migrate_executable->saveMessage($value);
|
||||
|
||||
// Pass through the same value we received.
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,11 +11,28 @@ use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* This plugin creates a machine name.
|
||||
* 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.
|
||||
* The machine_name process plugin takes the source value and runs it through
|
||||
* the transliteration service. This makes the source value lowercase,
|
||||
* replaces anything that is not a number or a letter with an underscore,
|
||||
* and removes duplicate underscores.
|
||||
*
|
||||
* Letters will have language decorations and accents removed.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: machine_name
|
||||
* source: foo
|
||||
* @endcode
|
||||
*
|
||||
* If the value of foo in the source is 'áéí!' then the destination value of bar
|
||||
* will be 'aei_'.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "machine_name"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
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 plugin ensures the source value is unique.
|
||||
*
|
||||
* The MakeUniqueBase process plugin is used to avoid duplication at the
|
||||
* destination. For example, when creating filter format names, the source
|
||||
* 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.
|
||||
* An optional postfix string can be insert before the numeric postfix.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - start: (optional) The position at which to start reading.
|
||||
* - length: (optional) The number of characters to read.
|
||||
* - postfix: (optional) A string to insert before the numeric postfix.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*/
|
||||
abstract class MakeUniqueBase extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* Creates a unique value based on the source value.
|
||||
*
|
||||
* @param string $value
|
||||
* The input string.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return string
|
||||
* The unique version of the input value.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
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 the unique 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);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Ensures the source value is made unique against an entity field.
|
||||
*
|
||||
* The make_unique process plugin is typically used to make the entity id
|
||||
* unique, ensuring that migrated entity data is preserved.
|
||||
*
|
||||
* The make_unique process plugin has two required configuration keys,
|
||||
* entity_type and field. It's typically used with an entity destination, making
|
||||
* sure that after saving the entity, the field value is unique. For example,
|
||||
* if the value is foo and there is already an entity where the field value is
|
||||
* foo, then the plugin will return foo1.
|
||||
*
|
||||
* The optional configuration key postfix which will be added between the number
|
||||
* and the original value, for example, foo_1 for postfix: _. Note that the
|
||||
* value of postfix is ignored if the value is not changed, if it was already
|
||||
* unique.
|
||||
*
|
||||
* The optional configuration key migrated, if true, indicates that an entity
|
||||
* will only be considered a duplicate if it was migrated by the current
|
||||
* migration.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - entity_type: The entity type.
|
||||
* - field: The entity field for the given value.
|
||||
* - migrated: (optional) A boolean to indicate that making the field unique
|
||||
* only occurs for migrated entities.
|
||||
* - start: (optional) The position at which to start reading.
|
||||
* - length: (optional) The number of characters to read.
|
||||
* - postfix: (optional) A string to insert before the numeric postfix.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* format:
|
||||
* -
|
||||
* plugin: machine_name
|
||||
* source: name
|
||||
* -
|
||||
* plugin: make_unique_entity_field
|
||||
* entity_type: filter_format
|
||||
* field: format
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* This will create a format machine name out the human readable name and make
|
||||
* sure it's unique.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* format:
|
||||
* -
|
||||
* plugin: machine_name
|
||||
* source: name
|
||||
* -
|
||||
* plugin: make_unique_entity_field
|
||||
* entity_type: filter_format
|
||||
* field: format
|
||||
* postfix: _
|
||||
* migrated: true
|
||||
*
|
||||
* @endcode
|
||||
*
|
||||
* This will create a format machine name out the human readable name and make
|
||||
* sure it's unique if the entity was migrated. The postfix character is
|
||||
* inserted between the added number and the original value.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\process\MakeUniqueBase
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "make_unique_entity_field"
|
||||
* )
|
||||
*/
|
||||
class MakeUniqueEntityField extends MakeUniqueBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The entity type manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||
*/
|
||||
protected $entityTypeManager;
|
||||
|
||||
/**
|
||||
* The current migration.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, EntityTypeManagerInterface $entity_type_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migration = $migration;
|
||||
$this->entityTypeManager = $entity_type_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_type.manager')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function exists($value) {
|
||||
// Plugins are cached so for every run we need a new query object.
|
||||
$query = $this
|
||||
->entityTypeManager
|
||||
->getStorage($this->configuration['entity_type'])
|
||||
->getQuery()
|
||||
->condition($this->configuration['field'], $value);
|
||||
if (!empty($this->configuration['migrated'])) {
|
||||
// Check if each entity is in the ID map.
|
||||
$idMap = $this->migration->getIdMap();
|
||||
foreach ($query->execute() as $id) {
|
||||
$dest_id_values[$this->configuration['field']] = $id;
|
||||
if ($idMap->lookupSourceID($dest_id_values)) {
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
else {
|
||||
// Just check if any such entity exists.
|
||||
return $query->count()->execute();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,170 +2,19 @@
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\Plugin\MigratePluginManager;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
@trigger_error('The ' . __NAMESPACE__ . '\Migration is deprecated in
|
||||
Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use ' . __NAMESPACE__ . '\MigrationLookup', E_USER_DEPRECATED);
|
||||
|
||||
/**
|
||||
* Calculates the value of a property based on a previous migration.
|
||||
*
|
||||
* @link https://www.drupal.org/node/2149801 Online handbook documentation for migration process plugin @endlink
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "migration"
|
||||
* )
|
||||
*
|
||||
* @deprecated in Drupal 8.3.x and will be removed in Drupal 9.0.x.
|
||||
* Use \Drupal\migrate\Plugin\migrate\process\MigrationLookup instead.
|
||||
*/
|
||||
class Migration extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The process plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $processPluginManager;
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManager $process_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$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('plugin.manager.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\Plugin\MigrationInterface[] $migrations */
|
||||
$destination_ids = NULL;
|
||||
$source_id_values = array();
|
||||
$migrations = $this->migrationPluginManager->createInstances($migration_ids);
|
||||
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 && !empty($this->configuration['no_stub'])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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->getProcess();
|
||||
|
||||
// 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->getSourceConfiguration(), $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) {
|
||||
$migration->getIdMap()->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($destination_ids) {
|
||||
$migration->getIdMap()->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
}
|
||||
}
|
||||
if ($destination_ids) {
|
||||
if ($scalar) {
|
||||
if (count($destination_ids) == 1) {
|
||||
return reset($destination_ids);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return $destination_ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the migration process entirely if the value is FALSE.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The incoming value to transform.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
protected function skipOnEmpty(array $value) {
|
||||
if (!array_filter($value)) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
class Migration extends MigrationLookup { }
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateSkipProcessException;
|
||||
use Drupal\migrate\Plugin\MigratePluginManagerInterface;
|
||||
use Drupal\migrate\Plugin\MigrationPluginManagerInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Looks up the value of a property based on a previous migration.
|
||||
*
|
||||
* It is important to maintain relationships among content coming from the
|
||||
* source site. For example, on the source site, a given user account may
|
||||
* have an ID of 123, but the Drupal user account created from it may have
|
||||
* a uid of 456. The migration process maintains the relationships between
|
||||
* source and destination identifiers in map tables, and this information
|
||||
* is leveraged by the migration_lookup process plugin.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - migration: A single migration ID, or an array of migration IDs.
|
||||
* - source_ids: (optional) An array keyed by migration IDs with values that are
|
||||
* a list of source properties.
|
||||
* - stub_id: (optional) Identifies the migration which will be used to create
|
||||
* any stub entities.
|
||||
* - no_stub: (optional) Prevents the creation of a stub entity when no
|
||||
* relationship is found in the migration map.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* Consider a node migration, where you want to maintain authorship. If you have
|
||||
* migrated the user accounts in a migration named "users", you would specify
|
||||
* the following:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* source: author
|
||||
* @endcode
|
||||
*
|
||||
* This takes the value of the author property in the source data, and looks it
|
||||
* up in the map table associated with the users migration, returning the
|
||||
* resulting user ID and assigning it to the destination uid property.
|
||||
*
|
||||
* The value of 'migration' can be a list of migration IDs. When using multiple
|
||||
* migrations it is possible each use different source identifiers. In this
|
||||
* case one can use source_ids which is an array keyed by the migration IDs
|
||||
* and the value is a list of source properties.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration:
|
||||
* - users
|
||||
* - members
|
||||
* source_ids:
|
||||
* users:
|
||||
* - author
|
||||
* members:
|
||||
* - id
|
||||
* @endcode
|
||||
*
|
||||
* If the migration_lookup plugin does not find the source ID in the migration
|
||||
* map it will create a stub entity for the relationship to use. This stub is
|
||||
* generated by the migration provided. In the case of multiple migrations the
|
||||
* first value of the migration list will be used, but you can select the
|
||||
* migration you wish to use by using the stub_id configuration key:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration:
|
||||
* - users
|
||||
* - members
|
||||
* stub_id: members
|
||||
* @endcode
|
||||
*
|
||||
* In the above example, the value of stub_id selects the members migration to
|
||||
* create any stub entities.
|
||||
*
|
||||
* To prevent the creation of a stub entity when no relationship is found in the
|
||||
* migration map, use no_stub:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* uid:
|
||||
* plugin: migration_lookup
|
||||
* migration: users
|
||||
* no_stub: true
|
||||
* source: author
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "migration_lookup"
|
||||
* )
|
||||
*/
|
||||
class MigrationLookup extends ProcessPluginBase implements ContainerFactoryPluginInterface {
|
||||
|
||||
/**
|
||||
* The process plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigratePluginManager
|
||||
*/
|
||||
protected $processPluginManager;
|
||||
|
||||
/**
|
||||
* The migration plugin manager.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationPluginManagerInterface
|
||||
*/
|
||||
protected $migrationPluginManager;
|
||||
|
||||
/**
|
||||
* The migration to be executed.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrationInterface
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration, MigrationPluginManagerInterface $migration_plugin_manager, MigratePluginManagerInterface $process_plugin_manager) {
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->migrationPluginManager = $migration_plugin_manager;
|
||||
$this->migration = $migration;
|
||||
$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('plugin.manager.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 = [$migration_ids];
|
||||
}
|
||||
if (!is_array($value)) {
|
||||
$value = [$value];
|
||||
}
|
||||
$this->skipOnEmpty($value);
|
||||
$self = FALSE;
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $migrations */
|
||||
$destination_ids = NULL;
|
||||
$source_id_values = [];
|
||||
$migrations = $this->migrationPluginManager->createInstances($migration_ids);
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
if ($migration_id == $this->migration->id()) {
|
||||
$self = TRUE;
|
||||
}
|
||||
if (isset($this->configuration['source_ids'][$migration_id])) {
|
||||
$configuration = ['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 && !empty($this->configuration['no_stub'])) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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->getProcess();
|
||||
|
||||
// We already have the source ID values but need to key them for the Row
|
||||
// constructor.
|
||||
$source_ids = $migration->getSourcePlugin()->getIds();
|
||||
$values = [];
|
||||
foreach (array_keys($source_ids) as $index => $source_id) {
|
||||
$values[$source_id] = $source_id_values[$migration->id()][$index];
|
||||
}
|
||||
|
||||
$stub_row = $this->createStubRow($values + $migration->getSourceConfiguration(), $source_ids);
|
||||
|
||||
// Do a normal migration with the stub row.
|
||||
$migrate_executable->processRow($stub_row, $process);
|
||||
$destination_ids = [];
|
||||
try {
|
||||
$destination_ids = $destination_plugin->import($stub_row);
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$migration->getIdMap()->saveMessage($stub_row->getSourceIdValues(), $e->getMessage());
|
||||
}
|
||||
|
||||
if ($destination_ids) {
|
||||
$migration->getIdMap()->saveIdMapping($stub_row, $destination_ids, MigrateIdMapInterface::STATUS_NEEDS_UPDATE);
|
||||
}
|
||||
}
|
||||
if ($destination_ids) {
|
||||
if (count($destination_ids) == 1) {
|
||||
return reset($destination_ids);
|
||||
}
|
||||
else {
|
||||
return $destination_ids;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips the migration process entirely if the value is FALSE.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The incoming value to transform.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
*/
|
||||
protected function skipOnEmpty(array $value) {
|
||||
if (!array_filter($value)) {
|
||||
throw new MigrateSkipProcessException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a stub row source for later import as stub data.
|
||||
*
|
||||
* This simple wrapper of the Row constructor allows sub-classing plugins to
|
||||
* have more control over the row.
|
||||
*
|
||||
* @param array $values
|
||||
* An array of values to add as properties on the object.
|
||||
* @param array $source_ids
|
||||
* An array containing the IDs of the source using the keys as the field
|
||||
* names.
|
||||
*
|
||||
* @return \Drupal\migrate\Row
|
||||
* The stub row.
|
||||
*/
|
||||
protected function createStubRow(array $values, array $source_ids) {
|
||||
return new Row($values, $source_ids, TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,14 +3,54 @@
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\Core\Path\PathValidatorInterface;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Sets the destination route information based on the source link_path.
|
||||
*
|
||||
* The source value is an array of two values:
|
||||
* - link_path: The path or URL of the route.
|
||||
* - options: An array of URL options, e.g. query string, attributes, etc.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_route_field:
|
||||
* plugin: route
|
||||
* source:
|
||||
* - 'https://www.drupal.org'
|
||||
* -
|
||||
* attributes:
|
||||
* title: Drupal
|
||||
* @endcode
|
||||
*
|
||||
* This will set new_route_field to be a route with the URL
|
||||
* "https://www.drupal.org" and title attribute "Drupal".
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* another_route_field:
|
||||
* plugin: route
|
||||
* source:
|
||||
* - 'user/login'
|
||||
* -
|
||||
* query:
|
||||
* destination: 'node/1'
|
||||
* @endcode
|
||||
*
|
||||
* This will set another_route_field to be a route to the user login page
|
||||
* (user/login) with a query string of "destination=node/1".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "route"
|
||||
* )
|
||||
@@ -52,14 +92,21 @@ class Route extends ProcessPluginBase implements ContainerFactoryPluginInterface
|
||||
* 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;
|
||||
if (is_string($value)) {
|
||||
$link_path = $value;
|
||||
$options = [];
|
||||
}
|
||||
else {
|
||||
list($link_path, $options) = $value;
|
||||
}
|
||||
|
||||
$extracted = $this->pathValidator->getUrlIfValidWithoutAccessCheck($link_path);
|
||||
$route = array();
|
||||
$route = [];
|
||||
|
||||
if ($extracted) {
|
||||
if ($extracted->isExternal()) {
|
||||
$route['route_name'] = NULL;
|
||||
$route['route_parameters'] = array();
|
||||
$route['route_parameters'] = [];
|
||||
$route['options'] = $options;
|
||||
$route['url'] = $extracted->getUri();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,52 @@ use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* If the source evaluates to empty, we skip processing or the whole row.
|
||||
* Skips processing the current row when the input value is empty.
|
||||
*
|
||||
* The skip_on_empty process plugin checks to see if the current input value
|
||||
* is empty (empty string, NULL, FALSE, 0, '0', or an empty array). If so, the
|
||||
* further processing of the property or the entire row (depending on the chosen
|
||||
* method) is skipped and will not be migrated.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - method: (optional) What to do if the input value is empty. Possible values:
|
||||
* - row: Skips the entire row when an empty value is encountered.
|
||||
* - process: Prevents further processing of the input property when the value
|
||||
* is empty.
|
||||
* - message: (optional) A message to be logged in the {migrate_message_*} table
|
||||
* for this row. Messages are only logged for the 'row' skip level. If not
|
||||
* set, nothing is logged in the message table.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* field_type_exists:
|
||||
* plugin: skip_on_empty
|
||||
* method: row
|
||||
* source: field_name
|
||||
* message: 'Field field_name is missed'
|
||||
* @endcode
|
||||
*
|
||||
* If field_name is empty, skips the entire row and the message 'Field
|
||||
* field_name is missed' is logged in the message table.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* parent:
|
||||
* -
|
||||
* plugin: skip_on_empty
|
||||
* method: process
|
||||
* source: parent
|
||||
* -
|
||||
* plugin: migration
|
||||
* migration: d6_taxonomy_term
|
||||
* @endcode
|
||||
*
|
||||
* If parent is empty, any further processing of the property is skipped - thus,
|
||||
* the next plugin (migration) will not be run.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_on_empty"
|
||||
@@ -18,17 +63,52 @@ use Drupal\migrate\MigrateSkipRowException;
|
||||
class SkipOnEmpty extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Skips the current row when value is not set.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The input value.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return mixed
|
||||
* The input value, $value, if it is not empty.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipRowException
|
||||
* Thrown if the source property is not set and the row should be skipped,
|
||||
* records with STATUS_IGNORED status in the map.
|
||||
*/
|
||||
public function row($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
throw new MigrateSkipRowException();
|
||||
$message = !empty($this->configuration['message']) ? $this->configuration['message'] : '';
|
||||
throw new MigrateSkipRowException($message);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Stops processing the current property when value is not set.
|
||||
*
|
||||
* @param mixed $value
|
||||
* The input value.
|
||||
* @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.
|
||||
* @param string $destination_property
|
||||
* The destination property currently worked on. This is only used together
|
||||
* with the $row above.
|
||||
*
|
||||
* @return mixed
|
||||
* The input value, $value, if it is not empty.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateSkipProcessException
|
||||
* Thrown if the source property is not set and rest of the process should
|
||||
* be skipped.
|
||||
*/
|
||||
public function process($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!$value) {
|
||||
|
||||
@@ -8,7 +8,34 @@ use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* If the source evaluates to empty, we skip the current row.
|
||||
* Skips processing the current row when a source value is not set.
|
||||
*
|
||||
* The skip_row_if_not_set process plugin checks whether a value is set. If the
|
||||
* value is set, it is returned. Otherwise, a MigrateSkipRowException
|
||||
* is thrown.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - index: The source property to check for.
|
||||
* - message: (optional) A message to be logged in the {migrate_message_*} table
|
||||
* for this row. If not set, nothing is logged in the message table.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* settings:
|
||||
* # Check if the "contact" key exists in the "data" array.
|
||||
* plugin: skip_row_if_not_set
|
||||
* index: contact
|
||||
* source: data
|
||||
* message: "Missed the 'data' key"
|
||||
* @endcode
|
||||
*
|
||||
* This will return $data['contact'] if it exists. Otherwise, the row will be
|
||||
* skipped and the message "Missed the 'data' key" will be logged in the
|
||||
* message table.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "skip_row_if_not_set",
|
||||
@@ -22,7 +49,8 @@ class SkipRowIfNotSet extends ProcessPluginBase {
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
if (!isset($value[$this->configuration['index']])) {
|
||||
throw new MigrateSkipRowException();
|
||||
$message = !empty($this->configuration['message']) ? $this->configuration['message'] : '';
|
||||
throw new MigrateSkipRowException($message);
|
||||
}
|
||||
return $value[$this->configuration['index']];
|
||||
}
|
||||
|
||||
@@ -10,9 +10,98 @@ use Drupal\migrate\Row;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
|
||||
/**
|
||||
* This plugin changes the current value based on a static lookup map.
|
||||
* Changes the source value based on a static lookup map.
|
||||
*
|
||||
* @see https://www.drupal.org/node/2143521
|
||||
* Maps the input value to another value using an associative array specified in
|
||||
* the configuration.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - source: The input value - either a scalar or an array.
|
||||
* - map: An array (of 1 or more dimensions) that identifies the mapping between
|
||||
* source values and destination values.
|
||||
* - bypass: (optional) Whether the plugin should proceed when the source is not
|
||||
* found in the map array. Defaults to FALSE.
|
||||
* - TRUE: Return the unmodified input value, or another default value, if one
|
||||
* is specified.
|
||||
* - FALSE: Throw a MigrateSkipRowException.
|
||||
* - default_value: (optional) The value to return if the source is not found in
|
||||
* the map array.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* @endcode
|
||||
*
|
||||
* If the value of the source property foo was "from" then the value of the
|
||||
* destination property bar will be "to". Similarly "this" becomes "that".
|
||||
* static_map can do a lot more than this: it supports a list of source
|
||||
* properties. This is super useful in module-delta to machine name conversions.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* id:
|
||||
* plugin: static_map
|
||||
* source:
|
||||
* - module
|
||||
* - delta
|
||||
* map:
|
||||
* filter:
|
||||
* 0: filter_html_escape
|
||||
* 1: filter_autop
|
||||
* 2: filter_url
|
||||
* 3: filter_htmlcorrector
|
||||
* 4: filter_html_escape
|
||||
* php:
|
||||
* 0: php_code
|
||||
* @endcode
|
||||
*
|
||||
* If the value of the source properties module and delta are "filter" and "2"
|
||||
* respectively, then the returned value will be "filter_url". By default, if a
|
||||
* value is not found in the map, an exception is thrown.
|
||||
*
|
||||
* When static_map is used to just rename a few things and leave the others, a
|
||||
* "bypass: true" option can be added. In this case, the source value is used
|
||||
* unchanged, e.g.:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* bypass: TRUE
|
||||
* @endcode
|
||||
*
|
||||
* If the value of the source property "foo" is "from" then the returned value
|
||||
* will be "to", but if the value of "foo" is "another" (a value that is not in
|
||||
* the map) then the source value is used unchanged so the returned value will
|
||||
* be "from" because "bypass" is set to TRUE.
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* bar:
|
||||
* plugin: static_map
|
||||
* source: foo
|
||||
* map:
|
||||
* from: to
|
||||
* this: that
|
||||
* default_value: bar
|
||||
* @endcode
|
||||
*
|
||||
* If the value of the source property "foo" is "yet_another" (a value that is
|
||||
* not in the map) then the default_value is used so the returned value will
|
||||
* be "bar".
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "static_map"
|
||||
@@ -31,7 +120,7 @@ class StaticMap extends ProcessPluginBase {
|
||||
}
|
||||
}
|
||||
else {
|
||||
$new_value = array($value);
|
||||
$new_value = [$value];
|
||||
}
|
||||
$new_value = NestedArray::getValue($this->configuration['map'], $new_value, $key_exists);
|
||||
if (!$key_exists) {
|
||||
|
||||
@@ -9,7 +9,44 @@ use Drupal\migrate\MigrateException;
|
||||
use Drupal\Component\Utility\Unicode;
|
||||
|
||||
/**
|
||||
* This plugin returns a substring of the current value.
|
||||
* Returns a substring of the input value.
|
||||
*
|
||||
* The substr process plugin returns the portion of the input value specified by
|
||||
* the start and length parameters. This is a wrapper around the PHP substr()
|
||||
* function.
|
||||
*
|
||||
* Available configuration keys:
|
||||
* - start: (optional) The returned string will start this many characters after
|
||||
* the beginning of the string. Defaults to NULL.
|
||||
* - length: (optional) The maximum number of characters in the returned
|
||||
* string. Defaults to NULL.
|
||||
*
|
||||
* If start is NULL and length is an integer, the start position is the
|
||||
* beginning of the string. If start is an integer and length is NULL, the
|
||||
* substring starting from the start position until the end of the string will
|
||||
* be returned. If both start and length are NULL the entire string is returned.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_text_field:
|
||||
* plugin: substr
|
||||
* source: some_text_field
|
||||
* start: 6
|
||||
* length: 10
|
||||
* @endcode
|
||||
*
|
||||
* If some_text_field was 'Marie Skłodowska Curie' then
|
||||
* $destination['new_text_field'] would be 'Skłodowska'.
|
||||
*
|
||||
* The PHP equivalent of this is:
|
||||
*
|
||||
* @code
|
||||
* $destination['new_text_field'] = substr($source['some_text_field'], 6, 10);
|
||||
* @endcode
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "substr"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\process;
|
||||
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* URL-encodes the input value.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* @code
|
||||
* process:
|
||||
* new_url:
|
||||
* plugin: urlencode
|
||||
* source: 'http://example.com/a url with spaces.html'
|
||||
* @endcode
|
||||
*
|
||||
* This will convert the source URL 'http://example.com/a url with spaces.html'
|
||||
* into 'http://example.com/a%20url%20with%20spaces.html'.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateProcessInterface
|
||||
*
|
||||
* @MigrateProcessPlugin(
|
||||
* id = "urlencode"
|
||||
* )
|
||||
*/
|
||||
class UrlEncode extends ProcessPluginBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
|
||||
// Only apply to a full URL.
|
||||
if (is_string($value) && strpos($value, '://') > 0) {
|
||||
// URL encode everything after the hostname.
|
||||
$parsed_url = parse_url($value);
|
||||
// Fail on seriously malformed URLs.
|
||||
if ($parsed_url === FALSE) {
|
||||
throw new MigrateException("Value '$value' is not a valid URL");
|
||||
}
|
||||
// Iterate over specific pieces of the URL rawurlencoding each one.
|
||||
$url_parts_to_encode = ['path', 'query', 'fragment'];
|
||||
foreach ($parsed_url as $parsed_url_key => $parsed_url_value) {
|
||||
if (in_array($parsed_url_key, $url_parts_to_encode)) {
|
||||
// urlencode() would convert spaces to + signs.
|
||||
$urlencoded_parsed_url_value = rawurlencode($parsed_url_value);
|
||||
// Restore special characters depending on which part of the URL this is.
|
||||
switch ($parsed_url_key) {
|
||||
case 'query':
|
||||
$urlencoded_parsed_url_value = str_replace('%26', '&', $urlencoded_parsed_url_value);
|
||||
break;
|
||||
|
||||
case 'path':
|
||||
$urlencoded_parsed_url_value = str_replace('%2F', '/', $urlencoded_parsed_url_value);
|
||||
break;
|
||||
}
|
||||
|
||||
$parsed_url[$parsed_url_key] = $urlencoded_parsed_url_value;
|
||||
}
|
||||
}
|
||||
$value = (string) Uri::fromParts($parsed_url);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,10 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
|
||||
/**
|
||||
* Source which takes its data directly from the plugin config.
|
||||
* Allows source data to be defined in the configuration of the source plugin.
|
||||
*
|
||||
* The embedded_data source plugin is used to inject source data from the plugin
|
||||
* configuration. One use case is when some small amount of fixed data is
|
||||
* imported, so that it can be referenced by other migrations. Another use case
|
||||
* is testing.
|
||||
*
|
||||
* Available configuration keys
|
||||
* - data_rows: The source data array.
|
||||
* - ids: The unique ID field of the data.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* @code
|
||||
* source:
|
||||
* plugin: embedded_data
|
||||
* data_rows:
|
||||
* -
|
||||
* channel_machine_name: music
|
||||
* channel_description: Music
|
||||
* -
|
||||
* channel_machine_name: movies
|
||||
* channel_description: Movies
|
||||
* ids:
|
||||
* channel_machine_name:
|
||||
* type: string
|
||||
* @endcode
|
||||
*
|
||||
* This example migrates a channel vocabulary.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "embedded_data"
|
||||
|
||||
@@ -17,16 +17,16 @@ class EmptySource extends SourcePluginBase {
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
return array(
|
||||
return [
|
||||
'id' => t('ID'),
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function initializeIterator() {
|
||||
return new \ArrayIterator(array(array('id' => '')));
|
||||
return new \ArrayIterator([['id' => '']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Core\Plugin\PluginBase;
|
||||
use Drupal\migrate\Event\MigrateRollbackEvent;
|
||||
use Drupal\migrate\Event\RollbackAwareInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
@@ -20,7 +22,7 @@ use Drupal\migrate\Row;
|
||||
*
|
||||
* @ingroup migration
|
||||
*/
|
||||
abstract class SourcePluginBase extends PluginBase implements MigrateSourceInterface {
|
||||
abstract class SourcePluginBase extends PluginBase implements MigrateSourceInterface, RollbackAwareInterface {
|
||||
|
||||
/**
|
||||
* The module handler service.
|
||||
@@ -36,15 +38,6 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -59,10 +52,27 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
*/
|
||||
protected $currentSourceIds;
|
||||
|
||||
/**
|
||||
* Information on the property used as the high-water mark.
|
||||
*
|
||||
* Array of 'name' and (optional) db 'alias' properties used for high-water
|
||||
* mark.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $highWaterProperty = [];
|
||||
|
||||
/**
|
||||
* The key-value storage for the high-water value.
|
||||
*
|
||||
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
*/
|
||||
protected $highWaterStorage;
|
||||
|
||||
/**
|
||||
* The high water mark at the beginning of the import operation.
|
||||
*
|
||||
* If the source has a property for tracking changes (like Drupal ha
|
||||
* If the source has a property for tracking changes (like Drupal has
|
||||
* node.changed) then this is the highest value of those imported so far.
|
||||
*
|
||||
* @var int
|
||||
@@ -141,15 +151,18 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
$this->migration = $migration;
|
||||
|
||||
// Set up some defaults based on the source configuration.
|
||||
$this->cacheCounts = !empty($configuration['cache_counts']);
|
||||
$this->skipCount = !empty($configuration['skip_count']);
|
||||
foreach (['cacheCounts' => 'cache_counts', 'skipCount' => 'skip_count', 'trackChanges' => 'track_changes'] as $property => $config_key) {
|
||||
if (isset($configuration[$config_key])) {
|
||||
$this->$property = (bool) $configuration[$config_key];
|
||||
}
|
||||
}
|
||||
$this->cacheKey = !empty($configuration['cache_key']) ? $configuration['cache_key'] : NULL;
|
||||
$this->trackChanges = !empty($configuration['track_changes']) ? $configuration['track_changes'] : FALSE;
|
||||
$this->idMap = $this->migration->getIdMap();
|
||||
$this->highWaterProperty = !empty($configuration['high_water_property']) ? $configuration['high_water_property'] : FALSE;
|
||||
|
||||
// Pull out the current highwater mark if we have a highwater property.
|
||||
if ($this->highWaterProperty = $this->migration->getHighWaterProperty()) {
|
||||
$this->originalHighWater = $this->migration->getHighWater();
|
||||
if ($this->highWaterProperty) {
|
||||
$this->originalHighWater = $this->getHighWater();
|
||||
}
|
||||
|
||||
// Don't allow the use of both highwater and track changes together.
|
||||
@@ -185,8 +198,8 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
public function prepareRow(Row $row) {
|
||||
$result = TRUE;
|
||||
try {
|
||||
$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));
|
||||
$result_hook = $this->getModuleHandler()->invokeAll('migrate_prepare_row', [$row, $this, $this->migration]);
|
||||
$result_named_hook = $this->getModuleHandler()->invokeAll('migrate_' . $this->migration->id() . '_prepare_row', [$row, $this, $this->migration]);
|
||||
// We will skip if any hook returned FALSE.
|
||||
$skip = ($result_hook && in_array(FALSE, $result_hook)) || ($result_named_hook && in_array(FALSE, $result_named_hook));
|
||||
$save_to_map = TRUE;
|
||||
@@ -194,6 +207,9 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
catch (MigrateSkipRowException $e) {
|
||||
$skip = TRUE;
|
||||
$save_to_map = $e->getSaveToMap();
|
||||
if ($message = trim($e->getMessage())) {
|
||||
$this->idMap->saveMessage($row->getSourceIdValues(), $message, MigrationInterface::MESSAGE_INFORMATIONAL);
|
||||
}
|
||||
}
|
||||
|
||||
// We're explicitly skipping this row - keep track in the map table.
|
||||
@@ -201,7 +217,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
// Make sure we replace any previous messages for this item with any
|
||||
// new ones.
|
||||
if ($save_to_map) {
|
||||
$this->idMap->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_IGNORED);
|
||||
$this->idMap->saveIdMapping($row, [], MigrateIdMapInterface::STATUS_IGNORED);
|
||||
$this->currentRow = NULL;
|
||||
$this->currentSourceIds = NULL;
|
||||
}
|
||||
@@ -240,7 +256,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
/**
|
||||
* Gets the iterator key.
|
||||
*
|
||||
* Implementation of Iterator::key - called when entering a loop iteration,
|
||||
* 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.
|
||||
@@ -252,7 +268,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
/**
|
||||
* Checks whether the iterator is currently valid.
|
||||
*
|
||||
* Implementation of Iterator::valid() - called at the top of the loop,
|
||||
* 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() {
|
||||
@@ -262,9 +278,9 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
/**
|
||||
* Rewinds the iterator.
|
||||
*
|
||||
* Implementation of Iterator::rewind() - subclasses of MigrateSource should
|
||||
* implement performRewind() to do any class-specific setup for iterating
|
||||
* source records.
|
||||
* Implementation of \Iterator::rewind() - subclasses of SourcePluginBase
|
||||
* should implement initializeIterator() to do any class-specific setup for
|
||||
* iterating source records.
|
||||
*/
|
||||
public function rewind() {
|
||||
$this->getIterator()->rewind();
|
||||
@@ -294,13 +310,13 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
while (!isset($this->currentRow) && $this->getIterator()->valid()) {
|
||||
|
||||
$row_data = $this->getIterator()->current() + $this->configuration;
|
||||
$this->getIterator()->next();
|
||||
$this->fetchNextRow();
|
||||
$row = new Row($row_data, $this->migration->getSourcePlugin()->getIds(), $this->migration->getDestinationIds());
|
||||
|
||||
// Populate the source key for this row.
|
||||
$this->currentSourceIds = $row->getSourceIdValues();
|
||||
|
||||
// Pick up the existing map row, if any, unless getNextRow() did it.
|
||||
// Pick up the existing map row, if any, unless fetchNextRow() did it.
|
||||
if (!$this->mapRowAdded && ($id_map = $this->idMap->getRowBySource($this->currentSourceIds))) {
|
||||
$row->setIdMap($id_map);
|
||||
}
|
||||
@@ -324,11 +340,22 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
if (!$row->getIdMap() || $row->needsUpdate() || $this->aboveHighwater($row) || $this->rowChanged($row)) {
|
||||
$this->currentRow = $row->freezeSource();
|
||||
}
|
||||
|
||||
if ($this->getHighWaterProperty()) {
|
||||
$this->saveHighWater($row->getSourceProperty($this->highWaterProperty['name']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the incoming data is newer than what we've previously imported.
|
||||
* Position the iterator to the following row.
|
||||
*/
|
||||
protected function fetchNextRow() {
|
||||
$this->getIterator()->next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the incoming data is newer than what we've previously imported.
|
||||
*
|
||||
* @param \Drupal\migrate\Row $row
|
||||
* The row we're importing.
|
||||
@@ -337,7 +364,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
* 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;
|
||||
return $this->getHighWaterProperty() && $row->getSourceProperty($this->highWaterProperty['name']) > $this->originalHighWater;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,7 +411,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
// 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();
|
||||
$count = $this->doCount();
|
||||
$this->getCache()->set($this->cacheKey, $count);
|
||||
}
|
||||
else {
|
||||
@@ -397,7 +424,7 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
else {
|
||||
// No cached count, ask the derived class to count 'em up, and cache
|
||||
// the result.
|
||||
$count = $this->getIterator()->count();
|
||||
$count = $this->doCount();
|
||||
$this->getCache()->set($this->cacheKey, $count);
|
||||
}
|
||||
}
|
||||
@@ -417,4 +444,101 @@ abstract class SourcePluginBase extends PluginBase implements MigrateSourceInter
|
||||
return $this->cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source count checking if the source is countable or using the
|
||||
* iterator_count function.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function doCount() {
|
||||
$iterator = $this->getIterator();
|
||||
return $iterator instanceof \Countable ? $iterator->count() : iterator_count($this->initializeIterator());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the high water storage object.
|
||||
*
|
||||
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
|
||||
* The storage object.
|
||||
*/
|
||||
protected function getHighWaterStorage() {
|
||||
if (!isset($this->highWaterStorage)) {
|
||||
$this->highWaterStorage = \Drupal::keyValue('migrate:high_water');
|
||||
}
|
||||
return $this->highWaterStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current value of the high water mark.
|
||||
*
|
||||
* The high water mark defines a timestamp stating the time the import was last
|
||||
* run. If the mark is set, only content with a higher timestamp will be
|
||||
* imported.
|
||||
*
|
||||
* @return int|null
|
||||
* A Unix timestamp representing the high water mark, or NULL if no high
|
||||
* water mark has been stored.
|
||||
*/
|
||||
protected function getHighWater() {
|
||||
return $this->getHighWaterStorage()->get($this->migration->id());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the new high water mark.
|
||||
*
|
||||
* @param int $high_water
|
||||
* The high water timestamp.
|
||||
*/
|
||||
protected function saveHighWater($high_water) {
|
||||
$this->getHighWaterStorage()->set($this->migration->id(), $high_water);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get information on the property used as the high watermark.
|
||||
*
|
||||
* Array of 'name' & (optional) db 'alias' properties used for high watermark.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()
|
||||
*
|
||||
* @return array
|
||||
* The property used as the high watermark.
|
||||
*/
|
||||
protected function getHighWaterProperty() {
|
||||
return $this->highWaterProperty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the field used as the high watermark.
|
||||
*
|
||||
* The name of the field qualified with an alias if available.
|
||||
*
|
||||
* @see \Drupal\migrate\Plugin\migrate\source\SqlBase::initializeIterator()
|
||||
*
|
||||
* @return string|null
|
||||
* The name of the field for the high water mark, or NULL if not set.
|
||||
*/
|
||||
protected function getHighWaterField() {
|
||||
if (!empty($this->highWaterProperty['name'])) {
|
||||
return !empty($this->highWaterProperty['alias']) ?
|
||||
$this->highWaterProperty['alias'] . '.' . $this->highWaterProperty['name'] :
|
||||
$this->highWaterProperty['name'];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function preRollback(MigrateRollbackEvent $event) {
|
||||
// Nothing to do in this implementation.
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function postRollback(MigrateRollbackEvent $event) {
|
||||
// Reset the high-water mark.
|
||||
$this->saveHighWater(NULL);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,16 @@
|
||||
|
||||
namespace Drupal\migrate\Plugin\migrate\source;
|
||||
|
||||
use Drupal\Core\Database\ConnectionNotDefinedException;
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\migrate\Exception\RequirementsException;
|
||||
use Drupal\migrate\MigrateException;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Plugin\migrate\id_map\Sql;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\RequirementsInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
@@ -19,7 +23,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
* is present, it is used as a database connection information array to define
|
||||
* the connection.
|
||||
*/
|
||||
abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPluginInterface {
|
||||
abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPluginInterface, RequirementsInterface {
|
||||
|
||||
/**
|
||||
* The query string.
|
||||
@@ -42,6 +46,22 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* The count of the number of batches run.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $batch = 0;
|
||||
|
||||
/**
|
||||
* Number of records to fetch from the database during each batch.
|
||||
*
|
||||
* A value of zero indicates no batching is to be done.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $batchSize = 0;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -110,12 +130,17 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
*
|
||||
* @return \Drupal\Core\Database\Connection
|
||||
* The connection to use for this plugin's queries.
|
||||
*
|
||||
* @throws \Drupal\migrate\Exception\RequirementsException
|
||||
* Thrown if no source database connection is configured.
|
||||
*/
|
||||
protected function setUpDatabase(array $database_info) {
|
||||
if (isset($database_info['key'])) {
|
||||
$key = $database_info['key'];
|
||||
}
|
||||
else {
|
||||
// If there is no explicit database configuration at all, fall back to a
|
||||
// connection named 'migrate'.
|
||||
$key = 'migrate';
|
||||
}
|
||||
if (isset($database_info['target'])) {
|
||||
@@ -127,13 +152,35 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
if (isset($database_info['database'])) {
|
||||
Database::addConnectionInfo($key, $target, $database_info['database']);
|
||||
}
|
||||
return Database::getConnection($target, $key);
|
||||
try {
|
||||
$connection = Database::getConnection($target, $key);
|
||||
}
|
||||
catch (ConnectionNotDefinedException $e) {
|
||||
// If we fell back to the magic 'migrate' connection and it doesn't exist,
|
||||
// treat the lack of the connection as a RequirementsException.
|
||||
if ($key == 'migrate') {
|
||||
throw new RequirementsException("No database connection configured for source plugin " . $this->pluginId, [], 0, $e);
|
||||
}
|
||||
else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function checkRequirements() {
|
||||
if ($this->pluginDefinition['requirements_met'] === TRUE) {
|
||||
$this->getDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for database select.
|
||||
*/
|
||||
protected function select($table, $alias = NULL, array $options = array()) {
|
||||
protected function select($table, $alias = NULL, array $options = []) {
|
||||
$options['fetch'] = \PDO::FETCH_ASSOC;
|
||||
return $this->getDatabase()->select($table, $alias, $options);
|
||||
}
|
||||
@@ -154,82 +201,127 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$this->prepareQuery();
|
||||
$high_water_property = $this->migration->getHighWaterProperty();
|
||||
|
||||
// Get the key values, for potential use in joining to the map table.
|
||||
$keys = array();
|
||||
|
||||
// The rules for determining what conditions to add to the query are as
|
||||
// follows (applying first applicable rule):
|
||||
// 1. 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 (empty($this->configuration['ignore_map']) && $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'] . '.' . $this->query->escapeField($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->getDestinationIds())) {
|
||||
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');
|
||||
}
|
||||
// 2. 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'];
|
||||
// Initialize the batch size.
|
||||
if ($this->batchSize == 0 && isset($this->configuration['batch_size'])) {
|
||||
// Valid batch sizes are integers >= 0.
|
||||
if (is_int($this->configuration['batch_size']) && ($this->configuration['batch_size']) >= 0) {
|
||||
$this->batchSize = $this->configuration['batch_size'];
|
||||
}
|
||||
else {
|
||||
$high_water = $high_water_property['name'];
|
||||
throw new MigrateException("batch_size must be greater than or equal to zero");
|
||||
}
|
||||
$conditions->condition($high_water, $high_water, '>');
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
}
|
||||
|
||||
// If a batch has run the query is already setup.
|
||||
if ($this->batch == 0) {
|
||||
$this->prepareQuery();
|
||||
|
||||
// Get the key values, for potential use in joining to the map table.
|
||||
$keys = [];
|
||||
|
||||
// The rules for determining what conditions to add to the query are as
|
||||
// follows (applying first applicable rule):
|
||||
// 1. 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;
|
||||
$added_fields = [];
|
||||
if (empty($this->configuration['ignore_map']) && $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'] . '.' . $this->query->escapeField($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");
|
||||
$added_fields[] = "$alias.$map_key";
|
||||
}
|
||||
if ($n = count($this->migration->getDestinationIds())) {
|
||||
for ($count = 1; $count <= $n; $count++) {
|
||||
$map_key = 'destid' . $count++;
|
||||
$this->query->addField($alias, $map_key, "migrate_map_$map_key");
|
||||
$added_fields[] = "$alias.$map_key";
|
||||
}
|
||||
}
|
||||
$this->query->addField($alias, 'source_row_status', 'migrate_map_source_row_status');
|
||||
$added_fields[] = "$alias.source_row_status";
|
||||
}
|
||||
// 2. 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 ($this->getHighWaterProperty() && ($high_water = $this->getHighWater())) {
|
||||
$high_water_field = $this->getHighWaterField();
|
||||
$conditions->condition($high_water_field, $high_water, '>');
|
||||
$this->query->orderBy($high_water_field);
|
||||
$condition_added = TRUE;
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
}
|
||||
// If the query has a group by, our added fields need it too, to keep the
|
||||
// query valid.
|
||||
// @see https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html
|
||||
$group_by = $this->query->getGroupBy();
|
||||
if ($group_by && $added_fields) {
|
||||
foreach ($added_fields as $added_field) {
|
||||
$this->query->groupBy($added_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Download data in batches for performance.
|
||||
if (($this->batchSize > 0)) {
|
||||
$this->query->range($this->batch * $this->batchSize, $this->batchSize);
|
||||
}
|
||||
return new \IteratorIterator($this->query->execute());
|
||||
}
|
||||
|
||||
/**
|
||||
* Position the iterator to the following row.
|
||||
*/
|
||||
protected function fetchNextRow() {
|
||||
$this->getIterator()->next();
|
||||
// We might be out of data entirely, or just out of data in the current
|
||||
// batch. Attempt to fetch the next batch and see.
|
||||
if ($this->batchSize > 0 && !$this->getIterator()->valid()) {
|
||||
$this->fetchNextBatch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares query for the next set of data from the source database.
|
||||
*/
|
||||
protected function fetchNextBatch() {
|
||||
$this->batch++;
|
||||
unset($this->iterator);
|
||||
$this->getIterator()->rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Drupal\Core\Database\Query\SelectInterface
|
||||
*/
|
||||
@@ -255,6 +347,14 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
if (!$this->getIds()) {
|
||||
return FALSE;
|
||||
}
|
||||
// With batching, we want a later batch to return the same rows that would
|
||||
// have been returned at the same point within a monolithic query. If we
|
||||
// join to the map table, the first batch is writing to the map table and
|
||||
// thus affecting the results of subsequent batches. To be safe, we avoid
|
||||
// joining to the map table when batching.
|
||||
if ($this->batchSize > 0) {
|
||||
return FALSE;
|
||||
}
|
||||
$id_map = $this->migration->getIdMap();
|
||||
if (!$id_map instanceof Sql) {
|
||||
return FALSE;
|
||||
@@ -270,7 +370,15 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
foreach (array('username', 'password', 'host', 'port', 'namespace', 'driver') as $key) {
|
||||
// FALSE if driver is PostgreSQL and database doesn't match.
|
||||
if ($id_map_database_options['driver'] === 'pgsql' &&
|
||||
$source_database_options['driver'] === 'pgsql' &&
|
||||
$id_map_database_options['database'] != $source_database_options['database']
|
||||
) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
foreach (['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;
|
||||
|
||||
@@ -15,21 +15,21 @@ class Row {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source = array();
|
||||
protected $source = [];
|
||||
|
||||
/**
|
||||
* The source identifiers.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $sourceIds = array();
|
||||
protected $sourceIds = [];
|
||||
|
||||
/**
|
||||
* The destination values.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $destination = array();
|
||||
protected $destination = [];
|
||||
|
||||
/**
|
||||
* Level separator of destination and source properties.
|
||||
@@ -41,11 +41,11 @@ class Row {
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $idMap = array(
|
||||
protected $idMap = [
|
||||
'original_hash' => '',
|
||||
'hash' => '',
|
||||
'source_row_status' => MigrateIdMapInterface::STATUS_NEEDS_UPDATE,
|
||||
);
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether the source has been frozen already.
|
||||
@@ -91,7 +91,7 @@ class Row {
|
||||
* @throws \InvalidArgumentException
|
||||
* Thrown when a source ID property does not exist.
|
||||
*/
|
||||
public function __construct(array $values, array $source_ids, $is_stub = FALSE) {
|
||||
public function __construct(array $values = [], array $source_ids = [], $is_stub = FALSE) {
|
||||
$this->source = $values;
|
||||
$this->sourceIds = $source_ids;
|
||||
$this->isStub = $is_stub;
|
||||
@@ -106,10 +106,11 @@ class Row {
|
||||
* Retrieves the values of the source identifiers.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the values of the source identifiers.
|
||||
* An array containing the values of the source identifiers. Returns values
|
||||
* in the same order as defined in $this->sourceIds.
|
||||
*/
|
||||
public function getSourceIdValues() {
|
||||
return array_intersect_key($this->source, $this->sourceIds);
|
||||
return array_merge($this->sourceIds, array_intersect_key($this->source, $this->sourceIds));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user