Skip to content
......@@ -193,12 +193,7 @@ public function submitForm(array &$form, array &$form_state) {
$form_state['redirect'] = $next_destination;
}
else {
$form_state['redirect_route'] = array(
'route_name' => 'field_ui.overview_' . $this->instance->entity_type,
'route_parameters' => array(
'bundle' => $this->instance->bundle,
)
);
$form_state['redirect_route'] = $this->entityManager->getAdminRouteInfo($this->instance->entity_type, $this->instance->bundle);
}
}
......@@ -212,10 +207,11 @@ public function delete(array &$form, array &$form_state) {
$destination = drupal_get_destination();
$request->query->remove('destination');
}
$entity_info = $this->entityManager->getDefinition($this->instance->entity_type);
$form_state['redirect_route'] = array(
'route_name' => 'field_ui.delete_' . $this->instance->entity_type,
'route_parameters' => array(
'bundle' => $this->instance->bundle,
$entity_info['bundle_entity_type'] => $this->instance->bundle,
'field_instance' => $this->instance->id(),
),
'options' => array(
......
......@@ -35,6 +35,17 @@ public function getFormId() {
return 'field_ui_form_display_overview_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, $entity_type = NULL, $bundle = NULL) {
if ($this->getRequest()->attributes->has('form_mode_name')) {
$this->mode = $this->getRequest()->attributes->get('form_mode_name');
}
return parent::buildForm($form, $form_state, $entity_type, $bundle);
}
/**
* {@inheritdoc}
*/
......
......@@ -74,10 +74,16 @@ public static function create(ContainerInterface $container) {
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, $entity_type = NULL, $bundle = NULL) {
$entity_info = $this->entityManager->getDefinition($entity_type);
if (!isset($form_state['bundle'])) {
if (!$bundle) {
$entity_info = $this->entityManager->getDefinition($entity_type);
$bundle = $this->getRequest()->attributes->get('_raw_variables')->get($entity_info['bundle_entity_type']);
}
$form_state['bundle'] = $bundle;
}
$this->entity_type = $entity_type;
$this->bundle = $bundle;
$this->bundle = $form_state['bundle'];
$this->adminPath = $this->entityManager->getAdminPath($this->entity_type, $this->bundle);
// When displaying the form, make sure the list of fields is up-to-date.
......
<?php
/**
* @file
* Contains \Drupal\field_ui\Plugin\Derivative\FieldUiLocalTask.
*/
namespace Drupal\field_ui\Plugin\Derivative;
use Drupal\Component\Plugin\Derivative\DerivativeBase;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Plugin\Discovery\ContainerDerivativeInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides local task definitions for all entity bundles.
*/
class FieldUiLocalTask extends DerivativeBase implements ContainerDerivativeInterface {
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* The entity manager
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The translation manager service.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* Creates an FieldUiLocalTask object.
*
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
* The entity manager.
* @param \Drupal\Core\StringTranslation\TranslationInterface $translation_manager
* The translation manager.
*/
public function __construct(RouteProviderInterface $route_provider, EntityManagerInterface $entity_manager, TranslationInterface $translation_manager) {
$this->routeProvider = $route_provider;
$this->entityManager = $entity_manager;
$this->translationManager = $translation_manager;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, $base_plugin_id) {
return new static(
$container->get('router.route_provider'),
$container->get('entity.manager'),
$container->get('string_translation')
);
}
/**
* {@inheritdoc}
*/
public function getDerivativeDefinitions(array $base_plugin_definition) {
$this->derivatives = array();
foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
$this->derivatives["overview_$entity_type"] = array(
'route_name' => "field_ui.overview_$entity_type",
'weight' => 1,
'title' => $this->t('Manage fields'),
'tab_root_id' => "field_ui.fields:overview_$entity_type",
);
// 'Manage form display' tab.
$this->derivatives["form_display_overview_$entity_type"] = array(
'route_name' => "field_ui.form_display_overview_$entity_type",
'weight' => 2,
'title' => $this->t('Manage form display'),
'tab_root_id' => "field_ui.fields:overview_$entity_type",
);
// 'Manage display' tab.
$this->derivatives["display_overview_$entity_type"] = array(
'route_name' => "field_ui.display_overview_$entity_type",
'weight' => 3,
'title' => $this->t('Manage display'),
'tab_root_id' => "field_ui.fields:overview_$entity_type",
);
// Field instance edit tab.
$this->derivatives["instance_edit_$entity_type"] = array(
'route_name' => "field_ui.instance_edit_$entity_type",
'title' => $this->t('Edit'),
'tab_root_id' => "field_ui.fields:instance_edit_$entity_type",
);
// Field settings tab.
$this->derivatives["field_edit_$entity_type"] = array(
'route_name' => "field_ui.field_edit_$entity_type",
'title' => $this->t('Field settings'),
'tab_root_id' => "field_ui.fields:instance_edit_$entity_type",
);
// View and form modes secondary tabs.
// The same base $path for the menu item (with a placeholder) can be
// used for all bundles of a given entity type; but depending on
// administrator settings, each bundle has a different set of view
// modes available for customisation. So we define menu items for all
// view modes, and use a route requirement to determine which ones are
// actually visible for a given bundle.
$this->derivatives['field_form_display_default_' . $entity_type] = array(
'title' => 'Default',
'route_name' => "field_ui.form_display_overview_$entity_type",
'tab_root_id' => "field_ui.fields:overview_$entity_type",
'tab_parent_id' => "field_ui.fields:form_display_overview_$entity_type",
);
$this->derivatives['field_display_default_' . $entity_type] = array(
'title' => 'Default',
'route_name' => "field_ui.display_overview_$entity_type",
'tab_root_id' => "field_ui.fields:overview_$entity_type",
'tab_parent_id' => "field_ui.fields:display_overview_$entity_type",
);
// One local task for each form mode.
$weight = 0;
foreach (entity_get_form_modes($entity_type) as $form_mode => $form_mode_info) {
$this->derivatives['field_form_display_' . $form_mode . '_' . $entity_type] = array(
'title' => $form_mode_info['label'],
'route_name' => "field_ui.form_display_overview_form_mode_$entity_type",
'route_parameters' => array(
'form_mode_name' => $form_mode,
),
'tab_root_id' => "field_ui.fields:overview_$entity_type",
'tab_parent_id' => "field_ui.fields:form_display_overview_$entity_type",
'weight' => $weight++,
);
}
// One local task for each view mode.
$weight = 0;
foreach (entity_get_view_modes($entity_type) as $view_mode => $form_mode_info) {
$this->derivatives['field_display_' . $view_mode . '_' . $entity_type] = array(
'title' => $form_mode_info['label'],
'route_name' => "field_ui.display_overview_view_mode_$entity_type",
'route_parameters' => array(
'view_mode_name' => $view_mode,
),
'tab_root_id' => "field_ui.fields:overview_$entity_type",
'tab_parent_id' => "field_ui.fields:display_overview_$entity_type",
'weight' => $weight++,
);
}
}
}
foreach ($this->derivatives as &$entry) {
$entry += $base_plugin_definition;
}
return $this->derivatives;
}
/**
* Alters the tab_root_id definition for field_ui local tasks.
*
* @param array $local_tasks
* An array of local tasks plugin definitions, keyed by plugin ID.
*/
public function alterLocalTasks(&$local_tasks) {
foreach ($this->entityManager->getDefinitions() as $entity_type => $entity_info) {
if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
if ($parent_task = $this->getPluginIdFromRoute($entity_info['links']['admin-form'], $local_tasks)) {
$local_tasks["field_ui.fields:overview_$entity_type"]['tab_root_id'] = $parent_task;
$local_tasks["field_ui.fields:form_display_overview_$entity_type"]['tab_root_id'] = $parent_task;
$local_tasks["field_ui.fields:display_overview_$entity_type"]['tab_root_id'] = $parent_task;
$local_tasks["field_ui.fields:field_form_display_default_$entity_type"]['tab_root_id'] = $parent_task;
$local_tasks["field_ui.fields:field_display_default_$entity_type"]['tab_root_id'] = $parent_task;
foreach (entity_get_form_modes($entity_type) as $form_mode => $form_mode_info) {
$local_tasks['field_ui.fields:field_form_display_' . $form_mode . '_' . $entity_type]['tab_root_id'] = $parent_task;
}
foreach (entity_get_view_modes($entity_type) as $view_mode => $form_mode_info) {
$local_tasks['field_ui.fields:field_display_' . $view_mode . '_' . $entity_type]['tab_root_id'] = $parent_task;
}
}
}
}
}
/**
* Finds the local task ID of a route given the route name.
*
* @param string $route_name
* The route name.
* @param array $local_tasks
* An array of all local task definitions.
*
* @return string|null
* Returns the local task ID of the given route or NULL if none is found.
*/
protected function getPluginIdFromRoute($route_name, &$local_tasks) {
$local_task_id = NULL;
foreach ($local_tasks as $plugin_id => $local_task) {
if ($local_task['route_name'] == $route_name) {
$local_task_id = $plugin_id;
break;
}
}
return $local_task_id;
}
/**
* Translates a string to the current language or to a given language.
*
* See the t() documentation for details.
*/
protected function t($string, array $args = array(), array $options = array()) {
return $this->translationManager->translate($string, $args, $options);
}
}
......@@ -8,7 +8,10 @@
namespace Drupal\field_ui\Routing;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\Routing\RouteSubscriberBase;
use Drupal\Core\Routing\RoutingEvents;
use Symfony\Component\Routing\Exception\RouteNotFoundException;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
......@@ -24,14 +27,24 @@ class RouteSubscriber extends RouteSubscriberBase {
*/
protected $manager;
/**
* The route provider.
*
* @var \Drupal\Core\Routing\RouteProviderInterface
*/
protected $routeProvider;
/**
* Constructs a RouteSubscriber object.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $manager
* The entity type manager.
* @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
* The route provider.
*/
public function __construct(EntityManagerInterface $manager) {
public function __construct(EntityManagerInterface $manager, RouteProviderInterface $route_provider) {
$this->manager = $manager;
$this->routeProvider = $route_provider;
}
/**
......@@ -40,8 +53,20 @@ public function __construct(EntityManagerInterface $manager) {
protected function routes(RouteCollection $collection) {
foreach ($this->manager->getDefinitions() as $entity_type => $entity_info) {
$defaults = array();
if ($entity_info['fieldable'] && isset($entity_info['route_base_path'])) {
$path = $entity_info['route_base_path'];
if ($entity_info['fieldable'] && isset($entity_info['links']['admin-form'])) {
// First try to get the route from the dynamic_routes collection.
if (!$entity_route = $collection->get($entity_info['links']['admin-form'])) {
// Then try to get the route from the route provider itself, checking
// all previous collections.
try {
$entity_route = $this->routeProvider->getRouteByName($entity_info['links']['admin-form']);
}
// If the route was not found, skip this entity type.
catch (RouteNotFoundException $e) {
continue;
}
}
$path = $entity_route->getPath();
$route = new Route(
"$path/fields/{field_instance}",
......@@ -85,20 +110,19 @@ protected function routes(RouteCollection $collection) {
'_form' => '\Drupal\field_ui\FormDisplayOverview',
'_title' => 'Manage form display',
) + $defaults,
array('_permission' => 'administer ' . $entity_type . ' form display')
array('_field_ui_form_mode_access' => 'administer ' . $entity_type . ' form display')
);
$collection->add("field_ui.form_display_overview_$entity_type", $route);
foreach (entity_get_form_modes($entity_type) as $form_mode => $form_mode_info) {
$route = new Route(
"$path/form-display/$form_mode",
array(
'_form' => '\Drupal\field_ui\FormDisplayOverview',
'mode' => $form_mode,
) + $defaults,
array('_field_ui_form_mode_access' => 'administer ' . $entity_type . ' form display'));
$collection->add("field_ui.form_display_overview_$entity_type" . '_'. $form_mode, $route);
}
$route = new Route(
"$path/form-display/{form_mode_name}",
array(
'_form' => '\Drupal\field_ui\FormDisplayOverview',
'form_mode_name' => NULL,
) + $defaults,
array('_field_ui_form_mode_access' => 'administer ' . $entity_type . ' form display')
);
$collection->add("field_ui.form_display_overview_form_mode_$entity_type", $route);
$route = new Route(
"$path/display",
......@@ -106,22 +130,30 @@ protected function routes(RouteCollection $collection) {
'_form' => '\Drupal\field_ui\DisplayOverview',
'_title' => 'Manage display',
) + $defaults,
array('_permission' => 'administer ' . $entity_type . ' display')
array('_field_ui_view_mode_access' => 'administer ' . $entity_type . ' display')
);
$collection->add("field_ui.display_overview_$entity_type", $route);
foreach (entity_get_view_modes($entity_type) as $view_mode => $view_mode_info) {
$route = new Route(
"$path/display/$view_mode",
array(
'_form' => '\Drupal\field_ui\DisplayOverview',
'mode' => $view_mode,
) + $defaults,
array('_field_ui_view_mode_access' => 'administer ' . $entity_type . ' display'));
$collection->add("field_ui.display_overview_$entity_type" . '_' . $view_mode, $route);
}
$route = new Route(
"$path/display/{view_mode_name}",
array(
'_form' => '\Drupal\field_ui\DisplayOverview',
'view_mode_name' => NULL,
) + $defaults,
array('_field_ui_view_mode_access' => 'administer ' . $entity_type . ' display')
);
$collection->add("field_ui.display_overview_view_mode_$entity_type", $route);
}
}
}
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
$events[RoutingEvents::DYNAMIC] = array('onDynamicRoutes', -100);
return $events;
}
}
......@@ -20,8 +20,7 @@
* controllers = {
* "storage" = "Drupal\Core\Entity\DatabaseStorageController"
* },
* fieldable = TRUE,
* route_base_path = "field-ui-test-no-bundle/manage"
* fieldable = TRUE
* )
*/
class FieldUITestNoBundle extends Entity {
......
......@@ -37,6 +37,21 @@ function file_help($path, $arg) {
}
}
/**
* Implements hook_menu().
*/
function file_menu() {
$items = array();
$items['file/ajax'] = array(
'route_name' => 'file.ajax_upload',
'theme callback' => 'ajax_base_page_theme',
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_element_info().
*
......
......@@ -2,8 +2,6 @@ file.ajax_upload:
path: '/file/ajax'
defaults:
_controller: '\Drupal\file\Controller\FileWidgetAjaxController::upload'
options:
_theme: ajax_base_page
requirements:
_permission: 'access content'
......
......@@ -2,9 +2,7 @@
/**
* @file
* Provide views data and handlers for file.module.
*
* @ingroup views_module_handlers
* Provide views data for file.module.
*/
use Drupal\Core\Entity\FieldableDatabaseStorageController;
......
......@@ -2,9 +2,7 @@
/**
* @file
* Provide views data and handlers for forum.module.
*
* @ingroup views_module_handlers
* Provide views data for forum.module.
*/
/**
......
......@@ -2,9 +2,7 @@
/**
* @file
* Provide views data and handlers for history.module.
*
* @ingroup views_module_handlers
* Provide views data for history.module.
*/
/**
......
......@@ -2,9 +2,7 @@
/**
* @file
* Provide views data and handlers for image.module.
*
* @ingroup views_module_handlers
* Provide views data for image.module.
*/
use Drupal\Core\Entity\FieldableDatabaseStorageController;
......
id: d6_system_cron
source:
plugin: drupal6_variable
variables:
- cron_threshold_warning
- cron_threshold_error
- cron_last
process:
'threshold:warning': cron_threshold_warning
'threshold:error': cron_threshold_error
destination:
plugin: d8_config
config_name: system.cron
id: d6_system_rss
source:
plugin: drupal6_variable
variables:
- feed_default_items
process:
'items:limit': feed_default_items
destination:
plugin: d8_config
config_name: system.rss
id: d6_system_site
source:
plugin: drupal6_variable
variables:
- site_name
- site_mail
- site_slogan
- site_frontpage
- site_403
- site_404
- drupal_weight_select_max
- admin_compact_mode
process:
name: site_name
mail: site_mail
slogan: site_slogan
'page:front': site_frontpage
'page:403': site_403
'page:404': site_404
weight_select_max: drupal_weight_select_max
admin_compact_mode: admin_compact_mode
destination:
plugin: d8_config
config_name: system.site
<?php
/**
* @file
* Contains \Drupal\migrate\Entity\Migration.
*/
namespace Drupal\migrate\Entity;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\migrate\MigrateException;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Defines the Migration entity.
*
* The migration entity stores the information about a single migration, like
* the source, process and destination plugins.
*
* @EntityType(
* id = "migration",
* label = @Translation("Migration"),
* module = "migrate",
* controllers = {
* "storage" = "Drupal\Core\Config\Entity\ConfigStorageController",
* "list" = "Drupal\Core\Config\Entity\DraggableListController",
* "access" = "Drupal\Core\Entity\EntityAccessController",
* "form" = {
* "add" = "Drupal\Core\Entity\EntityFormController",
* "edit" = "Drupal\Core\Entity\EntityFormController",
* "delete" = "Drupal\Core\Entity\EntityFormController"
* }
* },
* config_prefix = "migrate.migration",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* "weight" = "weight",
* "uuid" = "uuid"
* }
* )
*/
class Migration extends ConfigEntityBase implements MigrationInterface {
/**
* The migration ID (machine name).
*
* @var string
*/
public $id;
/**
* The migration UUID.
*
* This is assigned automatically when the migration is created.
*
* @var string
*/
public $uuid;
/**
* The human-readable label for the migration.
*
* @var string
*/
public $label;
/**
* The plugin ID for the row.
*
* @var string
*/
public $row;
/**
* The source configuration, with at least a 'plugin' key.
*
* Used to initialize the $sourcePlugin.
*
* @var array
*/
public $source;
/**
* The source plugin.
*
* @var \Drupal\migrate\Plugin\MigrateSourceInterface
*/
protected $sourcePlugin;
/**
* The configuration describing the process plugins.
*
* @var array
*/
public $process;
/**
* The destination configuration, with at least a 'plugin' key.
*
* Used to initialize $destinationPlugin.
*
* @var array
*/
public $destination;
/**
* The destination plugin.
*
* @var \Drupal\migrate\Plugin\MigrateDestinationInterface
*/
protected $destinationPlugin;
/**
* The identifier map data.
*
* Used to initialize $idMapPlugin.
*
* @var string
*/
public $idMap = array();
/**
* The identifier map.
*
* @var \Drupal\migrate\Plugin\MigrateIdMapInterface
*/
protected $idMapPlugin;
/**
* The source identifiers.
*
* An array of source identifiers: the keys are the name of the properties,
* the values are dependent on the ID map plugin.
*
* @var array
*/
public $sourceIds = array();
/**
* The destination identifiers.
*
* An array of destination identifiers: the keys are the name of the
* properties, the values are dependent on the ID map plugin.
*
* @var array
*/
public $destinationIds = array();
/**
* Information on the highwater mark.
*
* @var array
*/
public $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
*/
public $systemOfRecord = self::SOURCE;
/**
* Specify value of needs_update for current map row. Usually set by
* MigrateFieldHandler implementations.
*
* @var int
*/
public $needsUpdate = MigrateIdMapInterface::STATUS_IMPORTED;
/**
* @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected $highwaterStorage;
/**
* @var bool
*/
public $trackLastImported = FALSE;
/**
* {@inheritdoc}
*/
public function getSourcePlugin() {
if (!isset($this->sourcePlugin)) {
$this->sourcePlugin = \Drupal::service('plugin.manager.migrate.source')->createInstance($this->source['plugin'], $this->source, $this);
}
return $this->sourcePlugin;
}
/**
* {@inheritdoc}
*/
public function getProcessPlugins(array $process = NULL) {
if (!isset($process)) {
$process = $this->process;
}
$process_plugins = array();
foreach ($this->getProcessNormalized($process) as $property => $configurations) {
$process_plugins[$property] = array();
foreach ($configurations as $configuration) {
if (isset($configuration['source'])) {
$process_plugins[$property][] = \Drupal::service('plugin.manager.migrate.process')->createInstance('get', $configuration, $this);
}
// Get is already handled.
if ($configuration['plugin'] != 'get') {
$process_plugins[$property][] = \Drupal::service('plugin.manager.migrate.process')->createInstance($configuration['plugin'], $configuration, $this);
}
if (!$process_plugins[$property]) {
throw new MigrateException("Invalid process configuration for $property");
}
}
}
return $process_plugins;
}
/**
* Resolve shorthands into a list of plugin configurations.
*
* @param array $process
* A process configuration array.
*
* @return array
* The normalized process configuration.
*/
protected function getProcessNormalized(array $process) {
$normalized_configurations = array();
foreach ($process as $destination => $configuration) {
if (is_string($configuration)) {
$configuration = array(
'plugin' => 'get',
'source' => $configuration,
);
}
if (isset($configuration['plugin'])) {
$configuration = array($configuration);
}
$normalized_configurations[$destination] = $configuration;
}
return $normalized_configurations;
}
/**
* {@inheritdoc}
*/
public function getDestinationPlugin() {
if (!isset($this->destinationPlugin)) {
$this->destinationPlugin = \Drupal::service('plugin.manager.migrate.destination')->createInstance($this->destination['plugin'], $this->destination, $this);
}
return $this->destinationPlugin;
}
/**
* {@inheritdoc}
*/
public function getIdMap() {
if (!isset($this->idMapPlugin)) {
$configuration = $this->idMap;
$plugin = isset($configuration['plugin']) ? $configuration['plugin'] : 'sql';
$this->idMapPlugin = \Drupal::service('plugin.manager.migrate.id_map')->createInstance($plugin, $configuration, $this);
}
return $this->idMapPlugin;
}
/**
* @return \Drupal\Core\KeyValueStore\KeyValueStoreInterface
*/
protected function getHighWaterStorage() {
if (!isset($this->highwaterStorage)) {
$this->highwaterStorage = \Drupal::keyValue('migrate:highwater');
}
return $this->highwaterStorage;
}
public function getHighwater() {
return $this->getHighWaterStorage()->get($this->id());
}
public function saveHighwater($highwater) {
$this->getHighWaterStorage()->set($this->id(), $highwater);
}
}
<?php
/**
* @file
* Contains \Drupal\migrate\Entity\MigrationInterface.
*/
namespace Drupal\migrate\Entity;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
/**
* Interface for migrations.
*/
interface MigrationInterface extends ConfigEntityInterface {
const SOURCE = 'source';
const DESTINATION = 'destination';
/**
* Codes representing the current status of a migration, and stored in the
* migrate_status table.
*/
const STATUS_IDLE = 0;
const STATUS_IMPORTING = 1;
const STATUS_ROLLING_BACK = 2;
const STATUS_STOPPING = 3;
const STATUS_DISABLED = 4;
/**
* Message types to be passed to saveMessage() and saved in message tables.
* MESSAGE_INFORMATIONAL represents a condition that did not prevent the
* operation from succeeding - all others represent different severities of
* conditions resulting in a source record not being imported.
*/
const MESSAGE_ERROR = 1;
const MESSAGE_WARNING = 2;
const MESSAGE_NOTICE = 3;
const MESSAGE_INFORMATIONAL = 4;
/**
* Codes representing the result of a rollback or import process.
*/
const RESULT_COMPLETED = 1; // All records have been processed
const RESULT_INCOMPLETE = 2; // The process has interrupted itself (e.g., the
// memory limit is approaching)
const RESULT_STOPPED = 3; // The process was stopped externally (e.g., via
// drush migrate-stop)
const RESULT_FAILED = 4; // The process had a fatal error
const RESULT_SKIPPED = 5; // Dependencies are unfulfilled - skip the process
const RESULT_DISABLED = 6; // This migration is disabled, skipping
/**
* Returns the initialized source plugin.
*
* @return \Drupal\migrate\Plugin\MigrateSourceInterface
*/
public function getSourcePlugin();
/**
* Returns the process plugins.
*
* @param array $process
* A process configuration array.
*
* @return array
* A list of process plugins.
*/
public function getProcessPlugins(array $process = NULL);
/**
* Returns the initialized destination plugin.
*
* @return \Drupal\migrate\Plugin\MigrateDestinationInterface
*/
public function getDestinationPlugin();
/**
* Returns the initialized id_map plugin.
*
* @return \Drupal\migrate\Plugin\MigrateIdMapInterface
*/
public function getIdMap();
/**
* @return int
*/
public function getHighwater();
public function saveHighwater($highwater);
}
<?php
/**
* @file
* Contains \Drupal\migrate\MigrateException.
*/
namespace Drupal\migrate;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Defines the migrate exception class.
*/
class MigrateException extends \Exception {
/**
* The level of the error being reported.
*
* The value is a Migration::MESSAGE_* constant.
*
* @var int
*/
protected $level;
/**
* The status to record in the map table for the current item.
*
* The value is a MigrateMap::STATUS_* constant.
*
* @var int
*/
protected $status;
/**
* Constructs a MigrateException object.
*
* @param string $message
* The message for the exception.
* @param int $code
* The Exception code.
* @param \Exception $previous
* The previous exception used for the exception chaining.
* @param int $level
* The level of the error, a Migration::MESSAGE_* constant.
* @param int $status
* The status of the item for the map table, a MigrateMap::STATUS_*
* constant.
*/
public function __construct($message = null, $code = 0, \Exception $previous = null, $level = MigrationInterface::MESSAGE_ERROR, $status = MigrateIdMapInterface::STATUS_FAILED) {
$this->level = $level;
$this->status = $status;
parent::__construct($message);
}
/**
* Gets the level.
*
* @return int
*/
public function getLevel() {
return $this->level;
}
/**
* Gets the status of the current item.
*
* @return int
*/
public function getStatus() {
return $this->status;
}
}
<?php
/**
* @file
* Contains \Drupal\migrate\MigrateExecutable.
*/
namespace Drupal\migrate;
use Drupal\migrate\Entity\MigrationInterface;
use Drupal\migrate\Plugin\MigrateIdMapInterface;
/**
* Defines a migrate executable class.
*/
class MigrateExecutable {
/**
* The migration to do.
*
* @var \Drupal\migrate\Entity\MigrationInterface
*/
protected $migration;
/**
* The number of successfully imported rows since feedback was given.
*
* @var int
*/
protected $successes_since_feedback;
/**
* The number of rows that were successfully processed.
*
* @var int
*/
protected $total_successes;
/**
* Status of one row.
*
* The value is a MigrateIdMapInterface::STATUS_* constant, for example:
* STATUS_IMPORTED.
*
* @var int
*/
protected $needsUpdate;
/**
* The number of rows processed.
*
* The total attempted, whether or not they were successful.
*
* @var int
*/
protected $total_processed;
/**
* The queued messages not yet saved.
*
* Each element in the array is an array with two keys:
* - 'message': The message string.
* - 'level': The level, a MigrationInterface::MESSAGE_* constant.
*
* @var array
*/
protected $queuedMessages = array();
/**
* The options that can be set when executing the migration.
*
* Values can be set for:
* - 'limit': Sets a time limit.
*
* @var array
*/
protected $options;
/**
* The fraction of the memory limit at which an operation will be interrupted.
* Can be overridden by a Migration subclass if one would like to push the
* envelope. Defaults to 85%.
*
* @var float
*/
protected $memoryThreshold = 0.85;
/**
* The PHP memory_limit expressed in bytes.
*
* @var int
*/
protected $memoryLimit;
/**
* The fraction of the time limit at which an operation will be interrupted.
* Can be overridden by a Migration subclass if one would like to push the
* envelope. Defaults to 90%.
*
* @var float
*/
protected $timeThreshold = 0.90;
/**
* The PHP max_execution_time.
*
* @var int
*/
protected $timeLimit;
/**
* @var array
*/
protected $sourceIdValues;
/**
* @var int
*/
protected $processed_since_feedback = 0;
/**
* The translation manager.
*
* @var \Drupal\Core\StringTranslation\TranslationInterface
*/
protected $translationManager;
/**
* @param MigrationInterface $migration
* @param MigrateMessageInterface $message
*
* @throws \Drupal\migrate\MigrateException
*/
public function __construct(MigrationInterface $migration, MigrateMessageInterface $message) {
$this->migration = $migration;
$this->message = $message;
$this->migration->getIdMap()->setMessage($message);
// Record the memory limit in bytes
$limit = trim(ini_get('memory_limit'));
if ($limit == '-1') {
$this->memoryLimit = PHP_INT_MAX;
}
else {
if (!is_numeric($limit)) {
$last = strtolower(substr($limit, -1));
switch ($last) {
case 'g':
$limit *= 1024;
case 'm':
$limit *= 1024;
case 'k':
$limit *= 1024;
break;
default:
throw new MigrateException($this->t('Invalid PHP memory_limit !limit',
array('!limit' => $limit)));
}
}
$this->memoryLimit = $limit;
}
}
/**
* @return \Drupal\migrate\Source
*/
public function getSource() {
if (!isset($this->source)) {
$this->source = new Source($this->migration, $this);
}
return $this->source;
}
/**
* The rollback action to be saved for the current row.
*
* @var int
*/
public $rollbackAction;
/**
* An array of counts. Initially used for cache hit/miss tracking.
*
* @var array
*/
protected $counts = array();
/**
* When performing a bulkRollback(), the maximum number of items to pass in
* a single call. Can be overridden in derived class constructor.
*
* @var int
*/
protected $rollbackBatchSize = 50;
/**
* The object currently being constructed
* @var \stdClass
*/
protected $destinationValues;
/**
* The current data row retrieved from the source.
* @var \stdClass
*/
protected $sourceValues;
/**
* Perform an import operation - migrate items from source to destination.
*/
public function import() {
$return = MigrationInterface::RESULT_COMPLETED;
$source = $this->getSource();
$destination = $this->migration->getDestinationPlugin();
$id_map = $this->migration->getIdMap();
try {
$source->rewind();
}
catch (\Exception $e) {
$this->message->display(
$this->t('Migration failed with source plugin exception: !e',
array('!e' => $e->getMessage())));
return MigrationInterface::RESULT_FAILED;
}
while ($source->valid()) {
$row = $source->current();
if ($this->sourceIdValues = $row->getSourceIdValues()) {
// Wipe old messages, and save any new messages.
$id_map->delete($row->getSourceIdValues(), TRUE);
$this->saveQueuedMessages();
}
$this->processRow($row);
try {
$destination_id_values = $destination->import($row);
// @TODO handle the successful but no ID case like config.
if ($destination_id_values) {
$id_map->saveIdMapping($row, $destination_id_values, $this->needsUpdate, $this->rollbackAction);
$this->successes_since_feedback++;
$this->total_successes++;
}
else {
$id_map->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
if ($id_map->messageCount() == 0) {
$message = $this->t('New object was not saved, no error provided');
$this->saveMessage($message);
$this->message->display($message);
}
}
}
catch (MigrateException $e) {
$this->migration->getIdMap()->saveIdMapping($row, array(), $e->getStatus(), $this->rollbackAction);
$this->saveMessage($e->getMessage(), $e->getLevel());
$this->message->display($e->getMessage());
}
catch (\Exception $e) {
$this->migration->getIdMap()->saveIdMapping($row, array(), MigrateIdMapInterface::STATUS_FAILED, $this->rollbackAction);
$this->handleException($e);
}
$this->total_processed++;
$this->processed_since_feedback++;
if ($highwater_property = $this->migration->get('highwaterProperty')) {
$this->migration->saveHighwater($row->getSourceProperty($highwater_property['name']));
}
// Reset row properties.
unset($sourceValues, $destinationValues);
$this->needsUpdate = MigrateIdMapInterface::STATUS_IMPORTED;
// TODO: Temporary. Remove when http://drupal.org/node/375494 is committed.
// TODO: Should be done in MigrateDestinationEntity
if (!empty($destination->entityType)) {
entity_get_controller($destination->entityType)->resetCache();
}
if (($return = $this->checkStatus()) != MigrationInterface::RESULT_COMPLETED) {
break;
}
if ($this->timeOptionExceeded()) {
break;
}
try {
$source->next();
}
catch (\Exception $e) {
$this->message->display(
$this->t('Migration failed with source plugin exception: !e',
array('!e' => $e->getMessage())));
return MigrationInterface::RESULT_FAILED;
}
}
/**
* @TODO uncomment this
*/
#$this->progressMessage($return);
return $return;
}
/**
* @param Row $row
* The $row to be processed.
* @param array $process
* A process pipeline configuration. If not set, the top level process
* configuration in the migration entity is used.
* @param mixed $value
* Optional initial value of the pipeline for the first destination.
* Usually setting this is not necessary as $process typically starts with
* a 'get'. This is useful only when the $process contains a single
* destination and needs to access a value outside of the source. See
* \Drupal\migrate\Plugin\migrate\process\Iterator::transformKey for an
* example.
*/
public function processRow(Row $row, array $process = NULL, $value = NULL) {
foreach ($this->migration->getProcessPlugins($process) as $destination => $plugins) {
foreach ($plugins as $plugin) {
$value = $plugin->transform($value, $this, $row, $destination);
}
$row->setDestinationProperty($destination, $value);
// Reset the value.
$value = NULL;
}
}
/**
* Fetch the key array for the current source record.
*
* @return array
*/
protected function currentSourceIds() {
return $this->getSource()->getCurrentIds();
}
/**
* Test whether we've exceeded the designated time limit.
*
* @return boolean
* TRUE if the threshold is exceeded, FALSE if not.
*/
protected function timeOptionExceeded() {
if (!$time_limit = $this->getTimeLimit()) {
return FALSE;
}
$time_elapsed = time() - REQUEST_TIME;
if ($time_elapsed >= $time_limit) {
return TRUE;
}
else {
return FALSE;
}
}
public function getTimeLimit() {
if (isset($this->options['limit']) &&
($this->options['limit']['unit'] == 'seconds' || $this->options['limit']['unit'] == 'second')) {
return $this->options['limit']['value'];
}
else {
return NULL;
}
}
/**
* Pass messages through to the map class.
*
* @param string $message
* The message to record.
* @param int $level
* Optional message severity (defaults to MESSAGE_ERROR).
*/
public function saveMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
$this->migration->getIdMap()->saveMessage($this->sourceIdValues, $message, $level);
}
/**
* Queue messages to be later saved through the map class.
*
* @param string $message
* The message to record.
* @param int $level
* Optional message severity (defaults to MESSAGE_ERROR).
*/
public function queueMessage($message, $level = MigrationInterface::MESSAGE_ERROR) {
$this->queuedMessages[] = array('message' => $message, 'level' => $level);
}
/**
* Save any messages we've queued up to the message table.
*/
public function saveQueuedMessages() {
foreach ($this->queuedMessages as $queued_message) {
$this->saveMessage($queued_message['message'], $queued_message['level']);
}
$this->queuedMessages = array();
}
/**
* Standard top-of-loop stuff, common between rollback and import - check
* for exceptional conditions, and display feedback.
*/
protected function checkStatus() {
if ($this->memoryExceeded()) {
return MigrationInterface::RESULT_INCOMPLETE;
}
if ($this->timeExceeded()) {
return MigrationInterface::RESULT_INCOMPLETE;
}
/*
* @TODO uncomment this
if ($this->getStatus() == MigrationInterface::STATUS_STOPPING) {
return MigrationBase::RESULT_STOPPED;
}
*/
// If feedback is requested, produce a progress message at the proper time
/*
* @TODO uncomment this
if (isset($this->feedback)) {
if (($this->feedback_unit == 'seconds' && time() - $this->lastfeedback >= $this->feedback) ||
($this->feedback_unit == 'items' && $this->processed_since_feedback >= $this->feedback)) {
$this->progressMessage(MigrationInterface::RESULT_INCOMPLETE);
}
}
*/
return MigrationInterface::RESULT_COMPLETED;
}
/**
* Test whether we've exceeded the desired memory threshold. If so, output a message.
*
* @return boolean
* TRUE if the threshold is exceeded, FALSE if not.
*/
protected function memoryExceeded() {
$usage = memory_get_usage();
$pct_memory = $usage / $this->memoryLimit;
if ($pct_memory > $this->memoryThreshold) {
$this->message->display(
$this->t('Memory usage is !usage (!pct% of limit !limit), resetting statics',
array('!pct' => round($pct_memory*100),
'!usage' => format_size($usage),
'!limit' => format_size($this->memoryLimit))),
'warning');
// First, try resetting Drupal's static storage - this frequently releases
// plenty of memory to continue
drupal_static_reset();
$usage = memory_get_usage();
$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 > (.90 * $this->memoryThreshold)) {
$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' => format_size($usage),
'!limit' => format_size($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' => format_size($usage),
'!limit' => format_size($this->memoryLimit))),
'warning');
return FALSE;
}
}
else {
return FALSE;
}
}
/**
* Test whether we're approaching the PHP time limit.
*
* @return boolean
* TRUE if the threshold is exceeded, FALSE if not.
*/
protected function timeExceeded() {
if ($this->timeLimit == 0) {
return FALSE;
}
$time_elapsed = time() - REQUEST_TIME;
$pct_time = $time_elapsed / $this->timeLimit;
if ($pct_time > $this->timeThreshold) {
return TRUE;
}
else {
return FALSE;
}
}
/**
* Takes an Exception object and both saves and displays it, pulling additional
* information on the location triggering the exception.
*
* @param \Exception $exception
* Object representing the exception.
* @param boolean $save
* Whether to save the message in the migration's mapping table. Set to FALSE
* in contexts where this doesn't make sense.
*/
public function handleException($exception, $save = TRUE) {
$result = _drupal_decode_exception($exception);
$message = $result['!message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
if ($save) {
$this->saveMessage($message);
}
$this->message->display($message);
}
/**
* Translates a string to the current language or to a given language.
*
* See the t() documentation for details.
*/
protected function t($string, array $args = array(), array $options = array()) {
return $this->translationManager()->translate($string, $args, $options);
}
/**
* Gets the translation manager.
*
* @return \Drupal\Core\StringTranslation\TranslationInterface
* The translation manager.
*/
protected function translationManager() {
if (!$this->translationManager) {
$this->translationManager = \Drupal::translation();
}
return $this->translationManager;
}
}
<?php
/**
* @file
* Contains \Drupal\migrate\MigrateMessage.
*/
namespace Drupal\migrate;
/**
* Defines a migrate message class.
*/
class MigrateMessage implements MigrateMessageInterface {
/**
* Displays a migrate message.
*
* @param string $message
* The message to display.
* @param string $type
* The type of message, for example: status or warning.
*/
function display($message, $type = 'status') {
drupal_set_message($message, $type);
}
}