1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015 |
- <?php
- /**
- * @file
- * Defines the base class for migration processes.
- */
- /**
- * The base class for all objects representing distinct steps in a migration
- * process. Most commonly these will be Migration objects which actually import
- * data from a source into a Drupal destination, but by deriving classes directly
- * from MigrationBase one can have other sorts of tasks (e.g., enabling/disabling
- * of modules) occur during the migration process.
- */
- abstract class MigrationBase {
- /**
- * Track the migration currently running, so handlers can easily determine it
- * without having to pass a Migration object everywhere.
- *
- * @var Migration
- */
- protected static $currentMigration;
- public static function currentMigration() {
- return self::$currentMigration;
- }
- /**
- * The machine name of this Migration object, derived by removing the 'Migration'
- * suffix from the class name. Used to construct default map/message table names,
- * displayed in drush migrate-status, key to migrate_status table...
- *
- * @var string
- */
- protected $machineName;
- public function getMachineName() {
- return $this->machineName;
- }
- /**
- * The name of a migration group, used to collect related migrations.
- *
- * @var string
- */
- protected $group;
- public function getGroup() {
- return $this->group;
- }
- /**
- * Detailed information describing the migration.
- *
- * @var string
- */
- protected $description;
- public function getDescription() {
- return $this->description;
- }
- /**
- * Save options passed to current operation
- * @var array
- */
- protected $options;
- public function getOption($option_name) {
- if (isset($this->options[$option_name])) {
- return $this->options[$option_name];
- }
- else {
- return NULL;
- }
- }
- public function getItemLimit() {
- if (isset($this->options['limit']) &&
- ($this->options['limit']['unit'] == 'items' || $this->options['limit']['unit'] == 'item')) {
- return $this->options['limit']['value'];
- }
- else {
- return NULL;
- }
- }
- 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;
- }
- }
- /**
- * Indicates that we are processing a rollback or import - used to avoid
- * excess writes in endProcess()
- *
- * @var boolean
- */
- protected $processing = FALSE;
- /**
- * Are we importing, rolling back, or doing nothing?
- *
- * @var enum
- */
- protected $status = MigrationBase::STATUS_IDLE;
- /**
- * When the current operation started.
- * @var int
- */
- protected $starttime;
- /**
- * Whether to maintain a history of migration processes in migrate_log
- *
- * @var boolean
- */
- protected $logHistory = TRUE;
- /**
- * Primary key of the current history record (inserted at the beginning of
- * a process, to be updated at the end)
- *
- * @var int
- */
- protected $logID;
- /**
- * Number of "items" processed in the current migration process (whatever that
- * means for the type of process)
- *
- * @var int
- */
- protected $total_processed = 0;
- /**
- * List of other Migration classes which should be imported before this one.
- * E.g., a comment migration class would typically have node and user migrations
- * as dependencies.
- *
- * @var array
- */
- protected $dependencies = array(), $softDependencies = array();
- public function getHardDependencies() {
- return $this->dependencies;
- }
- public function getSoftDependencies() {
- return $this->softDependencies;
- }
- public function getDependencies() {
- return array_merge($this->dependencies, $this->softDependencies);
- }
- /**
- * Name of a function for displaying feedback. It must take the message to display
- * as its first argument, and a (string) message type as its second argument
- * (see drush_log()).
- * @var string
- */
- protected static $displayFunction;
- public static function setDisplayFunction($display_function) {
- self::$displayFunction = $display_function;
- }
- /**
- * 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;
- /**
- * MigrateTeamMember objects representing people involved with this
- * migration.
- *
- * @var array
- */
- protected $team = array();
- public function getTeam() {
- return $this->team;
- }
- /**
- * If provided, an URL for an issue tracking system containing :id where
- * the issue number will go (e.g., 'http://example.com/project/ticket/:id').
- *
- * @var string
- */
- protected $issuePattern;
- public function getIssuePattern() {
- return $this->issuePattern;
- }
- /**
- * If we set an error handler (during import), remember the previous one so
- * it can be restored.
- *
- * @var callback
- */
- protected $previousErrorHandler = NULL;
- /**
- * Disabling a migration prevents it from running with --all, or individually
- * without --force
- *
- * @var boolean
- */
- protected $enabled = TRUE;
- public function getEnabled() {
- return $this->enabled;
- }
- /**
- * 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
- /**
- * 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;
- /**
- * Get human readable name for a message constant.
- *
- * @return string
- * Name.
- */
- public function getMessageLevelName($constant) {
- $map = array(
- MigrationBase::MESSAGE_ERROR => t('Error'),
- MigrationBase::MESSAGE_WARNING => t('Warning'),
- MigrationBase::MESSAGE_NOTICE => t('Notice'),
- MigrationBase::MESSAGE_INFORMATIONAL => t('Informational'),
- );
- return $map[$constant];
- }
- /**
- * General initialization of a MigrationBase object.
- */
- public function __construct($group = NULL) {
- $this->machineName = $this->generateMachineName();
- if (empty($group)) {
- $this->group = MigrateGroup::getInstance('default');
- }
- else {
- $this->group = $group;
- }
- // 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 = drupal_strtolower($limit[strlen($limit)-1]);
- switch ($last) {
- case 'g':
- $limit *= 1024;
- case 'm':
- $limit *= 1024;
- case 'k':
- $limit *= 1024;
- break;
- default:
- throw new Exception(t('Invalid PHP memory_limit !limit',
- array('!limit' => $limit)));
- }
- }
- $this->memoryLimit = $limit;
- }
- // Record the time limit
- $this->timeLimit = ini_get('max_execution_time');
- // Prevent any emails from being sent out on migration
- global $conf;
- if (!empty($conf['mail_system'])) {
- foreach ($conf['mail_system'] as $system => $class) {
- $conf['mail_system'][$system] = 'MigrateMailIgnore';
- }
- }
- // Make sure we clear our semaphores in case of abrupt exit
- register_shutdown_function(array($this, 'endProcess'));
- }
- /**
- * Initialize static members, before any class instances are created.
- */
- static public function staticInitialize() {
- // Default the displayFunction outputFunction based on context
- if (function_exists('drush_log')) {
- self::$displayFunction = 'drush_log';
- }
- else {
- self::$displayFunction = 'drupal_set_message';
- }
- }
- /**
- * Register a new migration process in the migrate_status table. This will
- * generally be used in two contexts - by the class detection code for
- * static (one instance per class) migrations, and by the module implementing
- * dynamic (parameterized class) migrations.
- *
- * @param string $class_name
- * @param string $machine_name
- * @param array $arguments
- */
- static public function registerMigration($class_name, $machine_name = NULL, array $arguments = array()) {
- if (!$machine_name) {
- $machine_name = self::machineFromClass($class_name);
- }
- // Register the migration if it's not already there; if it is,
- // update the class and arguments in case they've changed.
- db_merge('migrate_status')
- ->key(array('machine_name' => $machine_name))
- ->fields(array(
- 'class_name' => $class_name,
- 'arguments' => serialize($arguments)
- ))
- ->execute();
- }
- /**
- * Deregister a migration - remove all traces of it from the database (without
- * touching any content which was created by this migration).
- *
- * @param string $machine_name
- */
- static public function deregisterMigration($machine_name) {
- $rows_deleted = db_delete('migrate_status')
- ->condition('machine_name', $machine_name)
- ->execute();
- }
- /**
- * By default, the migration machine name is the class name (with the
- * Migration suffix, if present, stripped).
- */
- protected function generateMachineName() {
- $class_name = get_class($this);
- return self::machineFromClass($class_name);
- }
- protected static function machineFromClass($class_name) {
- if (preg_match('/Migration$/', $class_name)) {
- $machine_name = drupal_substr($class_name, 0,
- strlen($class_name) - strlen('Migration'));
- }
- else {
- $machine_name = $class_name;
- }
- return $machine_name;
- }
- /**
- * Return the single instance of the given migration.
- *
- * @param string $machine_name
- */
- static public function getInstance($machine_name, $class_name = NULL, array $arguments = array()) {
- $migrations = &drupal_static(__FUNCTION__, array());
- // Otherwise might miss cache hit on case difference
- $machine_name_key = drupal_strtolower($machine_name);
- if (!isset($migrations[$machine_name_key])) {
- // Skip the query if our caller already made it
- if (!$class_name) {
- // See if we know about this migration
- $row = db_select('migrate_status', 'ms')
- ->fields('ms', array('class_name', 'arguments'))
- ->condition('machine_name', $machine_name)
- ->execute()
- ->fetchObject();
- if ($row) {
- $class_name = $row->class_name;
- $arguments = unserialize($row->arguments);
- }
- else {
- // Can't find a migration with this name
- throw new MigrateException(t('No migration found with machine name !machine',
- array('!machine' => $machine_name)));
- }
- }
- $migrations[$machine_name_key] = new $class_name($arguments);
- }
- return $migrations[$machine_name_key];
- }
- /**
- * Identifies whether this migration is "dynamic" (that is, allows multiple
- * instances distinguished by differing parameters). A dynamic class should
- * override this with a return value of TRUE.
- */
- static public function isDynamic() {
- return FALSE;
- }
- /**
- * Default to printing messages, but derived classes are expected to save
- * messages indexed by current source ID.
- *
- * @param string $message
- * The message to record.
- * @param int $level
- * Optional message severity (defaults to MESSAGE_ERROR).
- */
- public function saveMessage($message, $level = MigrationBase::MESSAGE_ERROR) {
- switch ($level) {
- case MigrationBase::MESSAGE_ERROR:
- $level = 'error';
- break;
- case MigrationBase::MESSAGE_WARNING:
- $level = 'warning';
- break;
- case MigrationBase::MESSAGE_NOTICE:
- $level = 'notice';
- break;
- case MigrationBase::MESSAGE_INFORMATIONAL:
- $level = 'status';
- break;
- }
- self::displayMessage($message, $level);
- }
- /**
- * Output the given message appropriately (drush_print/drupal_set_message/etc.)
- *
- * @param string $message
- * The message to output.
- * @param int $level
- * Optional message severity as understood by drupal_set_message and drush_log
- * (defaults to 'error').
- */
- static public function displayMessage($message, $level = 'error') {
- call_user_func(self::$displayFunction, $message, $level);
- }
- /**
- * Custom PHP error handler.
- * TODO: Redundant with hook_watchdog?
- *
- * @param $error_level
- * The level of the error raised.
- * @param $message
- * The error message.
- * @param $filename
- * The filename that the error was raised in.
- * @param $line
- * The line number the error was raised at.
- * @param $context
- * An array that points to the active symbol table at the point the error occurred.
- */
- public function errorHandler($error_level, $message, $filename, $line, $context) {
- if ($error_level & error_reporting()) {
- $message .= "\n" . t('File !file, line !line',
- array('!line' => $line, '!file' => $filename));
- // Record notices and continue
- if ($error_level == E_NOTICE || $error_level == E_USER_NOTICE) {
- $this->saveMessage($message . "(file: $filename, line $line)", MigrationBase::MESSAGE_INFORMATIONAL);
- }
- // Simply ignore strict and deprecated errors
- // Note DEPRECATED constants introduced in PHP 5.3
- elseif (!($error_level == E_STRICT || $error_level == 8192 ||
- $error_level == 16384)) {
- throw new MigrateException($message, MigrationBase::MESSAGE_ERROR);
- }
- }
- }
- /**
- * 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);
- }
- self::displayMessage($message);
- }
- /**
- * Check the current status of a migration.
- * @return int
- * One of the MigrationBase::STATUS_* constants
- */
- public function getStatus() {
- if (!$this->enabled) {
- return MigrationBase::STATUS_DISABLED;
- }
- $status = db_select('migrate_status', 'ms')
- ->fields('ms', array('status'))
- ->condition('machine_name', $this->machineName)
- ->execute()
- ->fetchField();
- if (!isset($status)) {
- $status = MigrationBase::STATUS_IDLE;
- }
- return $status;
- }
- /**
- * Retrieve the last time an import operation completed successfully.
- * @return string
- * Date/time string, formatted... How? Default DB server format?
- */
- public function getLastImported() {
- $last_imported = db_select('migrate_log', 'ml')
- ->fields('ml', array('endtime'))
- ->condition('machine_name', $this->machineName)
- ->isNotNull('endtime')
- ->orderBy('endtime', 'DESC')
- ->execute()
- ->fetchField();
- if ($last_imported) {
- $last_imported = date('Y-m-d H:i:s', $last_imported/1000);
- }
- else {
- $last_imported = '';
- }
- return $last_imported;
- }
- /**
- * Fetch the current highwater mark for updated content.
- *
- * @return string
- * The highwater mark.
- */
- public function getHighwater() {
- $highwater = db_select('migrate_status', 'ms')
- ->fields('ms', array('highwater'))
- ->condition('machine_name', $this->machineName)
- ->execute()
- ->fetchField();
- return $highwater;
- }
- /**
- * Save the highwater mark for this migration (but not when using an idlist).
- *
- * @param mixed $highwater
- * Highwater mark to save
- * @param boolean $force
- * If TRUE, save even if it's lower than the previous value.
- */
- protected function saveHighwater($highwater, $force = FALSE) {
- if (!isset($this->options['idlist'])) {
- $query = db_update('migrate_status')
- ->fields(array('highwater' => $highwater))
- ->condition('machine_name', $this->machineName);
- if (!$force) {
- if (!empty($this->highwaterField['type']) && $this->highwaterField['type'] == 'int') {
- // If the highwater is an integer type, we need to force the DB server
- // to treat the varchar highwater field as an integer (otherwise it will
- // think '5' > '10'). CAST(highwater AS INTEGER) would be ideal, but won't
- // work in MySQL. This hack is thought to be portable.
- $query->where('(highwater+0) < :highwater', array(':highwater' => $highwater));
- }
- else {
- $query->condition('highwater', $highwater, '<');
- }
- }
- $query->execute();
- }
- }
- /**
- * Retrieve the last throughput for current Migration (items / minute).
- * @return integer
- */
- public function getLastThroughput() {
- $last_throughput = 0;
- $row = db_select('migrate_log', 'ml')
- ->fields('ml', array('starttime', 'endtime', 'numprocessed'))
- ->condition('machine_name', $this->machineName)
- ->condition('process_type', 1)
- ->isNotNull('endtime')
- ->orderBy('starttime', 'DESC')
- ->execute()
- ->fetchObject();
- if ($row) {
- $elapsed = ($row->endtime - $row->starttime)/1000;
- if ($elapsed > 0) {
- $last_throughput = round(($row->numprocessed / $elapsed) * 60);
- }
- }
- return $last_throughput;
- }
- /**
- * Reports whether this migration process is complete. For a Migration, for
- * example, this would be whether all available source rows have been processed.
- * Other MigrationBase classes will need to return TRUE/FALSE appropriately.
- */
- abstract public function isComplete();
- /**
- * Reports whether all (hard) dependencies have completed migration
- */
- protected function dependenciesComplete($rollback = FALSE) {
- if ($rollback) {
- foreach (migrate_migrations() as $migration) {
- $dependencies = $migration->getHardDependencies();
- if (array_search($this->machineName, $dependencies) !== FALSE) {
- if (method_exists($migration, 'importedCount') && $migration->importedCount() > 0) {
- return FALSE;
- }
- }
- }
- }
- else {
- foreach ($this->dependencies as $dependency) {
- $migration = MigrationBase::getInstance($dependency);
- if (!$migration->isComplete()) {
- return FALSE;
- }
- }
- }
- return TRUE;
- }
- /**
- * Returns an array of the migration's dependencies that are incomplete.
- */
- public function incompleteDependencies() {
- $incomplete = array();
- foreach ($this->getDependencies() as $dependency) {
- $migration = MigrationBase::getInstance($dependency);
- if (!$migration->isComplete()) {
- $incomplete[] = $dependency;
- }
- }
- return $incomplete;
- }
- /**
- * Begin a process, ensuring only one process can be active
- * at once on a given migration.
- *
- * @param int $newStatus
- * MigrationBase::STATUS_IMPORTING or MigrationBase::STATUS_ROLLING_BACK
- */
- protected function beginProcess($newStatus) {
- // So hook_watchdog() knows what migration (if any) is running
- self::$currentMigration = $this;
- // Try to make the semaphore handling atomic (depends on DB support)
- $transaction = db_transaction();
- $this->starttime = microtime(TRUE);
- // Check to make sure there's no process already running for this migration
- $status = $this->getStatus();
- if ($status != MigrationBase::STATUS_IDLE) {
- throw new MigrateException(t('There is already an active process on !machine_name',
- array('!machine_name' => $this->machineName)));
- }
- $this->processing = TRUE;
- $this->status = $newStatus;
- db_merge('migrate_status')
- ->key(array('machine_name' => $this->machineName))
- ->fields(array('class_name' => get_class($this), 'status' => $newStatus))
- ->execute();
- // Set an error handler for imports
- if ($newStatus == MigrationBase::STATUS_IMPORTING) {
- $this->previousErrorHandler = set_error_handler(array($this, 'errorHandler'));
- }
- // Save the initial history record
- if ($this->logHistory) {
- $this->logID = db_insert('migrate_log')
- ->fields(array(
- 'machine_name' => $this->machineName,
- 'process_type' => $newStatus,
- 'starttime' => round(microtime(TRUE) * 1000),
- 'initialHighwater' => $this->getHighwater(),
- ))
- ->execute();
- }
- }
- /**
- * End a rollback or import process, releasing the semaphore. Note that it must
- * be public to be callable as the shutdown function.
- */
- public function endProcess() {
- if ($this->previousErrorHandler) {
- set_error_handler($this->previousErrorHandler);
- $this->previousErrorHandler = NULL;
- }
- if ($this->processing) {
- $this->status = MigrationBase::STATUS_IDLE;
- $fields = array('class_name' => get_class($this), 'status' => MigrationBase::STATUS_IDLE);
- db_merge('migrate_status')
- ->key(array('machine_name' => $this->machineName))
- ->fields($fields)
- ->execute();
- // Complete the log record
- if ($this->logHistory) {
- try {
- db_merge('migrate_log')
- ->key(array('mlid' => $this->logID))
- ->fields(array(
- 'endtime' => round(microtime(TRUE) * 1000),
- 'finalhighwater' => $this->getHighwater(),
- 'numprocessed' => $this->total_processed,
- ))
- ->execute();
- }
- catch (PDOException $e) {
- Migration::displayMessage(t('Could not log operation on migration !name - possibly MigrationBase::beginProcess() was not called',
- array('!name' => $this->machineName)));
- }
- }
- $this->processing = FALSE;
- }
- self::$currentMigration = NULL;
- }
- /**
- * Signal that any current import or rollback process should end itself at
- * the earliest opportunity
- */
- public function stopProcess() {
- // Do not change the status of an idle migration
- db_update('migrate_status')
- ->fields(array('status' => MigrationBase::STATUS_STOPPING))
- ->condition('machine_name', $this->machineName)
- ->condition('status', MigrationBase::STATUS_IDLE, '<>')
- ->execute();
- }
- /**
- * Reset the status of the migration to IDLE (to be used when the status
- * gets stuck, e.g. if a process core-dumped)
- */
- public function resetStatus() {
- // Do not change the status of an already-idle migration
- db_update('migrate_status')
- ->fields(array('status' => MigrationBase::STATUS_IDLE))
- ->condition('machine_name', $this->machineName)
- ->condition('status', MigrationBase::STATUS_IDLE, '<>')
- ->execute();
- }
- /**
- * Perform an operation during the rollback phase.
- *
- * @param array $options
- * List of options provided (usually from a drush command). Specific to
- * the derived class.
- */
- public function processRollback(array $options = array()) {
- if ($this->enabled) {
- $return = MigrationBase::RESULT_COMPLETED;
- if (method_exists($this, 'rollback')) {
- $this->options = $options;
- if (!isset($options['force'])) {
- if (!$this->dependenciesComplete(TRUE)) {
- return MigrationBase::RESULT_SKIPPED;
- }
- }
- $this->beginProcess(MigrationBase::STATUS_ROLLING_BACK);
- try {
- $return = $this->rollback();
- }
- catch (Exception $exception) {
- // If something bad happened, make sure we clear the semaphore
- $this->endProcess();
- throw $exception;
- }
- $this->endProcess();
- }
- }
- else {
- $return = MigrationBase::RESULT_DISABLED;
- }
- return $return;
- }
- /**
- * Perform an operation during the import phase
- *
- * @param array $options
- * List of options provided (usually from a drush command). Specific to
- * the derived class.
- */
- public function processImport(array $options = array()) {
- if ($this->enabled) {
- $return = MigrationBase::RESULT_COMPLETED;
- if (method_exists($this, 'import')) {
- $this->options = $options;
- if (!isset($options['force']) || !$options['force']) {
- if (!$this->dependenciesComplete()) {
- return MigrationBase::RESULT_SKIPPED;
- }
- }
- $this->beginProcess(MigrationBase::STATUS_IMPORTING);
- try {
- $return = $this->import();
- }
- catch (Exception $exception) {
- // If something bad happened, make sure we clear the semaphore
- $this->endProcess();
- throw $exception;
- }
- if ($return == MigrationBase::RESULT_COMPLETED && isset($this->total_successes)) {
- $overallThroughput = round(60*$this->total_successes / (microtime(TRUE) - $this->starttime));
- }
- else {
- $overallThroughput = 0;
- }
- $this->endProcess($overallThroughput);
- }
- }
- else {
- $return = MigrationBase::RESULT_DISABLED;
- }
- return $return;
- }
- /**
- * A derived migration class does the actual rollback or import work in these
- * methods - we cannot declare them abstract because some classes may define
- * only one.
- *
- * abstract protected function rollback();
- * abstract protected function import();
- */
- /**
- * 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) {
- self::displayMessage(
- 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)) {
- self::displayMessage(
- 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 {
- self::displayMessage(
- 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;
- }
- }
- /**
- * Test whether we've exceeded the designated time limit.
- *
- * @return boolean
- * TRUE if the threshold is exceeded, FALSE if not.
- */
- protected function timeOptionExceeded() {
- if (!$timelimit = $this->getTimeLimit()) {
- return FALSE;
- }
- $time_elapsed = time() - REQUEST_TIME;
- if ($time_elapsed >= $timelimit) {
- return TRUE;
- }
- else {
- return FALSE;
- }
- }
- /**
- * Convert an incoming string (which may be a UNIX timestamp, or an arbitrarily-formatted
- * date/time string) to a UNIX timestamp.
- *
- * @param string $value
- */
- static public function timestamp($value) {
- // Default empty values to now
- if (empty($value)) {
- return time();
- }
- // Does it look like it's already a timestamp? Just return it
- if (is_numeric($value)) {
- return $value;
- }
- $time = strtotime($value);
- if ($time == FALSE) {
- // Handles form YYYY-MM-DD HH:MM:SS.garbage
- if (drupal_strlen($value) > 19) {
- $time = strtotime(drupal_substr($value, 0, 19));
- }
- }
- return $time;
- }
- }
- // Make sure static members (in particular, $displayFunction) get
- // initialized even if there are no class instances.
- MigrationBase::staticInitialize();
|