base.inc 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395
  1. <?php
  2. /**
  3. * @file
  4. * Defines the base class for migration processes.
  5. */
  6. /**
  7. * The base class for all objects representing distinct steps in a migration
  8. * process. Most commonly these will be Migration objects which actually import
  9. * data from a source into a Drupal destination, but by deriving classes directly
  10. * from MigrationBase one can have other sorts of tasks (e.g., enabling/disabling
  11. * of modules) occur during the migration process.
  12. */
  13. abstract class MigrationBase {
  14. /**
  15. * Track the migration currently running, so handlers can easily determine it
  16. * without having to pass a Migration object everywhere.
  17. *
  18. * @var Migration
  19. */
  20. protected static $currentMigration;
  21. public static function currentMigration() {
  22. return self::$currentMigration;
  23. }
  24. /**
  25. * The machine name of this Migration object, derived by removing the 'Migration'
  26. * suffix from the class name. Used to construct default map/message table names,
  27. * displayed in drush migrate-status, key to migrate_status table...
  28. *
  29. * @var string
  30. */
  31. protected $machineName;
  32. public function getMachineName() {
  33. return $this->machineName;
  34. }
  35. /**
  36. * A migration group object, used to collect related migrations.
  37. *
  38. * @var MigrateGroup
  39. */
  40. protected $group;
  41. public function getGroup() {
  42. return $this->group;
  43. }
  44. /**
  45. * Detailed information describing the migration.
  46. *
  47. * @var string
  48. */
  49. protected $description;
  50. public function getDescription() {
  51. return $this->description;
  52. }
  53. public function setDescription($description) {
  54. $this->description = $description;
  55. }
  56. /**
  57. * Save options passed to current operation
  58. * @var array
  59. */
  60. protected $options;
  61. public function getOption($option_name) {
  62. if (isset($this->options[$option_name])) {
  63. return $this->options[$option_name];
  64. }
  65. else {
  66. return NULL;
  67. }
  68. }
  69. public function getItemLimit() {
  70. if (isset($this->options['limit']) &&
  71. ($this->options['limit']['unit'] == 'items' || $this->options['limit']['unit'] == 'item')) {
  72. return $this->options['limit']['value'];
  73. }
  74. else {
  75. return NULL;
  76. }
  77. }
  78. public function getTimeLimit() {
  79. if (isset($this->options['limit']) &&
  80. ($this->options['limit']['unit'] == 'seconds' || $this->options['limit']['unit'] == 'second')) {
  81. return $this->options['limit']['value'];
  82. }
  83. else {
  84. return NULL;
  85. }
  86. }
  87. /**
  88. * Indicates that we are processing a rollback or import - used to avoid
  89. * excess writes in endProcess()
  90. *
  91. * @var boolean
  92. */
  93. protected $processing = FALSE;
  94. /**
  95. * Are we importing, rolling back, or doing nothing?
  96. *
  97. * @var enum
  98. */
  99. protected $status = MigrationBase::STATUS_IDLE;
  100. /**
  101. * When the current operation started.
  102. * @var int
  103. */
  104. protected $starttime;
  105. /**
  106. * Whether to maintain a history of migration processes in migrate_log
  107. *
  108. * @var boolean
  109. */
  110. protected $logHistory = TRUE;
  111. /**
  112. * Primary key of the current history record (inserted at the beginning of
  113. * a process, to be updated at the end)
  114. *
  115. * @var int
  116. */
  117. protected $logID;
  118. /**
  119. * Number of "items" processed in the current migration process (whatever that
  120. * means for the type of process)
  121. *
  122. * @var int
  123. */
  124. protected $total_processed = 0;
  125. /**
  126. * List of other Migration classes which should be imported before this one.
  127. * E.g., a comment migration class would typically have node and user migrations
  128. * as dependencies.
  129. *
  130. * @var array
  131. */
  132. protected $dependencies = array(), $softDependencies = array();
  133. public function getHardDependencies() {
  134. return $this->dependencies;
  135. }
  136. public function setHardDependencies(array $dependencies) {
  137. $this->dependencies = $dependencies;
  138. }
  139. public function addHardDependencies(array $dependencies) {
  140. $this->dependencies = array_merge($this->dependencies, $dependencies);
  141. }
  142. public function getSoftDependencies() {
  143. return $this->softDependencies;
  144. }
  145. public function setSoftDependencies(array $dependencies) {
  146. $this->softDependencies = $dependencies;
  147. }
  148. public function addSoftDependencies(array $dependencies) {
  149. $this->softDependencies = array_merge($this->softDependencies, $dependencies);
  150. }
  151. public function getDependencies() {
  152. return array_merge($this->dependencies, $this->softDependencies);
  153. }
  154. /**
  155. * Name of a function for displaying feedback. It must take the message to display
  156. * as its first argument, and a (string) message type as its second argument
  157. * (see drush_log()).
  158. * @var string
  159. */
  160. protected static $displayFunction;
  161. public static function setDisplayFunction($display_function) {
  162. self::$displayFunction = $display_function;
  163. }
  164. /**
  165. * Track whether or not we've already displayed an encryption warning
  166. *
  167. * @var bool
  168. */
  169. protected static $showEncryptionWarning = TRUE;
  170. /**
  171. * The fraction of the memory limit at which an operation will be interrupted.
  172. * Can be overridden by a Migration subclass if one would like to push the
  173. * envelope. Defaults to 85%.
  174. *
  175. * @var float
  176. */
  177. protected $memoryThreshold = 0.85;
  178. /**
  179. * The PHP memory_limit expressed in bytes.
  180. *
  181. * @var int
  182. */
  183. protected $memoryLimit;
  184. /**
  185. * The fraction of the time limit at which an operation will be interrupted.
  186. * Can be overridden by a Migration subclass if one would like to push the
  187. * envelope. Defaults to 90%.
  188. *
  189. * @var float
  190. */
  191. protected $timeThreshold = 0.90;
  192. /**
  193. * The PHP max_execution_time.
  194. *
  195. * @var int
  196. */
  197. protected $timeLimit;
  198. /**
  199. * A time limit in seconds appropriate to be used in a batch
  200. * import. Defaults to 240.
  201. *
  202. * @var int
  203. */
  204. protected $batchTimeLimit = 240;
  205. /**
  206. * MigrateTeamMember objects representing people involved with this
  207. * migration.
  208. *
  209. * @var array
  210. */
  211. protected $team = array();
  212. public function getTeam() {
  213. return $this->team;
  214. }
  215. public function setTeam(array $team) {
  216. $this->team = $team;
  217. }
  218. /**
  219. * If provided, an URL for an issue tracking system containing :id where
  220. * the issue number will go (e.g., 'http://example.com/project/ticket/:id').
  221. *
  222. * @var string
  223. */
  224. protected $issuePattern;
  225. public function getIssuePattern() {
  226. return $this->issuePattern;
  227. }
  228. public function setIssuePattern($issue_pattern) {
  229. $this->issuePattern = $issue_pattern;
  230. }
  231. /**
  232. * If we set an error handler (during import), remember the previous one so
  233. * it can be restored.
  234. *
  235. * @var callback
  236. */
  237. protected $previousErrorHandler = NULL;
  238. /**
  239. * Arguments configuring a migration.
  240. *
  241. * @var array
  242. */
  243. protected $arguments = array();
  244. public function getArguments() {
  245. return $this->arguments;
  246. }
  247. public function setArguments(array $arguments) {
  248. $this->arguments = $arguments;
  249. }
  250. public function addArguments(array $arguments) {
  251. $this->arguments = array_merge($this->arguments, $arguments);
  252. }
  253. /**
  254. * Disabling a migration prevents it from running with --all, or individually
  255. * without --force
  256. *
  257. * @var boolean
  258. */
  259. protected $enabled = TRUE;
  260. public function getEnabled() {
  261. return $this->enabled;
  262. }
  263. public function setEnabled($enabled) {
  264. $this->enabled = $enabled;
  265. }
  266. /**
  267. * Any module hooks which should be disabled during migration processes.
  268. *
  269. * @var array
  270. * Key: Hook name (e.g., 'node_insert')
  271. * Value: Array of modules for which to disable this hook (e.g., array('pathauto')).
  272. */
  273. protected $disableHooks = array();
  274. public function getDisableHooks() {
  275. return $this->disableHooks;
  276. }
  277. /**
  278. * An array to track 'mail_system' variable if disabled.
  279. */
  280. protected $mailSystem = array();
  281. /**
  282. * Have we already warned about obsolete constructor argumentss on this request?
  283. *
  284. * @var bool
  285. */
  286. static protected $groupArgumentWarning = FALSE;
  287. static protected $emptyArgumentsWarning = FALSE;
  288. /**
  289. * Codes representing the result of a rollback or import process.
  290. */
  291. const RESULT_COMPLETED = 1; // All records have been processed
  292. const RESULT_INCOMPLETE = 2; // The process has interrupted itself (e.g., the
  293. // memory limit is approaching)
  294. const RESULT_STOPPED = 3; // The process was stopped externally (e.g., via
  295. // drush migrate-stop)
  296. const RESULT_FAILED = 4; // The process had a fatal error
  297. const RESULT_SKIPPED = 5; // Dependencies are unfulfilled - skip the process
  298. const RESULT_DISABLED = 6; // This migration is disabled, skipping
  299. /**
  300. * Codes representing the current status of a migration, and stored in the
  301. * migrate_status table.
  302. */
  303. const STATUS_IDLE = 0;
  304. const STATUS_IMPORTING = 1;
  305. const STATUS_ROLLING_BACK = 2;
  306. const STATUS_STOPPING = 3;
  307. const STATUS_DISABLED = 4;
  308. /**
  309. * Message types to be passed to saveMessage() and saved in message tables.
  310. * MESSAGE_INFORMATIONAL represents a condition that did not prevent the operation
  311. * from succeeding - all others represent different severities of conditions
  312. * resulting in a source record not being imported.
  313. */
  314. const MESSAGE_ERROR = 1;
  315. const MESSAGE_WARNING = 2;
  316. const MESSAGE_NOTICE = 3;
  317. const MESSAGE_INFORMATIONAL = 4;
  318. /**
  319. * Get human readable name for a message constant.
  320. *
  321. * @return string
  322. * Name.
  323. */
  324. public function getMessageLevelName($constant) {
  325. $map = array(
  326. MigrationBase::MESSAGE_ERROR => t('Error'),
  327. MigrationBase::MESSAGE_WARNING => t('Warning'),
  328. MigrationBase::MESSAGE_NOTICE => t('Notice'),
  329. MigrationBase::MESSAGE_INFORMATIONAL => t('Informational'),
  330. );
  331. return $map[$constant];
  332. }
  333. /**
  334. * Construction of a MigrationBase instance.
  335. *
  336. * @param array $arguments
  337. */
  338. public function __construct($arguments = array()) {
  339. // Support for legacy code passing a group object as the first parameter.
  340. if (is_object($arguments) && is_a($arguments, 'MigrateGroup')) {
  341. $this->group = $arguments;
  342. $this->arguments['group_name'] = $arguments->getName();
  343. if (!self::$groupArgumentWarning &&
  344. variable_get('migrate_deprecation_warnings', 1)) {
  345. self::displayMessage(t('Passing a group object to a migration constructor is now deprecated - pass through the arguments array passed to the leaf class instead.'));
  346. self::$groupArgumentWarning = TRUE;
  347. }
  348. }
  349. else {
  350. if (empty($arguments)) {
  351. $this->arguments = array();
  352. if (!self::$emptyArgumentsWarning &&
  353. variable_get('migrate_deprecation_warnings', 1)) {
  354. self::displayMessage(t('Passing an empty first parameter to a migration constructor is now deprecated - pass through the arguments array passed to the leaf class instead.'));
  355. self::$emptyArgumentsWarning = TRUE;
  356. }
  357. }
  358. else {
  359. $this->arguments = $arguments;
  360. }
  361. if (empty($this->arguments['group_name'])) {
  362. $this->arguments['group_name'] = 'default';
  363. }
  364. $this->group = MigrateGroup::getInstance($this->arguments['group_name']);
  365. }
  366. if (isset($this->arguments['machine_name'])) {
  367. $this->machineName = $this->arguments['machine_name'];
  368. }
  369. else {
  370. // Deprecated - this supports old code which does not pass the arguments
  371. // array through to the base constructor. Remove in the next version.
  372. $this->machineName = $this->machineFromClass(get_class($this));
  373. }
  374. // Make any group arguments directly accessible to the specific migration,
  375. // other than group dependencies.
  376. $group_arguments = $this->group->getArguments();
  377. unset($group_arguments['dependencies']);
  378. $this->arguments += $group_arguments;
  379. // Record the memory limit in bytes
  380. $limit = trim(ini_get('memory_limit'));
  381. if ($limit == '-1') {
  382. $this->memoryLimit = PHP_INT_MAX;
  383. }
  384. else {
  385. if (!is_numeric($limit)) {
  386. $last = drupal_strtolower($limit[strlen($limit)-1]);
  387. switch ($last) {
  388. case 'g':
  389. $limit *= 1024;
  390. case 'm':
  391. $limit *= 1024;
  392. case 'k':
  393. $limit *= 1024;
  394. break;
  395. default:
  396. throw new Exception(t('Invalid PHP memory_limit !limit',
  397. array('!limit' => $limit)));
  398. }
  399. }
  400. $this->memoryLimit = $limit;
  401. }
  402. // Record the time limit
  403. $this->timeLimit = ini_get('max_execution_time');
  404. // Save the current mail system, prior to disabling emails.
  405. $this->saveMailSystem();
  406. // Prevent emails from being sent out during migrations.
  407. $this->disableMailSystem();
  408. // Make sure we clear our semaphores in case of abrupt exit
  409. drupal_register_shutdown_function(array($this, 'endProcess'));
  410. // Save any hook disablement information.
  411. if (isset($this->arguments['disable_hooks']) &&
  412. is_array($this->arguments['disable_hooks'])) {
  413. $this->disableHooks = $this->arguments['disable_hooks'];
  414. }
  415. }
  416. /**
  417. * Initialize static members, before any class instances are created.
  418. */
  419. static public function staticInitialize() {
  420. // Default the displayFunction outputFunction based on context
  421. if (function_exists('drush_log')) {
  422. self::$displayFunction = 'drush_log';
  423. }
  424. else {
  425. self::$displayFunction = 'drupal_set_message';
  426. }
  427. }
  428. /**
  429. * Register a new migration process in the migrate_status table. This will
  430. * generally be used in two contexts - by the class detection code for
  431. * static (one instance per class) migrations, and by the module implementing
  432. * dynamic (parameterized class) migrations.
  433. *
  434. * @param string $class_name
  435. * @param string $machine_name
  436. * @param array $arguments
  437. */
  438. static public function registerMigration($class_name, $machine_name = NULL,
  439. array $arguments = array()) {
  440. // Support for legacy migration code - in later releases, the machine_name
  441. // should always be explicit.
  442. if (!$machine_name) {
  443. $machine_name = self::machineFromClass($class_name);
  444. }
  445. if (!preg_match('|^[a-z0-9_]+$|i', $machine_name)) {
  446. throw new Exception(t('!name is not a valid Migration machine name. Use only alphanumeric or underscore characters.',
  447. array('!name' => $machine_name)));
  448. }
  449. // We no longer have any need to store the machine_name in the arguments.
  450. if (isset($arguments['machine_name'])) {
  451. unset($arguments['machine_name']);
  452. }
  453. if (isset($arguments['group_name'])) {
  454. $group_name = $arguments['group_name'];
  455. unset($arguments['group_name']);
  456. }
  457. else {
  458. $group_name = 'default';
  459. }
  460. $arguments = self::encryptArguments($arguments);
  461. // Register the migration if it's not already there; if it is,
  462. // update the class and arguments in case they've changed.
  463. db_merge('migrate_status')
  464. ->key(array('machine_name' => $machine_name))
  465. ->fields(array(
  466. 'class_name' => $class_name,
  467. 'group_name' => $group_name,
  468. 'arguments' => serialize($arguments)
  469. ))
  470. ->execute();
  471. }
  472. /**
  473. * Deregister a migration - remove all traces of it from the database (without
  474. * touching any content which was created by this migration).
  475. *
  476. * @param string $machine_name
  477. */
  478. static public function deregisterMigration($machine_name) {
  479. $rows_deleted = db_delete('migrate_status')
  480. ->condition('machine_name', $machine_name)
  481. ->execute();
  482. // Make sure the group gets deleted if we were the only member.
  483. MigrateGroup::deleteOrphans();
  484. }
  485. /**
  486. * The migration machine name is stored in the arguments.
  487. *
  488. * @return string
  489. */
  490. protected function generateMachineName() {
  491. return $this->arguments['machine_name'];
  492. }
  493. /**
  494. * Given only a class name, derive a machine name (the class name with the
  495. * "Migration" suffix, if any, removed).
  496. *
  497. * @param $class_name
  498. *
  499. * @return string
  500. */
  501. protected static function machineFromClass($class_name) {
  502. if (preg_match('/Migration$/', $class_name)) {
  503. $machine_name = drupal_substr($class_name, 0,
  504. strlen($class_name) - strlen('Migration'));
  505. }
  506. else {
  507. $machine_name = $class_name;
  508. }
  509. return $machine_name;
  510. }
  511. /**
  512. * Return the single instance of the given migration.
  513. *
  514. * @param string $machine_name
  515. */
  516. /**
  517. * Return the single instance of the given migration.
  518. *
  519. * @param $machine_name
  520. * The unique machine name of the migration to retrieve.
  521. * @param string $class_name
  522. * Deprecated - no longer used, class name is retrieved from migrate_status.
  523. * @param array $arguments
  524. * Deprecated - no longer used, arguments are retrieved from migrate_status.
  525. *
  526. * @return MigrationBase
  527. */
  528. static public function getInstance($machine_name, $class_name = NULL, array $arguments = array()) {
  529. $migrations = &drupal_static(__FUNCTION__, array());
  530. // Otherwise might miss cache hit on case difference
  531. $machine_name_key = drupal_strtolower($machine_name);
  532. if (!isset($migrations[$machine_name_key])) {
  533. // See if we know about this migration
  534. $row = db_select('migrate_status', 'ms')
  535. ->fields('ms', array('class_name', 'group_name', 'arguments'))
  536. ->condition('machine_name', $machine_name)
  537. ->execute()
  538. ->fetchObject();
  539. if ($row) {
  540. $class_name = $row->class_name;
  541. $arguments = unserialize($row->arguments);
  542. $arguments = self::decryptArguments($arguments);
  543. $arguments['group_name'] = $row->group_name;
  544. }
  545. else {
  546. // Can't find a migration with this name
  547. self::displayMessage(t('No migration found with machine name !machine',
  548. array('!machine' => $machine_name)));
  549. return NULL;
  550. }
  551. $arguments['machine_name'] = $machine_name;
  552. if (class_exists($class_name)) {
  553. try {
  554. $migrations[$machine_name_key] = new $class_name($arguments);
  555. }
  556. catch (Exception $e) {
  557. self::displayMessage(t('Migration !machine could not be constructed.',
  558. array('!machine' => $machine_name)));
  559. self::displayMessage($e->getMessage());
  560. return NULL;
  561. }
  562. }
  563. else {
  564. self::displayMessage(t('No migration class !class found',
  565. array('!class' => $class_name)));
  566. return NULL;
  567. }
  568. if (isset($arguments['dependencies'])) {
  569. $migrations[$machine_name_key]->setHardDependencies(
  570. $arguments['dependencies']);
  571. }
  572. if (isset($arguments['soft_dependencies'])) {
  573. $migrations[$machine_name_key]->setSoftDependencies(
  574. $arguments['soft_dependencies']);
  575. }
  576. }
  577. return $migrations[$machine_name_key];
  578. }
  579. /**
  580. * @deprecated - No longer a useful distinction between "status" and "dynamic"
  581. * migrations.
  582. */
  583. static public function isDynamic() {
  584. return FALSE;
  585. }
  586. /**
  587. * Default to printing messages, but derived classes are expected to save
  588. * messages indexed by current source ID.
  589. *
  590. * @param string $message
  591. * The message to record.
  592. * @param int $level
  593. * Optional message severity (defaults to MESSAGE_ERROR).
  594. */
  595. public function saveMessage($message, $level = MigrationBase::MESSAGE_ERROR) {
  596. switch ($level) {
  597. case MigrationBase::MESSAGE_ERROR:
  598. $level = 'error';
  599. break;
  600. case MigrationBase::MESSAGE_WARNING:
  601. $level = 'warning';
  602. break;
  603. case MigrationBase::MESSAGE_NOTICE:
  604. $level = 'notice';
  605. break;
  606. case MigrationBase::MESSAGE_INFORMATIONAL:
  607. $level = 'status';
  608. break;
  609. }
  610. self::displayMessage($message, $level);
  611. }
  612. /**
  613. * Output the given message appropriately (drush_print/drupal_set_message/etc.)
  614. *
  615. * @param string $message
  616. * The message to output.
  617. * @param int $level
  618. * Optional message severity as understood by drupal_set_message and drush_log
  619. * (defaults to 'error').
  620. */
  621. static public function displayMessage($message, $level = 'error') {
  622. call_user_func(self::$displayFunction, $message, $level);
  623. }
  624. /**
  625. * Custom PHP error handler.
  626. * TODO: Redundant with hook_watchdog?
  627. *
  628. * @param $error_level
  629. * The level of the error raised.
  630. * @param $message
  631. * The error message.
  632. * @param $filename
  633. * The filename that the error was raised in.
  634. * @param $line
  635. * The line number the error was raised at.
  636. * @param $context
  637. * An array that points to the active symbol table at the point the error occurred.
  638. */
  639. public function errorHandler($error_level, $message, $filename, $line, $context) {
  640. if ($error_level & error_reporting()) {
  641. $message .= "\n" . t('File !file, line !line',
  642. array('!line' => $line, '!file' => $filename));
  643. // Record notices and continue
  644. if ($error_level == E_NOTICE || $error_level == E_USER_NOTICE) {
  645. $this->saveMessage($message . "(file: $filename, line $line)", MigrationBase::MESSAGE_INFORMATIONAL);
  646. }
  647. // Simply ignore strict and deprecated errors
  648. // Note DEPRECATED constants introduced in PHP 5.3
  649. elseif (!($error_level == E_STRICT || $error_level == 8192 ||
  650. $error_level == 16384)) {
  651. throw new MigrateException($message, MigrationBase::MESSAGE_ERROR);
  652. }
  653. }
  654. }
  655. /**
  656. * Takes an Exception object and both saves and displays it, pulling additional
  657. * information on the location triggering the exception.
  658. *
  659. * @param Exception $exception
  660. * Object representing the exception.
  661. * @param boolean $save
  662. * Whether to save the message in the migration's mapping table. Set to FALSE
  663. * in contexts where this doesn't make sense.
  664. */
  665. public function handleException($exception, $save = TRUE) {
  666. $result = _drupal_decode_exception($exception);
  667. $message = $result['!message'] . ' (' . $result['%file'] . ':' . $result['%line'] . ')';
  668. if ($save) {
  669. $this->saveMessage($message);
  670. }
  671. self::displayMessage($message);
  672. }
  673. /**
  674. * Check the current status of a migration.
  675. * @return int
  676. * One of the MigrationBase::STATUS_* constants
  677. */
  678. public function getStatus() {
  679. if (!$this->enabled) {
  680. return MigrationBase::STATUS_DISABLED;
  681. }
  682. $status = db_select('migrate_status', 'ms')
  683. ->fields('ms', array('status'))
  684. ->condition('machine_name', $this->machineName)
  685. ->execute()
  686. ->fetchField();
  687. if (!isset($status)) {
  688. $status = MigrationBase::STATUS_IDLE;
  689. }
  690. return $status;
  691. }
  692. /**
  693. * Retrieve the last time an import operation completed successfully.
  694. * @return string
  695. * Date/time string, formatted... How? Default DB server format?
  696. */
  697. public function getLastImported() {
  698. $last_imported = db_select('migrate_log', 'ml')
  699. ->fields('ml', array('endtime'))
  700. ->condition('machine_name', $this->machineName)
  701. ->isNotNull('endtime')
  702. ->orderBy('endtime', 'DESC')
  703. ->execute()
  704. ->fetchField();
  705. if ($last_imported) {
  706. $last_imported = date('Y-m-d H:i:s', $last_imported/1000);
  707. }
  708. else {
  709. $last_imported = '';
  710. }
  711. return $last_imported;
  712. }
  713. /**
  714. * Fetch the current highwater mark for updated content.
  715. *
  716. * @return string
  717. * The highwater mark.
  718. */
  719. public function getHighwater() {
  720. $highwater = db_select('migrate_status', 'ms')
  721. ->fields('ms', array('highwater'))
  722. ->condition('machine_name', $this->machineName)
  723. ->execute()
  724. ->fetchField();
  725. return $highwater;
  726. }
  727. /**
  728. * Save the highwater mark for this migration (but not when using an idlist).
  729. *
  730. * @param mixed $highwater
  731. * Highwater mark to save
  732. * @param boolean $force
  733. * If TRUE, save even if it's lower than the previous value.
  734. */
  735. protected function saveHighwater($highwater, $force = FALSE) {
  736. if (!isset($this->options['idlist'])) {
  737. $query = db_update('migrate_status')
  738. ->fields(array('highwater' => $highwater))
  739. ->condition('machine_name', $this->machineName);
  740. if (!$force) {
  741. if (!empty($this->highwaterField['type']) && $this->highwaterField['type'] == 'int') {
  742. // If the highwater is an integer type, we need to force the DB server
  743. // to treat the varchar highwater field as an integer (otherwise it will
  744. // think '5' > '10').
  745. switch (Database::getConnection()->databaseType()) {
  746. case 'pgsql':
  747. $query->where('(CASE WHEN highwater=\'\' THEN 0 ELSE CAST(highwater AS INTEGER) END) < :highwater', array(':highwater' => intval($highwater)));
  748. break;
  749. default:
  750. // CAST(highwater AS INTEGER) would be ideal, but won't
  751. // work in MySQL. This hack is thought to be portable.
  752. $query->where('(highwater+0) < :highwater', array(':highwater' => $highwater));
  753. }
  754. }
  755. else {
  756. $query->condition('highwater', $highwater, '<');
  757. }
  758. }
  759. $query->execute();
  760. }
  761. }
  762. /**
  763. * Retrieve the last throughput for current Migration (items / minute).
  764. * @return integer
  765. */
  766. public function getLastThroughput() {
  767. $last_throughput = 0;
  768. $row = db_select('migrate_log', 'ml')
  769. ->fields('ml', array('starttime', 'endtime', 'numprocessed'))
  770. ->condition('machine_name', $this->machineName)
  771. ->condition('process_type', 1)
  772. ->isNotNull('endtime')
  773. ->orderBy('starttime', 'DESC')
  774. ->execute()
  775. ->fetchObject();
  776. if ($row) {
  777. $elapsed = ($row->endtime - $row->starttime)/1000;
  778. if ($elapsed > 0) {
  779. $last_throughput = round(($row->numprocessed / $elapsed) * 60);
  780. }
  781. }
  782. return $last_throughput;
  783. }
  784. /**
  785. * Reports whether this migration process is complete. For a Migration, for
  786. * example, this would be whether all available source rows have been processed.
  787. * Other MigrationBase classes will need to return TRUE/FALSE appropriately.
  788. */
  789. abstract public function isComplete();
  790. /**
  791. * Reports whether all (hard) dependencies have completed migration
  792. */
  793. protected function dependenciesComplete($rollback = FALSE) {
  794. if ($rollback) {
  795. foreach (migrate_migrations() as $migration) {
  796. $dependencies = $migration->getHardDependencies();
  797. if (array_search($this->machineName, $dependencies) !== FALSE) {
  798. if (method_exists($migration, 'importedCount') && $migration->importedCount() > 0) {
  799. return FALSE;
  800. }
  801. }
  802. }
  803. }
  804. else {
  805. foreach ($this->dependencies as $dependency) {
  806. $migration = MigrationBase::getInstance($dependency);
  807. if (!$migration || !$migration->isComplete()) {
  808. return FALSE;
  809. }
  810. }
  811. }
  812. return TRUE;
  813. }
  814. /**
  815. * Returns an array of the migration's dependencies that are incomplete.
  816. */
  817. public function incompleteDependencies() {
  818. $incomplete = array();
  819. foreach ($this->getDependencies() as $dependency) {
  820. $migration = MigrationBase::getInstance($dependency);
  821. if (!$migration || !$migration->isComplete()) {
  822. $incomplete[] = $dependency;
  823. }
  824. }
  825. return $incomplete;
  826. }
  827. /**
  828. * Begin a process, ensuring only one process can be active
  829. * at once on a given migration.
  830. *
  831. * @param int $newStatus
  832. * MigrationBase::STATUS_IMPORTING or MigrationBase::STATUS_ROLLING_BACK
  833. */
  834. protected function beginProcess($newStatus) {
  835. // So hook_watchdog() knows what migration (if any) is running
  836. self::$currentMigration = $this;
  837. // Try to make the semaphore handling atomic (depends on DB support)
  838. $transaction = db_transaction();
  839. $this->starttime = microtime(TRUE);
  840. // Check to make sure there's no process already running for this migration
  841. $status = $this->getStatus();
  842. if ($status != MigrationBase::STATUS_IDLE) {
  843. throw new MigrateException(t('There is already an active process on !machine_name',
  844. array('!machine_name' => $this->machineName)));
  845. }
  846. $this->processing = TRUE;
  847. $this->status = $newStatus;
  848. db_merge('migrate_status')
  849. ->key(array('machine_name' => $this->machineName))
  850. ->fields(array('class_name' => get_class($this), 'status' => $newStatus))
  851. ->execute();
  852. // Set an error handler for imports
  853. if ($newStatus == MigrationBase::STATUS_IMPORTING) {
  854. $this->previousErrorHandler = set_error_handler(array($this, 'errorHandler'));
  855. }
  856. // Save the initial history record
  857. if ($this->logHistory) {
  858. $this->logID = db_insert('migrate_log')
  859. ->fields(array(
  860. 'machine_name' => $this->machineName,
  861. 'process_type' => $newStatus,
  862. 'starttime' => round(microtime(TRUE) * 1000),
  863. 'initialHighwater' => $this->getHighwater(),
  864. ))
  865. ->execute();
  866. }
  867. // If we're disabling any hooks, reset the static module_implements cache so
  868. // it is rebuilt with the specified hooks removed by our
  869. // hook_module_implements_alter(). By setting #write_cache to FALSE, we
  870. // ensure that our munged version of the hooks array does not get written
  871. // to the persistent cache and interfere with other Drupal processes.
  872. if (!empty($this->disableHooks)) {
  873. $implementations = &drupal_static('module_implements');
  874. $implementations = array();
  875. $implementations['#write_cache'] = FALSE;
  876. }
  877. }
  878. /**
  879. * End a rollback or import process, releasing the semaphore. Note that it must
  880. * be public to be callable as the shutdown function.
  881. */
  882. public function endProcess() {
  883. if ($this->previousErrorHandler) {
  884. set_error_handler($this->previousErrorHandler);
  885. $this->previousErrorHandler = NULL;
  886. }
  887. if ($this->processing) {
  888. $this->status = MigrationBase::STATUS_IDLE;
  889. $fields = array('class_name' => get_class($this), 'status' => MigrationBase::STATUS_IDLE);
  890. db_merge('migrate_status')
  891. ->key(array('machine_name' => $this->machineName))
  892. ->fields($fields)
  893. ->execute();
  894. // Complete the log record
  895. if ($this->logHistory) {
  896. try {
  897. db_merge('migrate_log')
  898. ->key(array('mlid' => $this->logID))
  899. ->fields(array(
  900. 'endtime' => round(microtime(TRUE) * 1000),
  901. 'finalhighwater' => $this->getHighwater(),
  902. 'numprocessed' => $this->total_processed,
  903. ))
  904. ->execute();
  905. }
  906. catch (PDOException $e) {
  907. Migration::displayMessage(t('Could not log operation on migration !name - possibly MigrationBase::beginProcess() was not called',
  908. array('!name' => $this->machineName)));
  909. }
  910. }
  911. $this->processing = FALSE;
  912. }
  913. self::$currentMigration = NULL;
  914. }
  915. /**
  916. * Signal that any current import or rollback process should end itself at
  917. * the earliest opportunity
  918. */
  919. public function stopProcess() {
  920. // Do not change the status of an idle migration
  921. db_update('migrate_status')
  922. ->fields(array('status' => MigrationBase::STATUS_STOPPING))
  923. ->condition('machine_name', $this->machineName)
  924. ->condition('status', MigrationBase::STATUS_IDLE, '<>')
  925. ->execute();
  926. }
  927. /**
  928. * Reset the status of the migration to IDLE (to be used when the status
  929. * gets stuck, e.g. if a process core-dumped)
  930. */
  931. public function resetStatus() {
  932. // Do not change the status of an already-idle migration
  933. db_update('migrate_status')
  934. ->fields(array('status' => MigrationBase::STATUS_IDLE))
  935. ->condition('machine_name', $this->machineName)
  936. ->condition('status', MigrationBase::STATUS_IDLE, '<>')
  937. ->execute();
  938. }
  939. /**
  940. * Perform an operation during the rollback phase.
  941. *
  942. * @param array $options
  943. * List of options provided (usually from a drush command). Specific to
  944. * the derived class.
  945. */
  946. public function processRollback(array $options = array()) {
  947. if ($this->enabled) {
  948. $return = MigrationBase::RESULT_COMPLETED;
  949. if (method_exists($this, 'rollback')) {
  950. $this->options = $options;
  951. if (!isset($options['force'])) {
  952. if (!$this->dependenciesComplete(TRUE)) {
  953. return MigrationBase::RESULT_SKIPPED;
  954. }
  955. }
  956. $this->beginProcess(MigrationBase::STATUS_ROLLING_BACK);
  957. try {
  958. $return = $this->rollback();
  959. }
  960. catch (Exception $exception) {
  961. // If something bad happened, make sure we clear the semaphore
  962. $this->endProcess();
  963. throw $exception;
  964. }
  965. $this->endProcess();
  966. }
  967. }
  968. else {
  969. $return = MigrationBase::RESULT_DISABLED;
  970. }
  971. return $return;
  972. }
  973. /**
  974. * Perform an operation during the import phase
  975. *
  976. * @param array $options
  977. * List of options provided (usually from a drush command). Specific to
  978. * the derived class.
  979. */
  980. public function processImport(array $options = array()) {
  981. if ($this->enabled) {
  982. $return = MigrationBase::RESULT_COMPLETED;
  983. if (method_exists($this, 'import')) {
  984. $this->options = $options;
  985. if (!isset($options['force']) || !$options['force']) {
  986. if (!$this->dependenciesComplete()) {
  987. return MigrationBase::RESULT_SKIPPED;
  988. }
  989. }
  990. $this->beginProcess(MigrationBase::STATUS_IMPORTING);
  991. try {
  992. $return = $this->import();
  993. }
  994. catch (Exception $exception) {
  995. // If something bad happened, make sure we clear the semaphore
  996. $this->endProcess();
  997. throw $exception;
  998. }
  999. if ($return == MigrationBase::RESULT_COMPLETED && isset($this->total_successes)) {
  1000. $time = microtime(TRUE) - $this->starttime;
  1001. if ($time > 0) {
  1002. $overallThroughput = round(60 * $this->total_successes / $time);
  1003. }
  1004. else {
  1005. $overallThroughput = 9999;
  1006. }
  1007. }
  1008. else {
  1009. $overallThroughput = 0;
  1010. }
  1011. $this->endProcess($overallThroughput);
  1012. }
  1013. }
  1014. else {
  1015. $return = MigrationBase::RESULT_DISABLED;
  1016. }
  1017. return $return;
  1018. }
  1019. /**
  1020. * Set the PHP time limit. This method may be called from batch callbacks
  1021. * before calling the processImport method.
  1022. */
  1023. public function setBatchTimeLimit() {
  1024. drupal_set_time_limit($this->batchTimeLimit);
  1025. }
  1026. /**
  1027. * A derived migration class does the actual rollback or import work in these
  1028. * methods - we cannot declare them abstract because some classes may define
  1029. * only one.
  1030. *
  1031. * abstract protected function rollback();
  1032. * abstract protected function import();
  1033. */
  1034. /**
  1035. * Test whether we've exceeded the desired memory threshold. If so, output a message.
  1036. *
  1037. * @return boolean
  1038. * TRUE if the threshold is exceeded, FALSE if not.
  1039. */
  1040. protected function memoryExceeded() {
  1041. $usage = memory_get_usage();
  1042. $pct_memory = $usage/$this->memoryLimit;
  1043. if ($pct_memory > $this->memoryThreshold) {
  1044. self::displayMessage(
  1045. t('Memory usage is !usage (!pct% of limit !limit), resetting statics',
  1046. array('!pct' => round($pct_memory*100),
  1047. '!usage' => format_size($usage),
  1048. '!limit' => format_size($this->memoryLimit))),
  1049. 'warning');
  1050. // First, try resetting Drupal's static storage - this frequently releases
  1051. // plenty of memory to continue
  1052. drupal_static_reset();
  1053. $usage = memory_get_usage();
  1054. $pct_memory = $usage/$this->memoryLimit;
  1055. // Use a lower threshold - we don't want to be in a situation where we keep
  1056. // coming back here and trimming a tiny amount
  1057. if ($pct_memory > (.90 * $this->memoryThreshold)) {
  1058. self::displayMessage(
  1059. t('Memory usage is now !usage (!pct% of limit !limit), not enough reclaimed, starting new batch',
  1060. array('!pct' => round($pct_memory*100),
  1061. '!usage' => format_size($usage),
  1062. '!limit' => format_size($this->memoryLimit))),
  1063. 'warning');
  1064. return TRUE;
  1065. }
  1066. else {
  1067. self::displayMessage(
  1068. t('Memory usage is now !usage (!pct% of limit !limit), reclaimed enough, continuing',
  1069. array('!pct' => round($pct_memory*100),
  1070. '!usage' => format_size($usage),
  1071. '!limit' => format_size($this->memoryLimit))),
  1072. 'warning');
  1073. return FALSE;
  1074. }
  1075. }
  1076. else {
  1077. return FALSE;
  1078. }
  1079. }
  1080. /**
  1081. * Test whether we're approaching the PHP time limit.
  1082. *
  1083. * @return boolean
  1084. * TRUE if the threshold is exceeded, FALSE if not.
  1085. */
  1086. protected function timeExceeded() {
  1087. if ($this->timeLimit == 0) {
  1088. return FALSE;
  1089. }
  1090. $time_elapsed = time() - REQUEST_TIME;
  1091. $pct_time = $time_elapsed / $this->timeLimit;
  1092. if ($pct_time > $this->timeThreshold) {
  1093. return TRUE;
  1094. }
  1095. else {
  1096. return FALSE;
  1097. }
  1098. }
  1099. /**
  1100. * Test whether we've exceeded the designated time limit.
  1101. *
  1102. * @return boolean
  1103. * TRUE if the threshold is exceeded, FALSE if not.
  1104. */
  1105. protected function timeOptionExceeded() {
  1106. if (!$timelimit = $this->getTimeLimit()) {
  1107. return FALSE;
  1108. }
  1109. $time_elapsed = time() - REQUEST_TIME;
  1110. if ($time_elapsed >= $timelimit) {
  1111. return TRUE;
  1112. }
  1113. else {
  1114. return FALSE;
  1115. }
  1116. }
  1117. /**
  1118. * Encrypt an incoming value. Detects for existence of the Drupal 'Encrypt'
  1119. * module or the mcrypt PHP extension.
  1120. *
  1121. * @param string $value
  1122. * @return string The encrypted value.
  1123. */
  1124. static public function encrypt($value) {
  1125. if (module_exists('encrypt')) {
  1126. $value = encrypt($value);
  1127. }
  1128. else if (extension_loaded('mcrypt')) {
  1129. // Mimic encrypt module to ensure compatibility
  1130. $key = drupal_substr(variable_get('drupal_private_key', 'no_key'), 0, 32);
  1131. $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
  1132. $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
  1133. $value = mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $value,
  1134. MCRYPT_MODE_ECB, $iv);
  1135. $encryption_array['text'] = $value;
  1136. // For forward compatibility with the encrypt module.
  1137. $encryption_array['method'] = 'mcrypt_rij_256';
  1138. $encryption_array['key_name'] = 'drupal_private_key';
  1139. $value = serialize($encryption_array);
  1140. }
  1141. else {
  1142. if (self::$showEncryptionWarning) {
  1143. MigrationBase::displayMessage(t('Encryption of secure migration information is not supported. Ensure the <a href="@encrypt">Encrypt module</a> or <a href="mcrypt">mcrypt PHP extension</a> is installed for this functionality.',
  1144. array(
  1145. '@encrypt' => 'http://drupal.org/project/encrypt',
  1146. '@mcrypt' => 'http://php.net/manual/en/book.mcrypt.php',
  1147. )
  1148. ),
  1149. 'warning');
  1150. self::$showEncryptionWarning = FALSE;
  1151. }
  1152. }
  1153. return $value;
  1154. }
  1155. /**
  1156. * Decrypt an incoming value.
  1157. *
  1158. * @param string $value
  1159. * @return string The encrypted value
  1160. */
  1161. static public function decrypt($value) {
  1162. if (module_exists('encrypt')) {
  1163. $value = decrypt($value);
  1164. }
  1165. else if (extension_loaded('mcrypt')) {
  1166. // Mimic encrypt module to ensure compatibility
  1167. $encryption_array = unserialize($value);
  1168. $method = $encryption_array['method']; // Not used right now
  1169. $text = $encryption_array['text'];
  1170. $key_name = $encryption_array['key_name']; // Not used right now
  1171. $iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_256, MCRYPT_MODE_ECB);
  1172. $iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
  1173. $key = drupal_substr(variable_get('drupal_private_key', 'no_key'), 0, 32);
  1174. $value = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $text,
  1175. MCRYPT_MODE_ECB, $iv);
  1176. }
  1177. else {
  1178. if (self::$showEncryptionWarning) {
  1179. MigrationBase::displayMessage(t('Encryption of secure migration information is not supported. Ensure the <a href="@encrypt">Encrypt module</a> or <a href="mcrypt">mcrypt PHP extension</a> is installed for this functionality.',
  1180. array(
  1181. '@encrypt' => 'http://drupal.org/project/encrypt',
  1182. '@mcrypt' => 'http://php.net/manual/en/book.mcrypt.php',
  1183. )
  1184. ),
  1185. 'warning');
  1186. self::$showEncryptionWarning = FALSE;
  1187. }
  1188. }
  1189. return $value;
  1190. }
  1191. /**
  1192. * Make sure any arguments we want to be encrypted get encrypted.
  1193. *
  1194. * @param array $arguments
  1195. *
  1196. * @return array
  1197. */
  1198. static public function encryptArguments(array $arguments) {
  1199. if (isset($arguments['encrypted_arguments'])) {
  1200. foreach ($arguments['encrypted_arguments'] as $argument_name) {
  1201. if (isset($arguments[$argument_name])) {
  1202. $arguments[$argument_name] = self::encrypt(
  1203. serialize($arguments[$argument_name]));
  1204. }
  1205. }
  1206. }
  1207. return $arguments;
  1208. }
  1209. /**
  1210. * Make sure any arguments we want to be decrypted get decrypted.
  1211. *
  1212. * @param array $arguments
  1213. *
  1214. * @return array
  1215. */
  1216. static public function decryptArguments(array $arguments) {
  1217. if (isset($arguments['encrypted_arguments'])) {
  1218. foreach ($arguments['encrypted_arguments'] as $argument_name) {
  1219. if (isset($arguments[$argument_name])) {
  1220. $decrypted_string = self::decrypt($arguments[$argument_name]);
  1221. // A decryption failure will return FALSE and issue a notice. We need
  1222. // to distinguish a failure from a serialized FALSE.
  1223. $unserialized_value = @unserialize($decrypted_string);
  1224. if ($unserialized_value === FALSE && $decrypted_string != serialize(FALSE)) {
  1225. self::displayMessage(t('Failed to decrypt argument %argument_name',
  1226. array('%argument_name' => $argument_name)));
  1227. unset($arguments[$argument_name]);
  1228. }
  1229. else {
  1230. $arguments[$argument_name] = $unserialized_value;
  1231. }
  1232. }
  1233. }
  1234. }
  1235. return $arguments;
  1236. }
  1237. /**
  1238. * Convert an incoming string (which may be a UNIX timestamp, or an arbitrarily-formatted
  1239. * date/time string) to a UNIX timestamp.
  1240. *
  1241. * @param string $value
  1242. */
  1243. static public function timestamp($value) {
  1244. // Does it look like it's already a timestamp? Just return it
  1245. if (is_numeric($value)) {
  1246. return $value;
  1247. }
  1248. // Default empty values to now
  1249. if (empty($value)) {
  1250. return time();
  1251. }
  1252. $date = new DateTime($value);
  1253. $time = $date->format('U');
  1254. if ($time == FALSE) {
  1255. // Handles form YYYY-MM-DD HH:MM:SS.garbage
  1256. if (drupal_strlen($value) > 19) {
  1257. $time = strtotime(drupal_substr($value, 0, 19));
  1258. }
  1259. }
  1260. return $time;
  1261. }
  1262. /**
  1263. * Saves the current mail system, or set a system default if there is none.
  1264. */
  1265. protected function saveMailSystem() {
  1266. global $conf;
  1267. if (empty($conf['mail_system'])) {
  1268. $conf['mail_system']['default-system'] = 'MigrateMailIgnore';
  1269. }
  1270. else {
  1271. $this->mailSystem = $conf['mail_system'];
  1272. }
  1273. }
  1274. /**
  1275. * Disables mail system to prevent emails from being sent during migrations.
  1276. */
  1277. public function disableMailSystem() {
  1278. global $conf;
  1279. if (!empty($conf['mail_system'])) {
  1280. foreach ($conf['mail_system'] as $system => $class) {
  1281. $conf['mail_system'][$system] = 'MigrateMailIgnore';
  1282. }
  1283. }
  1284. }
  1285. /**
  1286. * Restores the original saved mail system for migrations that require it.
  1287. */
  1288. public function restoreMailSystem() {
  1289. global $conf;
  1290. $conf['mail_system'] = $this->mailSystem;
  1291. }
  1292. }
  1293. // Make sure static members (in particular, $displayFunction) get
  1294. // initialized even if there are no class instances.
  1295. MigrationBase::staticInitialize();