Update to Drupal 8.2.0. For more information, see https://www.drupal.org/project/drupal/releases/8.2.0
This commit is contained in:
parent
2f563ab520
commit
f1c8716f57
1732 changed files with 52334 additions and 11780 deletions
|
@ -40,7 +40,8 @@ use Drupal\migrate\Row;
|
|||
* 'core/modules/action/migration_templates'. The plugin class is
|
||||
* \Drupal\migrate\Plugin\Migration, with interface
|
||||
* \Drupal\migrate\Plugin\MigrationInterface. Migration plugins are managed by
|
||||
* the \Drupal\migrate\Plugin\MigrationPluginManager class.
|
||||
* the \Drupal\migrate\Plugin\MigrationPluginManager class. Migration plugins
|
||||
* are only available if the providers of their source plugins are installed.
|
||||
*
|
||||
* @section sec_source Source plugins
|
||||
* Migration source plugins implement
|
||||
|
@ -49,7 +50,8 @@ use Drupal\migrate\Row;
|
|||
* with \Drupal\migrate\Annotation\MigrateSource annotation, and must be in
|
||||
* namespace subdirectory Plugin\migrate\source under the namespace of the
|
||||
* module that defines them. Migration source plugins are managed by the
|
||||
* \Drupal\migrate\Plugin\MigratePluginManager class.
|
||||
* \Drupal\migrate\Plugin\MigratePluginManager class. Source plugin providers
|
||||
* are determined by their and their parents namespaces.
|
||||
*
|
||||
* @section sec_process Process plugins
|
||||
* Migration process plugins implement
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
services:
|
||||
migrate.plugin_event_subscriber:
|
||||
class: Drupal\migrate\Plugin\PluginEventSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
cache.migrate:
|
||||
class: Drupal\Core\Cache\CacheBackendInterface
|
||||
tags:
|
||||
|
@ -6,8 +10,8 @@ services:
|
|||
factory: cache_factory:get
|
||||
arguments: [migrate]
|
||||
plugin.manager.migrate.source:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [source, '@container.namespaces', '@cache.discovery', '@module_handler', 'Drupal\migrate\Annotation\MigrateSource']
|
||||
class: Drupal\migrate\Plugin\MigrateSourcePluginManager
|
||||
arguments: [source, '@container.namespaces', '@cache.discovery', '@module_handler']
|
||||
plugin.manager.migrate.process:
|
||||
class: Drupal\migrate\Plugin\MigratePluginManager
|
||||
arguments: [process, '@container.namespaces', '@cache.discovery', '@module_handler', 'Drupal\migrate\Annotation\MigrateProcessPlugin']
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
60
core/modules/migrate/src/Event/EventBase.php
Normal file
60
core/modules/migrate/src/Event/EventBase.php
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
26
core/modules/migrate/src/Event/ImportAwareInterface.php
Normal file
26
core/modules/migrate/src/Event/ImportAwareInterface.php
Normal file
|
@ -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);
|
||||
|
||||
}
|
|
@ -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.
|
||||
*
|
||||
|
|
26
core/modules/migrate/src/Event/RollbackAwareInterface.php
Normal file
26
core/modules/migrate/src/Event/RollbackAwareInterface.php
Normal file
|
@ -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);
|
||||
|
||||
}
|
|
@ -177,7 +177,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
)), '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 +185,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',
|
||||
array(
|
||||
'@id' => $this->migration->id(),
|
||||
'@message' => $e->getMessage(),
|
||||
'@requirements' => $e->getRequirementsString(),
|
||||
)
|
||||
),
|
||||
'error'
|
||||
);
|
||||
|
||||
return MigrationInterface::RESULT_FAILED;
|
||||
}
|
||||
|
||||
|
@ -229,9 +235,9 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
|
||||
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) {
|
||||
|
@ -256,9 +262,6 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
$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);
|
||||
|
@ -288,7 +291,7 @@ class MigrateExecutable implements MigrateExecutableInterface {
|
|||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
@ -348,10 +351,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));
|
||||
|
@ -476,30 +475,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.',
|
||||
array(
|
||||
'@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',
|
||||
array(
|
||||
'@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',
|
||||
array(
|
||||
'@pct' => round($pct_memory * 100),
|
||||
'@usage' => $this->formatSize($usage),
|
||||
'@limit' => $this->formatSize($this->memoryLimit),
|
||||
)
|
||||
),
|
||||
'warning');
|
||||
return FALSE;
|
||||
}
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate;
|
||||
|
||||
use Drupal\Component\Graph\Graph;
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Config\Entity\ConfigEntityStorage;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactoryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Storage for migration entities.
|
||||
*/
|
||||
class MigrationStorage extends ConfigEntityStorage implements MigrateBuildDependencyInterface {
|
||||
|
||||
/**
|
||||
* The entity query factory service.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactoryInterface
|
||||
*/
|
||||
protected $queryFactory;
|
||||
|
||||
/**
|
||||
* Constructs a MigrationStorage object.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
|
||||
* An entity type definition.
|
||||
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
|
||||
* The config factory service.
|
||||
* @param \Drupal\Component\Uuid\UuidInterface $uuid_service
|
||||
* The UUID service.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactoryInterface $query_factory
|
||||
* The entity query factory service.
|
||||
*/
|
||||
public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, QueryFactoryInterface $query_factory) {
|
||||
parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
|
||||
$this->queryFactory = $query_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
|
||||
return new static(
|
||||
$entity_type,
|
||||
$container->get('config.factory'),
|
||||
$container->get('uuid'),
|
||||
$container->get('language_manager'),
|
||||
$container->get('entity.query.config')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function loadMultiple(array $ids = NULL) {
|
||||
if ($ids) {
|
||||
$ids = $this->getVariantIds($ids);
|
||||
}
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface[] $migrations */
|
||||
$migrations = parent::loadMultiple($ids);
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$dependencies = array_map([$this, 'getVariantIds'], $migration->getMigrationDependencies());
|
||||
$migration->set('migration_dependencies', $dependencies);
|
||||
}
|
||||
|
||||
// Build an array of dependencies and set the order of the migrations.
|
||||
return $this->buildDependencyMigration($migrations, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splices variant IDs into a list of migration IDs.
|
||||
*
|
||||
* IDs which match the template_id:* pattern are shorthand for every variant
|
||||
* of template_id. This method queries for those variant IDs and splices them
|
||||
* into the original list.
|
||||
*
|
||||
* @param string[] $ids
|
||||
* A set of migration IDs.
|
||||
*
|
||||
* @return string[]
|
||||
* The expanded list of IDs.
|
||||
*/
|
||||
public function getVariantIds(array $ids) {
|
||||
// Re-index the array numerically, since we need to limit the loop by size.
|
||||
$ids = array_values($ids);
|
||||
|
||||
$index = 0;
|
||||
while ($index < count($ids)) {
|
||||
if (substr($ids[$index], -2) == ':*') {
|
||||
$template_id = substr($ids[$index], 0, -2);
|
||||
$variants = $this->queryFactory->get($this->entityType, 'OR')
|
||||
->condition('id', $template_id)
|
||||
->condition('template', $template_id)
|
||||
->execute();
|
||||
array_splice($ids, $index, 1, $variants);
|
||||
$index += count($variants);
|
||||
}
|
||||
else {
|
||||
$index++;
|
||||
}
|
||||
}
|
||||
return $ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
|
||||
// Migration dependencies defined in the migration storage can be
|
||||
// optional or required. If an optional dependency does not run, the current
|
||||
// migration is still OK to go. Both optional and required dependencies
|
||||
// (if run at all) must run before the current migration.
|
||||
$dependency_graph = array();
|
||||
$requirement_graph = array();
|
||||
$different = FALSE;
|
||||
foreach ($migrations as $migration) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$id = $migration->id();
|
||||
$requirements[$id] = array();
|
||||
$dependency_graph[$id]['edges'] = array();
|
||||
$migration_dependencies = $migration->getMigrationDependencies();
|
||||
|
||||
if (isset($migration_dependencies['required'])) {
|
||||
foreach ($migration_dependencies['required'] as $dependency) {
|
||||
if (!isset($dynamic_ids[$dependency])) {
|
||||
$this->addDependency($requirement_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
}
|
||||
if (isset($migration_dependencies['optional'])) {
|
||||
foreach ($migration_dependencies['optional'] as $dependency) {
|
||||
$different = TRUE;
|
||||
$this->addDependency($dependency_graph, $id, $dependency, $dynamic_ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
$graph_object = new Graph($dependency_graph);
|
||||
$dependency_graph = $graph_object->searchAndSort();
|
||||
if ($different) {
|
||||
$graph_object = new Graph($requirement_graph);
|
||||
$requirement_graph = $graph_object->searchAndSort();
|
||||
}
|
||||
else {
|
||||
$requirement_graph = $dependency_graph;
|
||||
}
|
||||
$weights = array();
|
||||
foreach ($migrations as $migration_id => $migration) {
|
||||
// Populate a weights array to use with array_multisort later.
|
||||
$weights[] = $dependency_graph[$migration_id]['weight'];
|
||||
if (!empty($requirement_graph[$migration_id]['paths'])) {
|
||||
$migration->set('requirements', $requirement_graph[$migration_id]['paths']);
|
||||
}
|
||||
}
|
||||
array_multisort($weights, SORT_DESC, SORT_NUMERIC, $migrations);
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add one or more dependencies to a graph.
|
||||
*
|
||||
* @param array $graph
|
||||
* The graph so far, passed by reference.
|
||||
* @param int $id
|
||||
* The migration ID.
|
||||
* @param string $dependency
|
||||
* The dependency string.
|
||||
* @param array $dynamic_ids
|
||||
* The dynamic ID mapping.
|
||||
*/
|
||||
protected function addDependency(array &$graph, $id, $dependency, $dynamic_ids) {
|
||||
$dependencies = isset($dynamic_ids[$dependency]) ? $dynamic_ids[$dependency] : array($dependency);
|
||||
if (!isset($graph[$id]['edges'])) {
|
||||
$graph[$id]['edges'] = array();
|
||||
}
|
||||
$graph[$id]['edges'] += array_combine($dependencies, $dependencies);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 = array();
|
||||
|
||||
$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[] $definitions
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate\Plugin;
|
||||
|
||||
use Drupal\Component\Plugin\PluginInspectionInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
<?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 {
|
||||
|
||||
/**
|
||||
* The class loader.
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $classLoader;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -77,7 +86,7 @@ class MigrationPluginManager extends DefaultPluginManager implements MigrationPl
|
|||
* {@inheritdoc}
|
||||
*/
|
||||
public function createInstance($plugin_id, array $configuration = array()) {
|
||||
$instances = $this->createInstances([$plugin_id], $configuration);
|
||||
$instances = $this->createInstances([$plugin_id], [$plugin_id => $configuration]);
|
||||
return reset($instances);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
58
core/modules/migrate/src/Plugin/NoSourcePluginDecorator.php
Normal file
58
core/modules/migrate/src/Plugin/NoSourcePluginDecorator.php
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
94
core/modules/migrate/src/Plugin/PluginEventSubscriber.php
Normal file
94
core/modules/migrate/src/Plugin/PluginEventSubscriber.php
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -147,7 +147,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) {
|
||||
|
|
225
core/modules/migrate/src/Plugin/migrate/process/FileCopy.php
Normal file
225
core/modules/migrate/src/Plugin/migrate/process/FileCopy.php
Normal file
|
@ -0,0 +1,225 @@
|
|||
<?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\ProcessPluginBase;
|
||||
use Drupal\migrate\Row;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Copy a file from one place into another.
|
||||
*
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public function __construct(array $configuration, $plugin_id, array $plugin_definition, StreamWrapperManagerInterface $stream_wrappers, FileSystemInterface $file_system) {
|
||||
$configuration += array(
|
||||
'move' => FALSE,
|
||||
'rename' => FALSE,
|
||||
'reuse' => FALSE,
|
||||
);
|
||||
parent::__construct($configuration, $plugin_id, $plugin_definition);
|
||||
$this->streamWrapperManager = $stream_wrappers;
|
||||
$this->fileSystem = $file_system;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@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;
|
||||
|
||||
// Ensure the source file exists, if it's a local URI or path.
|
||||
if ($this->isLocalUri($source) && !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;
|
||||
}
|
||||
|
||||
$replace = $this->getOverwriteMode();
|
||||
// We attempt the copy/move first to avoid calling file_prepare_directory()
|
||||
// any more than absolutely necessary.
|
||||
$final_destination = $this->writeFile($source, $destination, $replace);
|
||||
if ($final_destination) {
|
||||
return $final_destination;
|
||||
}
|
||||
// If writeFile didn't work, make sure there's a writable directory in
|
||||
// place.
|
||||
$dir = $this->getDirectory($destination);
|
||||
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, $replace);
|
||||
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) {
|
||||
if ($this->configuration['move']) {
|
||||
return file_unmanaged_move($source, $destination, $replace);
|
||||
}
|
||||
// Check if there is a destination available for copying. If there isn't,
|
||||
// it already exists at the destination and the replace flag tells us to not
|
||||
// replace it. In that case, return the original destination.
|
||||
if (!($final_destination = file_destination($destination, $replace))) {
|
||||
return $destination;
|
||||
}
|
||||
// We can't use file_unmanaged_copy because it will break with remote Urls.
|
||||
if (@copy($source, $final_destination)) {
|
||||
return $final_destination;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* If either URI is a remote stream, will return FALSE.
|
||||
*
|
||||
* @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) {
|
||||
if ($this->isLocalUri($source) && $this->isLocalUri($destination)) {
|
||||
return $this->fileSystem->realpath($source) === $this->fileSystem->realpath($destination);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
return $scheme === FALSE || $this->streamWrapperManager->getViaScheme($scheme) instanceof LocalStream;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<?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;
|
||||
|
||||
/**
|
||||
* Apply urlencoding to a URI.
|
||||
*
|
||||
* This is needed when the URI is to be opened by a later migration stage, and
|
||||
* the source URI value is not already encoded.
|
||||
*
|
||||
* @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 = array('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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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.
|
||||
|
@ -324,6 +337,10 @@ 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']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,7 +354,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 +401,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 +414,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 +434,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -161,7 +161,6 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
*/
|
||||
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();
|
||||
|
@ -213,15 +212,10 @@ abstract class SqlBase extends SourcePluginBase implements ContainerFactoryPlugi
|
|||
}
|
||||
// 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'];
|
||||
}
|
||||
else {
|
||||
$high_water = $high_water_property['name'];
|
||||
}
|
||||
$conditions->condition($high_water, $high_water, '>');
|
||||
$condition_added = TRUE;
|
||||
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);
|
||||
}
|
||||
if ($condition_added) {
|
||||
$this->query->condition($conditions);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
langcode: en
|
||||
status: true
|
||||
name: High Water import node
|
||||
type: high_water_import_node
|
||||
description: ''
|
||||
help: ''
|
||||
new_revision: false
|
||||
preview_mode: 1
|
||||
display_submitted: true
|
|
@ -0,0 +1,7 @@
|
|||
type: module
|
||||
name: Migrate SQL Source test
|
||||
description: 'Provides a database table and records for SQL import testing.'
|
||||
package: Testing
|
||||
core: 8.x
|
||||
dependencies:
|
||||
- migrate
|
|
@ -0,0 +1,16 @@
|
|||
id: high_water_test
|
||||
label: High water test.
|
||||
source:
|
||||
plugin: high_water_test
|
||||
high_water_property:
|
||||
name: changed
|
||||
destination:
|
||||
plugin: entity:node
|
||||
migration_tags:
|
||||
test: test
|
||||
process:
|
||||
changed: changed
|
||||
title: title
|
||||
type:
|
||||
plugin: default_value
|
||||
default_value: high_water_import_node
|
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\migrate_sql_test\Plugin\migrate\source;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\source\SqlBase;
|
||||
|
||||
/**
|
||||
* Source plugin for migration high water tests.
|
||||
*
|
||||
* @MigrateSource(
|
||||
* id = "high_water_test"
|
||||
* )
|
||||
*/
|
||||
class HighWaterTest extends SqlBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function query() {
|
||||
$query = $this
|
||||
->select('high_water_node', 'm')
|
||||
->fields('m', array_keys($this->fields()));
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function fields() {
|
||||
$fields = [
|
||||
'id' => $this->t('Id'),
|
||||
'title' => $this->t('Title'),
|
||||
'changed' => $this->t('Changed'),
|
||||
];
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getIds() {
|
||||
return [
|
||||
'id' => [
|
||||
'type' => 'integer',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
177
core/modules/migrate/tests/src/Kernel/HighWaterTest.php
Normal file
177
core/modules/migrate/tests/src/Kernel/HighWaterTest.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
/**
|
||||
* Tests migration high water property.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class HighWaterTest extends MigrateTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'system',
|
||||
'user',
|
||||
'node',
|
||||
'migrate',
|
||||
'migrate_sql_test',
|
||||
'field',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
// Create source test table.
|
||||
$this->sourceDatabase->schema()->createTable('high_water_node', [
|
||||
'fields' => [
|
||||
'id' => [
|
||||
'description' => 'Serial',
|
||||
'type' => 'serial',
|
||||
'unsigned' => TRUE,
|
||||
'not null' => TRUE,
|
||||
],
|
||||
'changed' => [
|
||||
'description' => 'Highwater',
|
||||
'type' => 'int',
|
||||
'unsigned' => TRUE,
|
||||
],
|
||||
'title' => [
|
||||
'description' => 'Title',
|
||||
'type' => 'varchar',
|
||||
'length' => 128,
|
||||
'not null' => TRUE,
|
||||
'default' => '',
|
||||
],
|
||||
],
|
||||
'primary key' => [
|
||||
'id',
|
||||
],
|
||||
'description' => 'Contains nodes to import',
|
||||
]);
|
||||
|
||||
// Add 3 items to source table.
|
||||
$this->sourceDatabase->insert('high_water_node')
|
||||
->fields([
|
||||
'title',
|
||||
'changed',
|
||||
])
|
||||
->values([
|
||||
'title' => 'Item 1',
|
||||
'changed' => 1,
|
||||
])
|
||||
->values([
|
||||
'title' => 'Item 2',
|
||||
'changed' => 2,
|
||||
])
|
||||
->values([
|
||||
'title' => 'Item 3',
|
||||
'changed' => 3,
|
||||
])
|
||||
->execute();
|
||||
|
||||
$this->installEntitySchema('node');
|
||||
$this->installEntitySchema('user');
|
||||
$this->installSchema('node', 'node_access');
|
||||
|
||||
$this->executeMigration('high_water_test');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests high water property of SqlBase.
|
||||
*/
|
||||
public function testHighWater() {
|
||||
// Assert all of the nodes have been imported.
|
||||
$this->assertNodeExists('Item 1');
|
||||
$this->assertNodeExists('Item 2');
|
||||
$this->assertNodeExists('Item 3');
|
||||
|
||||
// Update Item 1 setting its high_water_property to value that is below
|
||||
// current high water mark.
|
||||
$this->sourceDatabase->update('high_water_node')
|
||||
->fields([
|
||||
'title' => 'Item 1 updated',
|
||||
'changed' => 2,
|
||||
])
|
||||
->condition('title', 'Item 1')
|
||||
->execute();
|
||||
|
||||
// Update Item 2 setting its high_water_property to value equal to
|
||||
// current high water mark.
|
||||
$this->sourceDatabase->update('high_water_node')
|
||||
->fields([
|
||||
'title' => 'Item 2 updated',
|
||||
'changed' => 3,
|
||||
])
|
||||
->condition('title', 'Item 2')
|
||||
->execute();
|
||||
|
||||
// Update Item 3 setting its high_water_property to value that is above
|
||||
// current high water mark.
|
||||
$this->sourceDatabase->update('high_water_node')
|
||||
->fields([
|
||||
'title' => 'Item 3 updated',
|
||||
'changed' => 4,
|
||||
])
|
||||
->condition('title', 'Item 3')
|
||||
->execute();
|
||||
|
||||
// Execute migration again.
|
||||
$this->executeMigration('high_water_test');
|
||||
|
||||
// Item with lower highwater should not be updated.
|
||||
$this->assertNodeExists('Item 1');
|
||||
$this->assertNodeDoesNotExist('Item 1 updated');
|
||||
|
||||
// Item with equal highwater should not be updated.
|
||||
$this->assertNodeExists('Item 2');
|
||||
$this->assertNodeDoesNotExist('Item 2 updated');
|
||||
|
||||
// Item with greater highwater should be updated.
|
||||
$this->assertNodeExists('Item 3 updated');
|
||||
$this->assertNodeDoesNotExist('Item 3');
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that node with given title exists.
|
||||
*
|
||||
* @param string $title
|
||||
* Title of the node.
|
||||
*/
|
||||
protected function assertNodeExists($title) {
|
||||
self::assertTrue($this->nodeExists($title));
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert that node with given title does not exist.
|
||||
*
|
||||
* @param string $title
|
||||
* Title of the node.
|
||||
*/
|
||||
protected function assertNodeDoesNotExist($title) {
|
||||
self::assertFalse($this->nodeExists($title));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if node with given title exists.
|
||||
*
|
||||
* @param string $title
|
||||
* Title of the node.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function nodeExists($title) {
|
||||
$query = \Drupal::entityQuery('node');
|
||||
$result = $query
|
||||
->condition('title', $title)
|
||||
->range(0, 1)
|
||||
->execute();
|
||||
|
||||
return !empty($result);
|
||||
}
|
||||
|
||||
}
|
180
core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php
Normal file
180
core/modules/migrate/tests/src/Kernel/MigrateSourceTestBase.php
Normal file
|
@ -0,0 +1,180 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Base class for tests of Migrate source plugins.
|
||||
*/
|
||||
abstract class MigrateSourceTestBase extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['migrate'];
|
||||
|
||||
/**
|
||||
* The mocked migration.
|
||||
*
|
||||
* @var MigrationInterface|\Prophecy\Prophecy\ObjectProphecy
|
||||
*/
|
||||
protected $migration;
|
||||
|
||||
/**
|
||||
* The source plugin under test.
|
||||
*
|
||||
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
*/
|
||||
protected $plugin;
|
||||
|
||||
/**
|
||||
* The data provider.
|
||||
*
|
||||
* @see \Drupal\Tests\migrate\Kernel\MigrateSourceTestBase::testSource
|
||||
*
|
||||
* @return array
|
||||
* Array of data sets to test, each of which is a numerically indexed array
|
||||
* with the following elements:
|
||||
* - An array of source data, which can be optionally processed and set up
|
||||
* by subclasses.
|
||||
* - An array of expected result rows.
|
||||
* - (optional) The number of result rows the plugin under test is expected
|
||||
* to return. If this is not a numeric value, the plugin will not be
|
||||
* counted.
|
||||
* - (optional) Array of configuration options for the plugin under test.
|
||||
*/
|
||||
abstract public function providerSource();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
// Create a mock migration. This will be injected into the source plugin
|
||||
// under test.
|
||||
$this->migration = $this->prophesize(MigrationInterface::class);
|
||||
|
||||
$this->migration->id()->willReturn(
|
||||
$this->randomMachineName(16)
|
||||
);
|
||||
// Prophesize a useless ID map plugin and an empty set of destination IDs.
|
||||
// Calling code can override these prophecies later and set up different
|
||||
// behaviors.
|
||||
$this->migration->getIdMap()->willReturn(
|
||||
$this->prophesize(MigrateIdMapInterface::class)->reveal()
|
||||
);
|
||||
$this->migration->getDestinationIds()->willReturn([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the plugin to be tested by reading the class @covers annotation.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPluginClass() {
|
||||
$annotations = $this->getAnnotations();
|
||||
|
||||
if (isset($annotations['class']['covers'])) {
|
||||
return $annotations['class']['covers'][0];
|
||||
}
|
||||
else {
|
||||
$this->fail('No plugin class was specified');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiates the source plugin under test.
|
||||
*
|
||||
* @param array $configuration
|
||||
* The source plugin configuration.
|
||||
*
|
||||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface|object
|
||||
* The fully configured source plugin.
|
||||
*/
|
||||
protected function getPlugin(array $configuration) {
|
||||
// Only create the plugin once per test.
|
||||
if ($this->plugin) {
|
||||
return $this->plugin;
|
||||
}
|
||||
|
||||
$class = ltrim($this->getPluginClass(), '\\');
|
||||
|
||||
/** @var \Drupal\migrate\Plugin\MigratePluginManager $plugin_manager */
|
||||
$plugin_manager = $this->container->get('plugin.manager.migrate.source');
|
||||
|
||||
foreach ($plugin_manager->getDefinitions() as $id => $definition) {
|
||||
if (ltrim($definition['class'], '\\') == $class) {
|
||||
$this->plugin = $plugin_manager
|
||||
->createInstance($id, $configuration, $this->migration->reveal());
|
||||
|
||||
$this->migration
|
||||
->getSourcePlugin()
|
||||
->willReturn($this->plugin);
|
||||
|
||||
return $this->plugin;
|
||||
}
|
||||
}
|
||||
$this->fail('No plugin found for class ' . $class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the source plugin against a particular data set.
|
||||
*
|
||||
* @param array $source_data
|
||||
* The source data that the source plugin will read.
|
||||
* @param array $expected_data
|
||||
* The result rows the source plugin is expected to return.
|
||||
* @param mixed $expected_count
|
||||
* (optional) How many rows the source plugin is expected to return.
|
||||
* Defaults to count($expected_data). If set to a non-null, non-numeric
|
||||
* value (like FALSE or 'nope'), the source plugin will not be counted.
|
||||
* @param array $configuration
|
||||
* (optional) Configuration for the source plugin.
|
||||
*
|
||||
* @dataProvider providerSource
|
||||
*/
|
||||
public function testSource(array $source_data, array $expected_data, $expected_count = NULL, array $configuration = []) {
|
||||
$plugin = $this->getPlugin($configuration);
|
||||
|
||||
// All source plugins must define IDs.
|
||||
$this->assertNotEmpty($plugin->getIds());
|
||||
|
||||
if (is_null($expected_count)) {
|
||||
$expected_count = count($expected_data);
|
||||
}
|
||||
// If an expected count was given, assert it only if the plugin is
|
||||
// countable.
|
||||
if (is_numeric($expected_count)) {
|
||||
$this->assertInstanceOf('\Countable', $plugin);
|
||||
$this->assertCount($expected_count, $plugin);
|
||||
}
|
||||
|
||||
$i = 0;
|
||||
/** @var \Drupal\migrate\Row $row */
|
||||
foreach ($plugin as $row) {
|
||||
$this->assertInstanceOf(Row::class, $row);
|
||||
|
||||
$expected = $expected_data[$i++];
|
||||
$actual = $row->getSource();
|
||||
|
||||
foreach ($expected as $key => $value) {
|
||||
$this->assertArrayHasKey($key, $actual);
|
||||
|
||||
if (is_array($value)) {
|
||||
ksort($value);
|
||||
ksort($actual[$key]);
|
||||
$this->assertEquals($value, $actual[$key]);
|
||||
}
|
||||
else {
|
||||
$this->assertSame((string) $value, (string) $actual[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel;
|
||||
|
||||
use Drupal\Core\Database\Driver\sqlite\Connection;
|
||||
|
||||
/**
|
||||
* Base class for tests of Migrate source plugins that use a database.
|
||||
*/
|
||||
abstract class MigrateSqlSourceTestBase extends MigrateSourceTestBase {
|
||||
|
||||
/**
|
||||
* Builds an in-memory SQLite database from a set of source data.
|
||||
*
|
||||
* @param array $source_data
|
||||
* The source data, keyed by table name. Each table is an array containing
|
||||
* the rows in that table.
|
||||
*
|
||||
* @return \Drupal\Core\Database\Driver\sqlite\Connection
|
||||
* The SQLite database connection.
|
||||
*/
|
||||
protected function getDatabase(array $source_data) {
|
||||
// Create an in-memory SQLite database. Plugins can interact with it like
|
||||
// any other database, and it will cease to exist when the connection is
|
||||
// closed.
|
||||
$connection_options = ['database' => ':memory:'];
|
||||
$pdo = Connection::open($connection_options);
|
||||
$connection = new Connection($pdo, $connection_options);
|
||||
|
||||
// Create the tables and fill them with data.
|
||||
foreach ($source_data as $table => $rows) {
|
||||
// Use the biggest row to build the table schema.
|
||||
$counts = array_map('count', $rows);
|
||||
asort($counts);
|
||||
end($counts);
|
||||
$pilot = $rows[key($counts)];
|
||||
|
||||
$connection->schema()
|
||||
->createTable($table, [
|
||||
// SQLite uses loose affinity typing, so it's OK for every field to
|
||||
// be a text field.
|
||||
'fields' => array_map(function() { return ['type' => 'text']; }, $pilot),
|
||||
]);
|
||||
|
||||
$fields = array_keys($pilot);
|
||||
$insert = $connection->insert($table)->fields($fields);
|
||||
array_walk($rows, [$insert, 'values']);
|
||||
$insert->execute();
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the source plugin against a particular data set.
|
||||
*
|
||||
* @param array $source_data
|
||||
* The source data that the plugin will read. See getDatabase() for the
|
||||
* expected format.
|
||||
* @param array $expected_data
|
||||
* The result rows the plugin is expected to return.
|
||||
* @param int $expected_count
|
||||
* (optional) How many rows the source plugin is expected to return.
|
||||
* @param array $configuration
|
||||
* (optional) Configuration for the source plugin.
|
||||
*
|
||||
* @dataProvider providerSource
|
||||
*
|
||||
* @requires extension pdo_sqlite
|
||||
*/
|
||||
public function testSource(array $source_data, array $expected_data, $expected_count = NULL, array $configuration = []) {
|
||||
$plugin = $this->getPlugin($configuration);
|
||||
|
||||
// Since we don't yet inject the database connection, we need to use a
|
||||
// reflection hack to set it in the plugin instance.
|
||||
$reflector = new \ReflectionObject($plugin);
|
||||
$property = $reflector->getProperty('database');
|
||||
$property->setAccessible(TRUE);
|
||||
$property->setValue($plugin, $this->getDatabase($source_data));
|
||||
|
||||
parent::testSource($source_data, $expected_data, $expected_count, $configuration);
|
||||
}
|
||||
|
||||
}
|
|
@ -7,6 +7,8 @@ use Drupal\KernelTests\KernelTestBase;
|
|||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\MigrateMessageInterface;
|
||||
use Drupal\migrate\Plugin\MigrateIdMapInterface;
|
||||
use Drupal\migrate\Plugin\Migration;
|
||||
use Drupal\migrate\Plugin\MigrationInterface;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
|
@ -136,6 +138,16 @@ abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageI
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify a migration's configuration before executing it.
|
||||
*
|
||||
* @param \Drupal\migrate\Plugin\MigrationInterface $migration
|
||||
* The migration to execute.
|
||||
*/
|
||||
protected function prepareMigration(MigrationInterface $migration) {
|
||||
// Default implementation for test classes not requiring modification.
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a single migration.
|
||||
*
|
||||
|
@ -152,6 +164,8 @@ abstract class MigrateTestBase extends KernelTestBase implements MigrateMessageI
|
|||
if ($this instanceof MigrateDumpAlterInterface) {
|
||||
static::migrateDumpAlter($this);
|
||||
}
|
||||
|
||||
$this->prepareMigration($this->migration);
|
||||
(new MigrateExecutable($this->migration, $this))->import();
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ class MigrationTest extends KernelTestBase {
|
|||
$this->assertEqual('entity:entity_view_mode', $migration->getDestinationPlugin()->getPluginId());
|
||||
|
||||
// Test the source plugin is invalidated.
|
||||
$migration->set('source', ['plugin' => 'd6_field']);
|
||||
$this->assertEqual('d6_field', $migration->getSourcePlugin()->getPluginId());
|
||||
$migration->set('source', ['plugin' => 'embedded_data', 'data_rows' => [], 'ids' => []]);
|
||||
$this->assertEqual('embedded_data', $migration->getSourcePlugin()->getPluginId());
|
||||
|
||||
// Test the destination plugin is invalidated.
|
||||
$migration->set('destination', ['plugin' => 'null']);
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel\Plugin;
|
||||
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the migration plugin manager.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrationPluginConfigurationTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'migrate',
|
||||
'migrate_drupal',
|
||||
// Test with a simple migration.
|
||||
'ban',
|
||||
];
|
||||
|
||||
/**
|
||||
* Test merging configuration into a plugin through the plugin manager.
|
||||
*
|
||||
* @dataProvider mergeProvider
|
||||
*/
|
||||
public function testConfigurationMerge($configuration, $expected) {
|
||||
/** @var \Drupal\migrate\Plugin\MigrationInterface $migration */
|
||||
$migration = $this->container->get('plugin.manager.migration')->createInstance('d7_blocked_ips', $configuration);
|
||||
$source_configuration = $migration->getSourceConfiguration();
|
||||
$this->assertEquals($expected, $source_configuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide configuration data for testing.
|
||||
*/
|
||||
public function mergeProvider() {
|
||||
return [
|
||||
// Tests adding new configuration to a migration.
|
||||
[
|
||||
// New configuration.
|
||||
[
|
||||
'source' => [
|
||||
'constants' => [
|
||||
'added_setting' => 'Ban them all!',
|
||||
],
|
||||
],
|
||||
],
|
||||
// Expected final source configuration.
|
||||
[
|
||||
'plugin' => 'd7_blocked_ips',
|
||||
'constants' => [
|
||||
'added_setting' => 'Ban them all!',
|
||||
],
|
||||
],
|
||||
],
|
||||
// Tests overriding pre-existing configuration in a migration.
|
||||
[
|
||||
// New configuration.
|
||||
[
|
||||
'source' => [
|
||||
'plugin' => 'a_different_plugin',
|
||||
],
|
||||
],
|
||||
// Expected final source configuration.
|
||||
[
|
||||
'plugin' => 'a_different_plugin',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel\Plugin;
|
||||
|
||||
use Drupal\Core\Database\Database;
|
||||
use Drupal\KernelTests\KernelTestBase;
|
||||
|
||||
/**
|
||||
* Tests the migration plugin manager.
|
||||
*
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\MigratePluginManager
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrationPluginListTest extends KernelTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = [
|
||||
'migrate',
|
||||
// Test with all modules containing Drupal migrations.
|
||||
'action',
|
||||
'aggregator',
|
||||
'ban',
|
||||
'block',
|
||||
'block_content',
|
||||
'book',
|
||||
'comment',
|
||||
'contact',
|
||||
'dblog',
|
||||
'field',
|
||||
'file',
|
||||
'filter',
|
||||
'forum',
|
||||
'image',
|
||||
'language',
|
||||
'locale',
|
||||
'menu_link_content',
|
||||
'menu_ui',
|
||||
'node',
|
||||
'path',
|
||||
'search',
|
||||
'shortcut',
|
||||
'simpletest',
|
||||
'statistics',
|
||||
'syslog',
|
||||
'system',
|
||||
'taxonomy',
|
||||
'text',
|
||||
'tracker',
|
||||
'update',
|
||||
'user',
|
||||
];
|
||||
|
||||
/**
|
||||
* @covers ::getDefinitions
|
||||
*/
|
||||
public function testGetDefinitions() {
|
||||
// Make sure retrieving all the core migration plugins does not throw any
|
||||
// errors.
|
||||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
// All the plugins provided by core depend on migrate_drupal.
|
||||
$this->assertEmpty($migration_plugins);
|
||||
|
||||
// Enable a module that provides migrations that do not depend on
|
||||
// migrate_drupal.
|
||||
$this->enableModules(['migrate_external_translated_test']);
|
||||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
// All the plugins provided by migrate_external_translated_test do not
|
||||
// depend on migrate_drupal.
|
||||
$this::assertArrayHasKey('external_translated_test_node', $migration_plugins);
|
||||
$this::assertArrayHasKey('external_translated_test_node_translation', $migration_plugins);
|
||||
|
||||
// Disable the test module and the list should be empty again.
|
||||
$this->disableModules(['migrate_external_translated_test']);
|
||||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
// All the plugins provided by core depend on migrate_drupal.
|
||||
$this->assertEmpty($migration_plugins);
|
||||
|
||||
// Enable migrate_drupal to test that the plugins can now be discovered.
|
||||
$this->enableModules(['migrate_drupal']);
|
||||
// Set up a migrate database connection so that plugin discovery works.
|
||||
// Clone the current connection and replace the current prefix.
|
||||
$connection_info = Database::getConnectionInfo('migrate');
|
||||
if ($connection_info) {
|
||||
Database::renameConnection('migrate', 'simpletest_original_migrate');
|
||||
}
|
||||
$connection_info = Database::getConnectionInfo('default');
|
||||
foreach ($connection_info as $target => $value) {
|
||||
$prefix = is_array($value['prefix']) ? $value['prefix']['default'] : $value['prefix'];
|
||||
// Simpletest uses 7 character prefixes at most so this can't cause
|
||||
// collisions.
|
||||
$connection_info[$target]['prefix']['default'] = $prefix . '0';
|
||||
|
||||
// Add the original simpletest prefix so SQLite can attach its database.
|
||||
// @see \Drupal\Core\Database\Driver\sqlite\Connection::init()
|
||||
$connection_info[$target]['prefix'][$value['prefix']['default']] = $value['prefix']['default'];
|
||||
}
|
||||
Database::addConnectionInfo('migrate', 'default', $connection_info['default']);
|
||||
|
||||
$migration_plugins = $this->container->get('plugin.manager.migration')->getDefinitions();
|
||||
// All the plugins provided by core depend on migrate_drupal.
|
||||
$this->assertNotEmpty($migration_plugins);
|
||||
}
|
||||
|
||||
}
|
|
@ -33,10 +33,27 @@ class MigrationTest extends KernelTestBase {
|
|||
* @covers ::getMigrationDependencies
|
||||
*/
|
||||
public function testGetMigrationDependencies() {
|
||||
$migration = \Drupal::service('plugin.manager.migration')->createStubMigration([
|
||||
'migration_dependencies' => NULL
|
||||
]);
|
||||
$this->assertNotEmpty($migration->getMigrationDependencies(), 'Migration dependencies is not empty');
|
||||
$plugin_manager = \Drupal::service('plugin.manager.migration');
|
||||
$plugin_definition = [
|
||||
'process' => [
|
||||
'f1' => 'bar',
|
||||
'f2' => [
|
||||
'plugin' => 'migration',
|
||||
'migration' => 'm1'
|
||||
],
|
||||
'f3' => [
|
||||
'plugin' => 'iterator',
|
||||
'process' => [
|
||||
'target_id' => [
|
||||
'plugin' => 'migration',
|
||||
'migration' => 'm2',
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
$migration = $plugin_manager->createStubMigration($plugin_definition);
|
||||
$this->assertSame(['required' => [], 'optional' => ['m1', 'm2']], $migration->getMigrationDependencies());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
183
core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php
Normal file
183
core/modules/migrate/tests/src/Kernel/process/CopyFileTest.php
Normal file
|
@ -0,0 +1,183 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Kernel\process;
|
||||
|
||||
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
|
||||
use Drupal\KernelTests\Core\File\FileTestBase;
|
||||
use Drupal\migrate\MigrateExecutableInterface;
|
||||
use Drupal\migrate\Plugin\migrate\process\FileCopy;
|
||||
use Drupal\migrate\Row;
|
||||
|
||||
/**
|
||||
* Tests the copy_file process plugin.
|
||||
*
|
||||
* @group migrate
|
||||
*/
|
||||
class CopyFileTest extends FileTestBase {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static $modules = ['system'];
|
||||
|
||||
/**
|
||||
* The file system service.
|
||||
*
|
||||
* @var \Drupal\Core\File\FileSystemInterface
|
||||
*/
|
||||
protected $fileSystem;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
$this->fileSystem = $this->container->get('file_system');
|
||||
$this->container->get('stream_wrapper_manager')->registerWrapper('temporary', 'Drupal\Core\StreamWrapper\TemporaryStream', StreamWrapperInterface::LOCAL_NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful imports/copies.
|
||||
*/
|
||||
public function testSuccessfulCopies() {
|
||||
$file = $this->createUri(NULL, NULL, 'temporary');
|
||||
$file_absolute = $this->fileSystem->realpath($file);
|
||||
$data_sets = [
|
||||
// Test a local to local copy.
|
||||
[
|
||||
$this->root . '/core/modules/simpletest/files/image-test.jpg',
|
||||
'public://file1.jpg'
|
||||
],
|
||||
// Test a temporary file using an absolute path.
|
||||
[
|
||||
$file_absolute,
|
||||
'temporary://test.jpg'
|
||||
],
|
||||
// Test a temporary file using a relative path.
|
||||
[
|
||||
$file_absolute,
|
||||
'temporary://core/modules/simpletest/files/test.jpg'
|
||||
],
|
||||
];
|
||||
foreach ($data_sets as $data) {
|
||||
list($source_path, $destination_path) = $data;
|
||||
$actual_destination = $this->doImport($source_path, $destination_path);
|
||||
$message = sprintf('File %s exists', $destination_path);
|
||||
$this->assertFileExists($destination_path, $message);
|
||||
// Make sure we didn't accidentally do a move.
|
||||
$this->assertFileExists($source_path, $message);
|
||||
$this->assertSame($actual_destination, $destination_path, 'The import returned the copied filename.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful file reuse.
|
||||
*/
|
||||
public function testSuccessfulReuse() {
|
||||
$source_path = $this->root . '/core/modules/simpletest/files/image-test.jpg';
|
||||
$destination_path = 'public://file1.jpg';
|
||||
$file_reuse = file_unmanaged_copy($source_path, $destination_path);
|
||||
$timestamp = (new \SplFileInfo($file_reuse))->getMTime();
|
||||
$this->assertInternalType('int', $timestamp);
|
||||
|
||||
// We need to make sure the modified timestamp on the file is sooner than
|
||||
// the attempted migration.
|
||||
sleep(1);
|
||||
$configuration = ['reuse' => TRUE];
|
||||
$this->doImport($source_path, $destination_path, $configuration);
|
||||
clearstatcache(TRUE, $destination_path);
|
||||
$modified_timestamp = (new \SplFileInfo($destination_path))->getMTime();
|
||||
$this->assertEquals($timestamp, $modified_timestamp);
|
||||
|
||||
$configuration = ['reuse' => FALSE];
|
||||
$this->doImport($source_path, $destination_path, $configuration);
|
||||
clearstatcache(TRUE, $destination_path);
|
||||
$modified_timestamp = (new \SplFileInfo($destination_path))->getMTime();
|
||||
$this->assertGreaterThan($timestamp, $modified_timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test successful moves.
|
||||
*/
|
||||
public function testSuccessfulMoves() {
|
||||
$file_1 = $this->createUri(NULL, NULL, 'temporary');
|
||||
$file_1_absolute = $this->fileSystem->realpath($file_1);
|
||||
$file_2 = $this->createUri(NULL, NULL, 'temporary');
|
||||
$file_2_absolute = $this->fileSystem->realpath($file_2);
|
||||
$local_file = $this->createUri(NULL, NULL, 'public');
|
||||
$data_sets = [
|
||||
// Test a local to local copy.
|
||||
[
|
||||
$local_file,
|
||||
'public://file1.jpg'
|
||||
],
|
||||
// Test a temporary file using an absolute path.
|
||||
[
|
||||
$file_1_absolute,
|
||||
'temporary://test.jpg'
|
||||
],
|
||||
// Test a temporary file using a relative path.
|
||||
[
|
||||
$file_2_absolute,
|
||||
'temporary://core/modules/simpletest/files/test.jpg'
|
||||
],
|
||||
];
|
||||
foreach ($data_sets as $data) {
|
||||
list($source_path, $destination_path) = $data;
|
||||
$actual_destination = $this->doImport($source_path, $destination_path, ['move' => TRUE]);
|
||||
$message = sprintf('File %s exists', $destination_path);
|
||||
$this->assertFileExists($destination_path, $message);
|
||||
$message = sprintf('File %s does not exist', $source_path);
|
||||
$this->assertFileNotExists($source_path, $message);
|
||||
$this->assertSame($actual_destination, $destination_path, 'The importer returned the moved filename.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that non-existent files throw an exception.
|
||||
*
|
||||
* @expectedException \Drupal\migrate\MigrateException
|
||||
*
|
||||
* @expectedExceptionMessage File '/non/existent/file' does not exist
|
||||
*/
|
||||
public function testNonExistentSourceFile() {
|
||||
$source = '/non/existent/file';
|
||||
$this->doImport($source, 'public://wontmatter.jpg');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the 'rename' overwrite mode.
|
||||
*/
|
||||
public function testRenameFile() {
|
||||
$source = $this->createUri(NULL, NULL, 'temporary');
|
||||
$destination = $this->createUri('foo.txt', NULL, 'public');
|
||||
$expected_destination = 'public://foo_0.txt';
|
||||
$actual_destination = $this->doImport($source, $destination, ['rename' => TRUE]);
|
||||
$this->assertFileExists($expected_destination, 'File was renamed on import');
|
||||
$this->assertSame($actual_destination, $expected_destination, 'The importer returned the renamed filename.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Do an import using the destination.
|
||||
*
|
||||
* @param string $source_path
|
||||
* Source path to copy from.
|
||||
* @param string $destination_path
|
||||
* The destination path to copy to.
|
||||
* @param array $configuration
|
||||
* Process plugin configuration settings.
|
||||
*
|
||||
* @throws \Drupal\migrate\MigrateException
|
||||
*/
|
||||
protected function doImport($source_path, $destination_path, $configuration = []) {
|
||||
$plugin = FileCopy::create($this->container, $configuration, 'file_copy', []);
|
||||
$executable = $this->prophesize(MigrateExecutableInterface::class)->reveal();
|
||||
$row = new Row([], []);
|
||||
|
||||
$result = $plugin->transform([$source_path, $destination_path], $executable, $row, 'foobaz');
|
||||
|
||||
// Return the imported file Uri.
|
||||
return $result;
|
||||
}
|
||||
|
||||
}
|
44
core/modules/migrate/tests/src/Unit/Event/EventBaseTest.php
Normal file
44
core/modules/migrate/tests/src/Unit/Event/EventBaseTest.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Event;
|
||||
|
||||
use Drupal\migrate\Event\EventBase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Event\EventBase
|
||||
* @group migrate
|
||||
*/
|
||||
class EventBaseTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* Test getMigration method.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getMigration
|
||||
*/
|
||||
public function testGetMigration() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface')->reveal();
|
||||
$row = $this->prophesize('\Drupal\migrate\Row')->reveal();
|
||||
$event = new EventBase($migration, $message_service, $row, [1, 2, 3]);
|
||||
$this->assertSame($migration, $event->getMigration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logging a message.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::logMessage
|
||||
*/
|
||||
public function testLogMessage() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface');
|
||||
$event = new EventBase($migration, $message_service->reveal());
|
||||
// Assert that the intended calls to the services happen.
|
||||
$message_service->display('status message', 'status')->shouldBeCalledTimes(1);
|
||||
$event->logMessage('status message');
|
||||
$message_service->display('warning message', 'warning')->shouldBeCalledTimes(1);
|
||||
$event->logMessage('warning message', 'warning');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Event;
|
||||
|
||||
use Drupal\migrate\Event\MigrateImportEvent;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Event\MigrateImportEvent
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrateImportEventTest extends \PHPUnit_Framework_TestCase {
|
||||
|
||||
/**
|
||||
* Test getMigration method.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getMigration
|
||||
*/
|
||||
public function testGetMigration() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface')->reveal();
|
||||
$event = new MigrateImportEvent($migration, $message_service);
|
||||
$this->assertSame($migration, $event->getMigration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logging a message.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::logMessage
|
||||
*/
|
||||
public function testLogMessage() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface');
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface');
|
||||
$event = new MigrateImportEvent($migration->reveal(), $message_service->reveal());
|
||||
// Assert that the intended calls to the services happen.
|
||||
$message_service->display('status message', 'status')->shouldBeCalledTimes(1);
|
||||
$event->logMessage('status message');
|
||||
$message_service->display('warning message', 'warning')->shouldBeCalledTimes(1);
|
||||
$event->logMessage('warning message', 'warning');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Event;
|
||||
|
||||
use Drupal\migrate\Event\MigratePostRowSaveEvent;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Event\MigratePostRowSaveEvent
|
||||
* @group migrate
|
||||
*/
|
||||
class MigratePostRowSaveEventTest extends EventBaseTest {
|
||||
|
||||
/**
|
||||
* Test getDestinationIdValues method.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getDestinationIdValues
|
||||
*/
|
||||
public function testGetDestinationIdValues() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface')->reveal();
|
||||
$row = $this->prophesize('\Drupal\migrate\Row')->reveal();
|
||||
$event = new MigratePostRowSaveEvent($migration, $message_service, $row, [1, 2, 3]);
|
||||
$this->assertSame([1, 2, 3], $event->getDestinationIdValues());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test getRow method.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getRow
|
||||
*/
|
||||
public function testGetRow() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface');
|
||||
$row = $this->prophesize('\Drupal\migrate\Row')->reveal();
|
||||
$event = new MigratePostRowSaveEvent($migration, $message_service->reveal(), $row, [1, 2, 3]);
|
||||
$this->assertSame($row, $event->getRow());
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\Event;
|
||||
|
||||
use Drupal\migrate\Event\MigratePreRowSaveEvent;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Event\MigratePreRowSaveEvent
|
||||
* @group migrate
|
||||
*/
|
||||
class MigratePreRowSaveEventTest extends EventBaseTest {
|
||||
|
||||
/**
|
||||
* Test getRow method.
|
||||
*
|
||||
* @covers ::__construct
|
||||
* @covers ::getRow
|
||||
*/
|
||||
public function testGetRow() {
|
||||
$migration = $this->prophesize('\Drupal\migrate\Plugin\MigrationInterface')->reveal();
|
||||
$message_service = $this->prophesize('\Drupal\migrate\MigrateMessageInterface')->reveal();
|
||||
$row = $this->prophesize('\Drupal\migrate\Row')->reveal();
|
||||
$event = new MigratePreRowSaveEvent($migration, $message_service, $row);
|
||||
$this->assertSame($row, $event->getRow());
|
||||
}
|
||||
|
||||
}
|
|
@ -114,9 +114,9 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
->with($row, array('test'))
|
||||
->will($this->returnValue(array('id' => 'test')));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
->willReturn($destination);
|
||||
|
||||
$this->assertSame(MigrationInterface::RESULT_COMPLETED, $this->executable->import());
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
->with($row, array('test'))
|
||||
->will($this->returnValue(TRUE));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
->willReturn($destination);
|
||||
|
||||
$this->idMap->expects($this->never())
|
||||
->method('saveIdMapping');
|
||||
|
@ -196,9 +196,9 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
->with($row, array('test'))
|
||||
->will($this->returnValue(array()));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
->willReturn($destination);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
|
@ -256,9 +256,9 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
->with($row, array('test'))
|
||||
->will($this->throwException(new MigrateException($exception_message)));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
->willReturn($destination);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
|
@ -306,7 +306,7 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
$destination->expects($this->never())
|
||||
->method('import');
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->willReturn($destination);
|
||||
|
||||
|
@ -354,9 +354,9 @@ class MigrateExecutableTest extends MigrateTestCase {
|
|||
->with($row, array('test'))
|
||||
->will($this->throwException(new \Exception($exception_message)));
|
||||
|
||||
$this->migration->expects($this->once())
|
||||
$this->migration
|
||||
->method('getDestinationPlugin')
|
||||
->will($this->returnValue($destination));
|
||||
->willReturn($destination);
|
||||
|
||||
$this->idMap->expects($this->once())
|
||||
->method('saveIdMapping')
|
||||
|
|
|
@ -10,6 +10,8 @@ namespace Drupal\Tests\migrate\Unit;
|
|||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\MigrateSkipRowException;
|
||||
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
|
||||
|
@ -75,7 +77,21 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
|
||||
* A mocked source plugin.
|
||||
*/
|
||||
protected function getSource($configuration = [], $migrate_config = [], $status = MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
|
||||
protected function getSource($configuration = [], $migrate_config = [], $status = MigrateIdMapInterface::STATUS_NEEDS_UPDATE, $high_water_value = NULL) {
|
||||
$container = new ContainerBuilder();
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$key_value = $this->getMock(KeyValueStoreInterface::class);
|
||||
|
||||
$key_value_factory = $this->getMock(KeyValueFactoryInterface::class);
|
||||
$key_value_factory
|
||||
->method('get')
|
||||
->with('migrate:high_water')
|
||||
->willReturn($key_value);
|
||||
$container->set('keyvalue', $key_value_factory);
|
||||
|
||||
$container->set('cache.migrate', $this->getMock(CacheBackendInterface::class));
|
||||
|
||||
$this->migrationConfiguration = $this->defaultMigrationConfiguration + $migrate_config;
|
||||
$this->migration = parent::getMigration();
|
||||
$this->executable = $this->getMigrateExecutable($this->migration);
|
||||
|
@ -90,47 +106,45 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
->willReturn($id_map_array);
|
||||
|
||||
$constructor_args = [$configuration, 'd6_action', [], $this->migration];
|
||||
$methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'getIterator', 'prepareRow', 'initializeIterator', 'calculateDependencies'];
|
||||
$source_plugin = $this->getMock('\Drupal\migrate\Plugin\migrate\source\SourcePluginBase', $methods, $constructor_args);
|
||||
$methods = ['getModuleHandler', 'fields', 'getIds', '__toString', 'prepareRow', 'initializeIterator'];
|
||||
$source_plugin = $this->getMock(SourcePluginBase::class, $methods, $constructor_args);
|
||||
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('fields')
|
||||
->willReturn([]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getIds')
|
||||
->willReturn([]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('__toString')
|
||||
->willReturn('');
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('prepareRow')
|
||||
->willReturn(empty($migrate_config['prepare_row_false']));
|
||||
|
||||
$rows = [$this->row];
|
||||
if (isset($configuration['high_water_property']) && isset($high_water_value)) {
|
||||
$property = $configuration['high_water_property']['name'];
|
||||
$rows = array_filter($rows, function (array $row) use ($property, $high_water_value) {
|
||||
return $row[$property] >= $high_water_value;
|
||||
});
|
||||
}
|
||||
$iterator = new \ArrayIterator($rows);
|
||||
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('initializeIterator')
|
||||
->willReturn([]);
|
||||
$iterator = new \ArrayIterator([$this->row]);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getIterator')
|
||||
->willReturn($iterator);
|
||||
|
||||
$module_handler = $this->getMock('\Drupal\Core\Extension\ModuleHandlerInterface');
|
||||
$module_handler = $this->getMock(ModuleHandlerInterface::class);
|
||||
$source_plugin
|
||||
->expects($this->any())
|
||||
->method('getModuleHandler')
|
||||
->willReturn($module_handler);
|
||||
|
||||
$this->migration
|
||||
->expects($this->any())
|
||||
->method('getSourcePlugin')
|
||||
->willReturn($source_plugin);
|
||||
|
||||
return $this->migration->getSourcePlugin();
|
||||
return $source_plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -138,9 +152,8 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
* @expectedException \Drupal\migrate\MigrateException
|
||||
*/
|
||||
public function testHighwaterTrackChangesIncompatible() {
|
||||
$source_config = ['track_changes' => TRUE];
|
||||
$migration_config = ['highWaterProperty' => ['name' => 'something']];
|
||||
$this->getSource($source_config, $migration_config);
|
||||
$source_config = ['track_changes' => TRUE, 'high_water_property' => ['name' => 'something']];
|
||||
$this->getSource($source_config);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -168,9 +181,20 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
// Test the skip argument.
|
||||
$source = $this->getSource(['skip_count' => TRUE]);
|
||||
$this->assertEquals(-1, $source->count());
|
||||
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourceGeneratorPlugin([], '', [], $migration);
|
||||
|
||||
// Test the skipCount property's default value.
|
||||
$this->assertEquals(-1, $source->count());
|
||||
|
||||
// Test the count value using a generator.
|
||||
$source = new StubSourceGeneratorPlugin(['skip_count' => FALSE], '', [], $migration);
|
||||
$this->assertEquals(3, $source->count());
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Test that the key can be set for the count cache.
|
||||
*
|
||||
* @covers ::count
|
||||
|
@ -219,14 +243,12 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
* Test that an outdated highwater mark does not cause a row to be imported.
|
||||
*/
|
||||
public function testOutdatedHighwater() {
|
||||
|
||||
$source = $this->getSource([], [], MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
|
||||
// Set the originalHighwater to something higher than our timestamp.
|
||||
$this->migration
|
||||
->expects($this->any())
|
||||
->method('getHighwater')
|
||||
->willReturn($this->row['timestamp'] + 1);
|
||||
$configuration = [
|
||||
'high_water_property' => [
|
||||
'name' => 'timestamp',
|
||||
],
|
||||
];
|
||||
$source = $this->getSource($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $this->row['timestamp'] + 1);
|
||||
|
||||
// The current highwater mark is now higher than the row timestamp so no row
|
||||
// is expected.
|
||||
|
@ -240,13 +262,17 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
* @throws \Exception
|
||||
*/
|
||||
public function testNewHighwater() {
|
||||
|
||||
$configuration = [
|
||||
'high_water_property' => [
|
||||
'name' => 'timestamp',
|
||||
],
|
||||
];
|
||||
// Set a highwater property field for source. Now we should have a row
|
||||
// because the row timestamp is greater than the current highwater mark.
|
||||
$source = $this->getSource([], ['highWaterProperty' => ['name' => 'timestamp']], MigrateIdMapInterface::STATUS_IMPORTED);
|
||||
$source = $this->getSource($configuration, [], MigrateIdMapInterface::STATUS_IMPORTED, $this->row['timestamp'] - 1);
|
||||
|
||||
$source->rewind();
|
||||
$this->assertTrue(is_a($source->current(), 'Drupal\migrate\Row'), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
|
||||
$this->assertInstanceOf(Row::class, $source->current(), 'Incoming row timestamp is greater than current highwater mark so we have a row.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -387,6 +413,21 @@ class MigrateSourceTest extends MigrateTestCase {
|
|||
$this->assertFalse($source->prepareRow($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that cacheCounts, skipCount, trackChanges preserve their default
|
||||
* values.
|
||||
*/
|
||||
public function testDefaultPropertiesValues() {
|
||||
$this->migrationConfiguration['id'] = 'test_migration';
|
||||
$migration = $this->getMigration();
|
||||
$source = new StubSourceGeneratorPlugin([], '', [], $migration);
|
||||
|
||||
// Test the default value of the skipCount Value;
|
||||
$this->assertTrue($source->getSkipCount());
|
||||
$this->assertTrue($source->getCacheCounts());
|
||||
$this->assertTrue($source->getTrackChanges());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a mock executable for the test.
|
||||
*
|
||||
|
@ -450,3 +491,61 @@ class StubSourcePlugin extends SourcePluginBase {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stubbed source plugin with a generator as iterator. Also it overwrites the
|
||||
* $skipCount, $cacheCounts and $trackChanges properties.
|
||||
*/
|
||||
class StubSourceGeneratorPlugin extends StubSourcePlugin {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $skipCount = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $cacheCounts = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $trackChanges = TRUE;
|
||||
|
||||
/**
|
||||
* Return the skipCount value.
|
||||
*/
|
||||
public function getSkipCount() {
|
||||
return $this->skipCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the cacheCounts value.
|
||||
*/
|
||||
public function getCacheCounts() {
|
||||
return $this->cacheCounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the trackChanges value.
|
||||
*/
|
||||
public function getTrackChanges() {
|
||||
return $this->trackChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function initializeIterator() {
|
||||
$data = [
|
||||
['title' => 'foo'],
|
||||
['title' => 'bar'],
|
||||
['title' => 'iggy'],
|
||||
];
|
||||
foreach ($data as $row) {
|
||||
yield $row;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,9 +3,16 @@
|
|||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Core\Database\Query\SelectInterface;
|
||||
use Drupal\Core\DependencyInjection\ContainerBuilder;
|
||||
use Drupal\Core\DependencyInjection\ContainerNotInitializedException;
|
||||
use Drupal\Core\KeyValueStore\KeyValueFactoryInterface;
|
||||
use Drupal\Core\KeyValueStore\KeyValueStoreInterface;
|
||||
|
||||
/**
|
||||
* Base class for Migrate module source unit tests.
|
||||
*
|
||||
* @deprecated in Drupal 8.2.0, will be removed before Drupal 9.0.0. Use
|
||||
* \Drupal\Tests\migrate\Kernel\MigrateSqlSourceTestBase instead.
|
||||
*/
|
||||
abstract class MigrateSqlSourceTestCase extends MigrateTestCase {
|
||||
|
||||
|
@ -44,9 +51,9 @@ abstract class MigrateSqlSourceTestCase extends MigrateTestCase {
|
|||
* Once the migration is run, we save a mark of the migrated sources, so the
|
||||
* migration can run again and update only new sources or changed sources.
|
||||
*
|
||||
* @var string
|
||||
* @var mixed
|
||||
*/
|
||||
const ORIGINAL_HIGH_WATER = '';
|
||||
const ORIGINAL_HIGH_WATER = NULL;
|
||||
|
||||
/**
|
||||
* Expected results after the source parsing.
|
||||
|
@ -77,6 +84,27 @@ abstract class MigrateSqlSourceTestCase extends MigrateTestCase {
|
|||
$state = $this->getMock('Drupal\Core\State\StateInterface');
|
||||
$entity_manager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
|
||||
|
||||
// Mock a key-value store to return high-water values.
|
||||
$key_value = $this->getMock(KeyValueStoreInterface::class);
|
||||
|
||||
// SourcePluginBase does not yet support full dependency injection so we
|
||||
// need to make sure that \Drupal::keyValue() works as expected by mocking
|
||||
// the keyvalue service.
|
||||
$key_value_factory = $this->getMock(KeyValueFactoryInterface::class);
|
||||
$key_value_factory
|
||||
->method('get')
|
||||
->with('migrate:high_water')
|
||||
->willReturn($key_value);
|
||||
|
||||
try {
|
||||
$container = \Drupal::getContainer();
|
||||
}
|
||||
catch (ContainerNotInitializedException $e) {
|
||||
$container = new ContainerBuilder();
|
||||
}
|
||||
$container->set('keyvalue', $key_value_factory);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$migration = $this->getMigration();
|
||||
$migration->expects($this->any())
|
||||
->method('getHighWater')
|
||||
|
|
|
@ -80,7 +80,7 @@ abstract class MigrateTestCase extends UnitTestCase {
|
|||
|
||||
$migration->method('getHighWaterProperty')
|
||||
->willReturnCallback(function () use ($configuration) {
|
||||
return isset($configuration['highWaterProperty']) ? $configuration['highWaterProperty'] : '';
|
||||
return isset($configuration['high_water_property']) ? $configuration['high_water_property'] : '';
|
||||
});
|
||||
|
||||
$migration->method('set')
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\MigrationStorageTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit;
|
||||
|
||||
use Drupal\Component\Uuid\UuidInterface;
|
||||
use Drupal\Core\Config\ConfigFactoryInterface;
|
||||
use Drupal\Core\Entity\EntityTypeInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactoryInterface;
|
||||
use Drupal\Core\Entity\Query\QueryInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Drupal\migrate\MigrationStorage;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\MigrationStorage
|
||||
* @group migrate
|
||||
*/
|
||||
class MigrationStorageTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The migration storage.
|
||||
*
|
||||
* @var \Drupal\Tests\migrate\Unit\TestMigrationStorage
|
||||
*/
|
||||
protected $storage;
|
||||
|
||||
/**
|
||||
* The storage query.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $query;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp() {
|
||||
$this->query = $this->getMock(QueryInterface::class);
|
||||
$this->query->method('condition')
|
||||
->willReturnSelf();
|
||||
|
||||
$query_factory = $this->getMock(QueryFactoryInterface::class);
|
||||
$query_factory->method('get')
|
||||
->willReturn($this->query);
|
||||
|
||||
$this->storage = new TestMigrationStorage(
|
||||
$this->getMock(EntityTypeInterface::class),
|
||||
$this->getMock(ConfigFactoryInterface::class),
|
||||
$this->getMock(UuidInterface::class),
|
||||
$this->getMock(LanguageManagerInterface::class),
|
||||
$query_factory
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getVariantIds() when variants exist.
|
||||
*
|
||||
* @covers ::getVariantIds
|
||||
*/
|
||||
public function testGetVariantIdsWithVariants() {
|
||||
$this->query->method('execute')
|
||||
->willReturn(['d6_node__page', 'd6_node__article']);
|
||||
|
||||
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_user']);
|
||||
$this->assertSame(['d6_node__page', 'd6_node__article', 'd6_user'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getVariantIds() when no variants exist.
|
||||
*
|
||||
* @covers ::getVariantIds
|
||||
*/
|
||||
public function testGetVariantIdsNoVariants() {
|
||||
$this->query->method('execute')
|
||||
->willReturn([]);
|
||||
|
||||
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_user']);
|
||||
$this->assertSame(['d6_user'], $ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests getVariantIds().
|
||||
*
|
||||
* This tests getVariantIds() when no variants exist and there are no static
|
||||
* (non-variant) dependencies.
|
||||
*
|
||||
* @covers ::getVariantIds
|
||||
*/
|
||||
public function testGetVariantIdsNoVariantsOrStaticDependencies() {
|
||||
$this->query->method('execute')
|
||||
->willReturn([]);
|
||||
|
||||
$ids = $this->storage->getVariantIds(['d6_node:*', 'd6_node_revision:*']);
|
||||
$this->assertSame([], $ids);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Test version of \Drupal\migrate\MigrationStorage.
|
||||
*
|
||||
* This class exposes protected methods for testing.
|
||||
*/
|
||||
class TestMigrationStorage extends MigrationStorage {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getVariantIds(array $ids) {
|
||||
return parent::getVariantIds($ids);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Tests\migrate\Unit\process\GetTest.
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
<?php
|
||||
|
||||
namespace Drupal\Tests\migrate\Unit\process;
|
||||
|
||||
use Drupal\migrate\Plugin\migrate\process\UrlEncode;
|
||||
use Drupal\migrate\MigrateExecutable;
|
||||
use Drupal\migrate\MigrateMessage;
|
||||
use Drupal\migrate\Row;
|
||||
use Drupal\Tests\migrate\Unit\MigrateTestCase;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\migrate\Plugin\migrate\process\UrlEncode
|
||||
* @group file
|
||||
*/
|
||||
class UrlEncodeTest extends MigrateTestCase {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $migrationConfiguration = [
|
||||
'id' => 'test',
|
||||
];
|
||||
|
||||
/**
|
||||
* The data provider for testing URL encoding scenarios.
|
||||
*
|
||||
* @return array
|
||||
* An array of URLs to test.
|
||||
*/
|
||||
public function urlDataProvider() {
|
||||
return array(
|
||||
'A URL with no characters requiring encoding' => array('http://example.com/normal_url.html', 'http://example.com/normal_url.html'),
|
||||
'The definitive use case - encoding spaces in URLs' => array('http://example.com/url with spaces.html', 'http://example.com/url%20with%20spaces.html'),
|
||||
'Definitive use case 2 - spaces in directories' => array('http://example.com/dir with spaces/foo.html', 'http://example.com/dir%20with%20spaces/foo.html'),
|
||||
'Local filespecs without spaces should not be transformed' => array('/tmp/normal.txt', '/tmp/normal.txt'),
|
||||
'Local filespecs with spaces should not be transformed' => array('/tmp/with spaces.txt', '/tmp/with spaces.txt'),
|
||||
'Make sure URL characters (:, ?, &) are not encoded but others are.' => array('https://example.com/?a=b@c&d=e+f%', 'https://example.com/?a%3Db%40c&d%3De%2Bf%25'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cover various encoding scenarios.
|
||||
* @dataProvider urlDataProvider
|
||||
*/
|
||||
public function testUrls($input, $output) {
|
||||
$this->assertEquals($output, $this->doTransform($input));
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the urlencode process plugin over the given value.
|
||||
*
|
||||
* @param string $value
|
||||
* URL to be encoded.
|
||||
*
|
||||
* @return string
|
||||
* Encoded URL.
|
||||
*/
|
||||
protected function doTransform($value) {
|
||||
$executable = new MigrateExecutable($this->getMigration(), new MigrateMessage());
|
||||
$row = new Row([], []);
|
||||
|
||||
return (new UrlEncode([], 'urlencode', []))
|
||||
->transform($value, $executable, $row, 'foobaz');
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue