migrate.drush.inc 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527
  1. <?php
  2. /**
  3. * @file
  4. * Drush support for the migrate module
  5. */
  6. /**
  7. * Implements hook_drush_command().
  8. */
  9. function migrate_drush_command() {
  10. $migration_options = array(
  11. 'limit' => 'Limit on the length of each migration process, expressed in seconds or number of items',
  12. 'feedback' => 'Frequency of progress messages, in seconds or items processed',
  13. 'idlist' => 'A comma delimited list of ids to import or rollback. If unspecified, migrate imports all pending items or rolls back all items for the content set.',
  14. 'all' => 'Process all migrations that come after the specified migration. If no value is supplied, all migrations are processed.',
  15. 'instrument' => 'Capture performance information (timer, memory, or all)',
  16. 'force' => 'Force an operation to run, even if all dependencies are not satisfied',
  17. 'group' => 'Name of the migration group to run',
  18. 'notify' => 'Send email notification upon completion of operation',
  19. 'wildcard' => 'Process migrations that match a certain pattern. For example, Content*.',
  20. );
  21. $items['migrate-status'] = array(
  22. 'description' => 'List all migrations with current status.',
  23. 'options' => array(
  24. 'refresh' => 'Recognize new migrations and update counts',
  25. 'group' => 'Name of the migration group to list',
  26. 'names-only' => 'Only return names, not all the details (faster)',
  27. ),
  28. 'arguments' => array(
  29. 'migration' => 'Restrict to a single migration. Optional',
  30. ),
  31. 'examples' => array(
  32. 'migrate-status' => 'Retrieve status for all migrations',
  33. 'migrate-status BeerNode' => 'Retrieve status for just one migration',
  34. ),
  35. 'drupal dependencies' => array('migrate'),
  36. 'aliases' => array('ms'),
  37. );
  38. $items['migrate-fields-destination'] = array(
  39. 'description' => 'List the fields available for mapping in a destination.',
  40. 'options' => array(
  41. 'all' => $migration_options['all'],
  42. 'group' => $migration_options['group'],
  43. ),
  44. 'arguments' => array(
  45. 'migration' => 'Name of the migration or destination class to query for fields',
  46. ),
  47. 'examples' => array(
  48. 'migrate-fields-destination MyNode' => 'List fields for the destination in the MyNode migration',
  49. ),
  50. 'drupal dependencies' => array('migrate'),
  51. 'aliases' => array('mfd'),
  52. );
  53. $items['migrate-fields-source'] = array(
  54. 'description' => 'List the fields available for mapping from a source.',
  55. 'arguments' => array(
  56. 'migration' => 'Name of the migration or destination class to query for fields',
  57. ),
  58. 'options' => array(
  59. 'all' => $migration_options['all'],
  60. 'group' => $migration_options['group'],
  61. ),
  62. 'examples' => array(
  63. 'migrate-fields-destination MyNode' => 'List fields in the source query for the MyNode migration',
  64. ),
  65. 'drupal dependencies' => array('migrate'),
  66. 'aliases' => array('mfs'),
  67. );
  68. $items['migrate-mappings'] = array(
  69. 'description' => 'View information on all field mappings in a migration.',
  70. 'options' => array(
  71. 'all' => $migration_options['all'],
  72. 'group' => $migration_options['group'],
  73. 'csv' => 'Export information as a CSV',
  74. 'full' => 'Include more information on each mapping',
  75. ),
  76. 'arguments' => array(
  77. 'migration' => 'Name of the migration',
  78. ),
  79. 'examples' => array(
  80. 'migrate-mappings MyNode' => 'Show mappings for the MyNode migration',
  81. 'migrate-mappings MyNode --csv --full' => 'Export full mapping information in CSV format',
  82. ),
  83. 'drupal dependencies' => array('migrate'),
  84. 'aliases' => array('mm'),
  85. );
  86. $items['migrate-messages'] = array(
  87. 'description' => 'View any messages associated with a migration.',
  88. 'options' => array(
  89. 'csv' => 'Export messages as a CSV',
  90. ),
  91. 'arguments' => array(
  92. 'migration' => 'Name of the migration',
  93. ),
  94. 'examples' => array(
  95. 'migrate-messages MyNode' => 'Show all messages for the MyNode migration',
  96. ),
  97. 'drupal dependencies' => array('migrate'),
  98. 'aliases' => array('mmsg'),
  99. );
  100. $items['migrate-analyze'] = array(
  101. 'description' => 'Analyze the source fields for a migration.',
  102. 'options' => array(
  103. 'all' => $migration_options['all'],
  104. 'group' => $migration_options['group'],
  105. ),
  106. 'arguments' => array(
  107. 'migration' => 'Name of the migration',
  108. ),
  109. 'examples' => array(
  110. 'migrate-analyze MyNode' => 'Report on field values for the MyNode migration',
  111. ),
  112. 'drupal dependencies' => array('migrate'),
  113. 'aliases' => array('maz'),
  114. );
  115. $items['migrate-audit'] = array(
  116. 'description' => 'View information on problems in a migration.',
  117. 'options' => array(
  118. 'all' => $migration_options['all'],
  119. 'group' => $migration_options['group'],
  120. ),
  121. 'arguments' => array(
  122. 'migration' => 'Name of the migration',
  123. ),
  124. 'examples' => array(
  125. 'migrate-audit MyNode' => 'Report on problems in the MyNode migration',
  126. ),
  127. 'drupal dependencies' => array('migrate'),
  128. 'aliases' => array('ma'),
  129. );
  130. $items['migrate-rollback'] = array(
  131. 'description' => 'Roll back the destination objects from a given migration',
  132. 'options' => $migration_options,
  133. 'arguments' => array(
  134. 'migration' => 'Name of migration(s) to roll back. Delimit multiple using commas.',
  135. ),
  136. 'examples' => array(
  137. 'migrate-rollback Article' => 'Roll back the article migration',
  138. 'migrate-rollback Article --idlist=4,9' => 'Roll back two articles. The ids refer to the value of the primary key in base table',
  139. 'migrate-rollback User --limit="50 items"' =>
  140. 'Roll back up to 50 items from the migration named User',
  141. 'migrate-rollback User --feedback="60 seconds"' => 'Display a progress message every 60 seconds or less',
  142. ),
  143. 'drupal dependencies' => array('migrate'),
  144. 'aliases' => array('mr'),
  145. );
  146. $migration_options['update'] = 'In addition to processing unprocessed items from the source, update previously-imported items with new data';
  147. $migration_options['needs-update'] =
  148. 'Reimport up to 10K records where needs_update=1. This option is only needed when your Drupal DB is on a different DB server from your source data. Otherwise, these records get migrated with just migrate-import.';
  149. $migration_options['stop'] = 'Stop specified migration(s) if applicable.';
  150. $migration_options['rollback'] = 'Rollback specified migration(s) if applicable.';
  151. $migration_options['file_function'] = 'Override file function to use when migrating images.';
  152. $migration_options['ignore-highwater'] = 'Ignore the highwater field during migration';
  153. $items['migrate-import'] = array(
  154. 'description' => 'Perform one or more migration processes',
  155. 'options' => $migration_options,
  156. 'arguments' => array(
  157. 'migration' => 'Name of migration(s) to import. Delimit multiple using commas.',
  158. ),
  159. 'examples' => array(
  160. 'migrate-import Article' => 'Import new articles',
  161. 'migrate-import Article --update' => 'Import new items, and also update previously-imported items',
  162. 'migrate-import Article --idlist=4,9' => 'Import two specific articles. The ids refer to the value of the primary key in base table',
  163. 'migrate-import Article --idlist=450:pasta,451' => 'Import two specific articles. A colon can be used to separate parts of compound keys; otherwise, compound keys match by the first key field.',
  164. 'migrate-import Article --limit="60 seconds" --stop --rollback' =>
  165. 'Import for up to 60 seconds after stopping and rolling back the Article migration.',
  166. 'migrate-import Article --limit="100 items"' =>
  167. 'Import up to 100 items from the migration named Article.',
  168. 'migrate-import User --feedback="1000 items"' => 'Display a progress message every 1000 processed items or less',
  169. 'migrate-import --all=User' => 'Perform User migrations and all that follow it.',
  170. ),
  171. 'drupal dependencies' => array('migrate'),
  172. 'aliases' => array('mi'),
  173. );
  174. $items['migrate-stop'] = array(
  175. 'description' => 'Stop an active migration operation',
  176. 'options' => array('all' => 'Stop all active migration operations',
  177. 'group' => 'Name of a specific migration group to stop'),
  178. 'arguments' => array(
  179. 'migration' => 'Name of migration to stop',
  180. ),
  181. 'examples' => array(
  182. 'migrate-stop Article' => 'Stop any active operation on the Article migration',
  183. 'migrate-stop --all' => 'Stop all active migration operations',
  184. ),
  185. 'drupal dependencies' => array('migrate'),
  186. 'aliases' => array('mst'),
  187. );
  188. $items['migrate-reset-status'] = array(
  189. 'description' => 'Reset a active migration\'s status to idle',
  190. 'options' => array('all' => 'Reset all active migration operations'),
  191. 'arguments' => array(
  192. 'migration' => 'Name of migration to reset',
  193. ),
  194. 'examples' => array(
  195. 'migrate-reset-status Article' => 'Reset any active operation on the Article migration',
  196. 'migrate-reset-status --all' => 'Reset all active migration operations',
  197. ),
  198. 'drupal dependencies' => array('migrate'),
  199. 'aliases' => array('mrs'),
  200. );
  201. $items['migrate-deregister'] = array(
  202. 'description' => 'Remove all tracking of a migration',
  203. 'options' => array(
  204. 'orphans' => 'Remove tracking for any migrations whose implementing class no longer exists',
  205. 'group' => 'Remove tracking of a migration group, and any migrations assigned to it',
  206. ),
  207. 'arguments' => array(
  208. 'migration' => 'Name of migration to deregister',
  209. ),
  210. 'examples' => array(
  211. 'migrate-deregister Article' => 'Deregister the Article migration',
  212. 'migrate-deregister --orphans' => 'Deregister any no-longer-implemented migrations',
  213. 'migrate-deregister --group=myblog' => 'Deregister the myblog group and all migrations within it',
  214. ),
  215. 'drupal dependencies' => array('migrate'),
  216. );
  217. $items['migrate-auto-register'] = array(
  218. 'description' => 'Register any newly defined migration classes',
  219. 'drupal dependencies' => array('migrate'),
  220. 'aliases' => array('mar'),
  221. );
  222. $items['migrate-register'] = array(
  223. 'description' => 'Register or reregister any statically defined migrations',
  224. 'drupal dependencies' => array('migrate'),
  225. 'aliases' => array('mreg'),
  226. );
  227. $items['migrate-wipe'] = array(
  228. 'description' => 'Delete all nodes from specified content types.',
  229. 'examples' => array(
  230. "migrate-wipe story article" => 'Delete all story and article nodes.',
  231. ),
  232. 'arguments' => array(
  233. 'type' => 'A space delimited list of content type machine readable Ids.',
  234. ),
  235. 'drupal dependencies' => array('migrate'),
  236. 'aliases' => array('mw'),
  237. );
  238. return $items;
  239. }
  240. /**
  241. * Get the value of all migrate related options. Used when spawning a subshell.
  242. * Don't pass along all, stop, update, and rollback options.
  243. *
  244. * @return
  245. * An array of command specific options and their values.
  246. */
  247. function drush_migrate_get_options() {
  248. $options = array();
  249. $blacklist = array('stop', 'rollback', 'update', 'all', 'group');
  250. $command = drush_parse_command();
  251. $global_options = drush_get_global_options();
  252. $opts = array_merge($command['options'], $global_options);
  253. foreach ($opts as $key => $value) {
  254. // Strip leading --
  255. $key = ltrim($key, '-');
  256. if (!in_array($key, $blacklist)) {
  257. $value = drush_get_option($key);
  258. if (isset($value)) {
  259. $options[$key] = $value;
  260. }
  261. }
  262. }
  263. return $options;
  264. }
  265. /*
  266. * Spawn a subshell which runs the same command we are currently running.
  267. */
  268. function drush_migrate_invoke_process($migrations = '') {
  269. $args = drush_get_arguments();
  270. $options = drush_migrate_get_options();
  271. if (intval(DRUSH_MAJOR_VERSION) < 4) {
  272. // @todo: use drush_backend_invoke_args() as per http://drupal.org/node/658420.
  273. return drush_backend_invoke(implode(' ', $args), $options);
  274. }
  275. else {
  276. // $args[0] is the command name, $args[1] is the list of migrations.
  277. if (empty($migrations)) {
  278. $command_args = array($args[1]);
  279. }
  280. else {
  281. $command_args = array($migrations);
  282. }
  283. $return = drush_invoke_process('@self', $args[0], $command_args, $options);
  284. return $return;
  285. }
  286. }
  287. /**
  288. * A simplified version of the dashboard page.
  289. */
  290. function drush_migrate_status($name = NULL) {
  291. try {
  292. $refresh = drush_get_option('refresh');
  293. $group_option = drupal_strtolower(drush_get_option('group'));
  294. $names_only = drush_get_option('names-only');
  295. // Validate input and load Migration(s).
  296. if ($name) {
  297. if ($migration = MigrationBase::getInstance($name)) {
  298. $migrations = array($migration);
  299. }
  300. else {
  301. return drush_set_error(dt('Unrecognized migration: !cn', array('!cn' => $name)));
  302. }
  303. }
  304. else {
  305. $migrations = migrate_migrations();
  306. }
  307. $groups = MigrateGroup::groups();
  308. $table = array();
  309. foreach ($groups as $group) {
  310. if ($group_option && drupal_strtolower($group->getName()) != $group_option) {
  311. continue;
  312. }
  313. $group_members_count = 0;
  314. foreach ($migrations as $migration) {
  315. if ($migration->getGroup() != $group) {
  316. // This migration is not from this group.
  317. continue;
  318. }
  319. ++$group_members_count;
  320. if ($group_members_count == 1) {
  321. // An empty line and the headers.
  322. $table[] = array('');
  323. if ($names_only) {
  324. $table[] = array(dt('Group: !name',
  325. array('!name' => $group->getName())));
  326. }
  327. else {
  328. $table[] = array(dt('Group: !name',
  329. array('!name' => $group->getName())), dt('Total'), dt('Imported'),
  330. dt('Unprocessed'), dt('Status'), dt('Last imported'));
  331. }
  332. }
  333. if (!$names_only) {
  334. $has_counts = TRUE;
  335. if (method_exists($migration, 'sourceCount')) {
  336. $total = $migration->sourceCount($refresh);
  337. if ($total < 0) {
  338. $has_counts = FALSE;
  339. $total = dt('N/A');
  340. }
  341. }
  342. else {
  343. $has_counts = FALSE;
  344. $total = dt('N/A');
  345. }
  346. if (method_exists($migration, 'importedCount')) {
  347. $imported = $migration->importedCount();
  348. $processed = $migration->processedCount();
  349. }
  350. else {
  351. $has_counts = FALSE;
  352. $imported = dt('N/A');
  353. }
  354. if ($has_counts) {
  355. $unimported = $total - $processed;
  356. }
  357. else {
  358. $unimported = dt('N/A');
  359. }
  360. $status = $migration->getStatus();
  361. switch ($status) {
  362. case MigrationBase::STATUS_IDLE:
  363. $status = dt('Idle');
  364. break;
  365. case MigrationBase::STATUS_IMPORTING:
  366. $status = dt('Importing');
  367. break;
  368. case MigrationBase::STATUS_ROLLING_BACK:
  369. $status = dt('Rolling back');
  370. break;
  371. case MigrationBase::STATUS_STOPPING:
  372. $status = dt('Stopping');
  373. break;
  374. case MigrationBase::STATUS_DISABLED:
  375. $status = dt('Disabled');
  376. break;
  377. default:
  378. $status = dt('Unknown');
  379. break;
  380. }
  381. $table[] = array($migration->getMachineName(), $total, $imported, $unimported, $status, $migration->getLastImported());
  382. }
  383. else {
  384. $table[] = array($migration->getMachineName());
  385. }
  386. }
  387. }
  388. drush_print_table($table);
  389. }
  390. catch (MigrateException $e) {
  391. drush_print($e->getMessage());
  392. exit;
  393. }
  394. }
  395. // TODO: Use drush_choice for detailed field info
  396. function drush_migrate_fields_destination($args = NULL) {
  397. try {
  398. $migrations = drush_migrate_get_migrations($args);
  399. foreach ($migrations as $name => $migration) {
  400. drush_print("\n" . dt('@migration Destination Fields', array('@migration' => $name)) . "\n");
  401. $destination = $migration->getDestination();
  402. if (method_exists($destination, 'fields')) {
  403. $table = array();
  404. foreach ($destination->fields($migration) as $machine_name => $description) {
  405. $table[] = array(strip_tags($description), $machine_name);
  406. }
  407. drush_print_table($table);
  408. }
  409. else {
  410. drush_print(dt('No fields were found.'));
  411. }
  412. }
  413. }
  414. catch (MigrateException $e) {
  415. drush_print($e->getMessage());
  416. exit;
  417. }
  418. }
  419. function drush_migrate_fields_source($args = NULL) {
  420. try {
  421. $migrations = drush_migrate_get_migrations($args);
  422. foreach ($migrations as $name => $migration) {
  423. drush_print("\n" . dt('@migration Source Fields', array('@migration' => $name)) . "\n");
  424. $source = $migration->getSource();
  425. if (method_exists($source, 'fields')) {
  426. $table = array();
  427. foreach ($source->fields() as $machine_name => $description) {
  428. $table[] = array(strip_tags($description), $machine_name);
  429. }
  430. drush_print_table($table);
  431. }
  432. else {
  433. drush_print(dt('No fields were found.'));
  434. }
  435. }
  436. }
  437. catch (MigrateException $e) {
  438. drush_print($e->getMessage());
  439. exit;
  440. }
  441. }
  442. /**
  443. * Display field mappings for a migration.
  444. */
  445. function drush_migrate_mappings($args = NULL) {
  446. try {
  447. $full = drush_get_option('full');
  448. $migrations = drush_migrate_get_migrations($args);
  449. foreach ($migrations as $name => $migration) {
  450. drush_print("\n" . dt('@migration Mappings', array('@migration' => $name)) . "\n");
  451. // In verbose mode, we'll also get source and destination field descriptions
  452. if ($full) {
  453. $destination = $migration->getDestination();
  454. $dest_descriptions = array();
  455. if (method_exists($destination, 'fields')) {
  456. foreach ($destination->fields($migration) as $machine_name => $description) {
  457. if (is_array($description)) {
  458. $description = reset($description);
  459. }
  460. $dest_descriptions[$machine_name] = strip_tags($description);
  461. }
  462. }
  463. $source = $migration->getSource();
  464. $src_descriptions = array();
  465. if (method_exists($source, 'fields')) {
  466. foreach ($source->fields() as $machine_name => $description) {
  467. if (is_array($description)) {
  468. $description = reset($description);
  469. }
  470. $src_descriptions[$machine_name] = strip_tags($description);
  471. }
  472. }
  473. }
  474. if (method_exists($migration, 'getFieldMappings')) {
  475. // First group the mappings. We want "interesting" mappings first, so
  476. // put the boring Done and DNM mappings last.
  477. $descriptions = array();
  478. $done = array();
  479. $dnm = array();
  480. foreach ($migration->getFieldMappings() as $mapping) {
  481. $group = $mapping->getIssueGroup();
  482. $lowergroup = drupal_strtolower($group);
  483. if ($lowergroup == dt('done')) {
  484. $done[$group][] = $mapping;
  485. }
  486. elseif ($lowergroup == dt('dnm') || $lowergroup == dt('do not migrate')) {
  487. $dnm[$group][] = $mapping;
  488. }
  489. else {
  490. $descriptions[$group][] = $mapping;
  491. }
  492. }
  493. $descriptions = array_merge($descriptions, $done, $dnm);
  494. // Put out each group header
  495. $table = array();
  496. if ($full) {
  497. $table[] = array(dt('Destination'), dt(''), dt('Source'), dt(''), dt('Default'),
  498. dt('Description'));
  499. }
  500. else {
  501. $table[] = array(dt('Destination'), dt('Source'), dt('Default'),
  502. dt('Description'));
  503. }
  504. $first = TRUE;
  505. foreach ($descriptions as $group => $mappings) {
  506. if ($first) {
  507. $first = FALSE;
  508. }
  509. else {
  510. $table[] = array(' ');
  511. }
  512. // Attempt to highlight the group header a bit so it stands out
  513. $group_header = '--- ' . drupal_strtoupper($group) . ' ---';
  514. $table[] = array($group_header);
  515. foreach ($mappings as $mapping) {
  516. if (is_array($mapping->getDefaultValue())) {
  517. $default = implode(',', $mapping->getDefaultValue());
  518. }
  519. else {
  520. $default = $mapping->getDefaultValue();
  521. }
  522. $destination = $mapping->getDestinationField();
  523. $source = $mapping->getSourceField();
  524. if ($full) {
  525. if ($destination && $dest_descriptions[$destination]) {
  526. $dest_description = $dest_descriptions[$destination];
  527. }
  528. else {
  529. $dest_description = '';
  530. }
  531. if ($source && $src_descriptions[$source]) {
  532. $src_description = $src_descriptions[$source];
  533. }
  534. else {
  535. $src_description = '';
  536. }
  537. $table[] = array($destination, $dest_description, $source, $src_description,
  538. $default, $mapping->getDescription());
  539. }
  540. else {
  541. $table[] = array($destination, $source,
  542. $default, $mapping->getDescription());
  543. }
  544. }
  545. }
  546. if (drush_get_option('csv')) {
  547. foreach ($table as $row) {
  548. fputcsv(STDOUT, $row);
  549. }
  550. }
  551. else {
  552. drush_print_table($table, TRUE);
  553. }
  554. }
  555. }
  556. }
  557. catch (MigrateException $e) {
  558. drush_print($e->getMessage());
  559. exit;
  560. }
  561. }
  562. /**
  563. * Display messages for a migration.
  564. */
  565. function drush_migrate_messages($migration_name) {
  566. if (!trim($migration_name)) {
  567. drush_log(dt('You must specify a migration name'), 'status');
  568. return;
  569. }
  570. try {
  571. $migration = MigrationBase::getInstance($migration_name);
  572. if (is_a($migration, 'Migration')) {
  573. $map = $migration->getMap();
  574. $message_table = $map->getMessageTable();
  575. $result = db_select($message_table, 'msg', array('fetch' => PDO::FETCH_ASSOC))
  576. ->fields('msg')
  577. ->execute();
  578. $first = TRUE;
  579. $table = array();
  580. foreach ($result as $row) {
  581. unset($row['msgid']);
  582. unset($row['level']);
  583. if ($first) {
  584. $table[] = array_keys($row);
  585. $first = FALSE;
  586. }
  587. $table[] = $row;
  588. }
  589. }
  590. if (empty($table)) {
  591. drush_log(dt('No messages for this migration'), 'status');
  592. }
  593. else {
  594. if (drush_get_option('csv')) {
  595. foreach ($table as $row) {
  596. fputcsv(STDOUT, $row);
  597. }
  598. }
  599. else {
  600. $widths = array();
  601. foreach ($table[0] as $header) {
  602. $widths[] = strlen($header) + 1;
  603. }
  604. drush_print_table($table, TRUE, $widths);
  605. }
  606. }
  607. }
  608. catch (MigrateException $e) {
  609. drush_print($e->getMessage());
  610. exit;
  611. }
  612. }
  613. /**
  614. * Analyze the source fields for any passed migrations.
  615. */
  616. function drush_migrate_analyze($args = NULL) {
  617. $migrations = drush_migrate_get_migrations($args);
  618. foreach ($migrations as $name => $migration) {
  619. // "Migrations" derived from MigrationBase won't have an analyze method.
  620. if (method_exists($migration, 'analyze')) {
  621. drush_print("\n" . dt('Analyzing @migration', array('@migration' => $name)) . "\n");
  622. $analysis = $migration->analyze();
  623. if (!empty($analysis)) {
  624. foreach ($analysis as $field_name => $details) {
  625. if (!empty($details['description'])) {
  626. drush_print(dt('@name (@description):', array('@name' => $field_name,
  627. '@description' => $details['description'])));
  628. }
  629. else {
  630. drush_print(dt('@name:', array('@name' => $field_name)));
  631. }
  632. // Special handling in degenerate cases
  633. if (count($details['distinct_values']) == 1) {
  634. $value = trim(reset(array_keys($details['distinct_values'])));
  635. if ($value === '') {
  636. drush_print(' ' . dt('The field is always empty'));
  637. }
  638. else {
  639. drush_print(' ' . dt('Only one value present: @value',
  640. array('@value' => $value)));
  641. }
  642. }
  643. else {
  644. if ($details['is_numeric']) {
  645. drush_print(' ' . dt('Numeric field with a range of @min to @max',
  646. array('@min' => $details['min_numeric'], '@max' => $details['max_numeric'])));
  647. }
  648. else {
  649. drush_print(' ' . dt('String field with a length ranging from @min to @max',
  650. array('@min' => $details['min_strlen'], '@max' => $details['max_strlen'])));
  651. }
  652. $values = array();
  653. $header = NULL;
  654. // If the max of 10 tracked distinct values was reached, we assume
  655. // there are many values and treat them as samples. Under 10 this
  656. // may be an enumerated field, show all values with their counts.
  657. if (count($details['distinct_values']) < 10) {
  658. drush_print(' ' . dt('Distinct values:'));
  659. $header = array('', dt('Value'), dt('Count'));
  660. $values[] = $header;
  661. }
  662. else {
  663. drush_print(' ' . dt('Sample values:'));
  664. }
  665. ksort($details['distinct_values']);
  666. foreach ($details['distinct_values'] as $value => $count) {
  667. // Truncate long strings
  668. $value = substr($value, 0, 60);
  669. if (strlen($value) == 60) {
  670. $value .= dt('...');
  671. }
  672. $row = array(' ', $value);
  673. if (count($details['distinct_values']) < 10) {
  674. $row[] = $count;
  675. }
  676. $values[] = $row;
  677. }
  678. // No header for sample values.
  679. drush_print_table($values, !is_null($header));
  680. }
  681. }
  682. }
  683. }
  684. }
  685. }
  686. /**
  687. * Display field mappings for a migration.
  688. */
  689. function drush_migrate_audit($args = NULL) {
  690. try {
  691. $problem_descriptions = array(
  692. 'wtf' => dt("Probably an incomplete migration:"),
  693. 'noted_issues' => dt("Noted as an issue:"),
  694. // I wish drush had dformat_plural().
  695. 'sources_unmapped' => dt("Source(s) not used in a mapping:"),
  696. 'sources_missing' => dt("Used as source field in mapping but not in source field list:"),
  697. 'destinations_unmapped' => dt("Destination(s) not used in a mapping:"),
  698. 'destinations_missing' => dt("Used as destination field in mapping but not in destination field list:"),
  699. );
  700. drush_print("Auditing migrations");
  701. $migrations = drush_migrate_get_migrations($args);
  702. foreach ($migrations as $name => $migration) {
  703. $problems = array();
  704. foreach ($problem_descriptions as $key => $description) {
  705. $problems[$key] = array();
  706. }
  707. drush_print("\n" . dt('@migration', array('@migration' => $name)) . "\n");
  708. if (!method_exists($migration, 'getSource') || !($source = $migration->getSource())) {
  709. $problems['wtf'][] = dt('Missing a source');
  710. $source_fields = array();
  711. }
  712. else {
  713. $source_fields = $source->fields();
  714. }
  715. if (!method_exists($migration, 'getDestination') || !($destination = $migration->getDestination())) {
  716. $problems['wtf'][] = dt('Missing a destination');
  717. $destination_fields = array();
  718. }
  719. else {
  720. $destination_fields = $destination->fields($migration);
  721. }
  722. if (!method_exists($migration, 'getFieldMappings')) {
  723. $problems['wtf'][] = dt('Missing field mappings');
  724. $field_mappings = array();
  725. }
  726. else {
  727. $field_mappings = $migration->getFieldMappings();
  728. }
  729. $used_sources = array();
  730. $used_destinations = array();
  731. foreach ($field_mappings as $mapping) {
  732. $source_field = $mapping->getSourceField();
  733. $destination_field = $mapping->getDestinationField();
  734. $used_sources[$source_field] = TRUE;
  735. $used_destinations[$destination_field] = TRUE;
  736. $issue_priority = $mapping->getIssuePriority();
  737. if (!is_null($issue_priority) && $issue_priority != MigrateFieldMapping::ISSUE_PRIORITY_OK) {
  738. $problems['noted_issues'][] = array(
  739. dt('Source') => $source_field,
  740. dt('Destination') => $destination_field,
  741. dt('Priority') => MigrateFieldMapping::$priorities[$issue_priority],
  742. dt('Description') => $mapping->getDescription(),
  743. );
  744. }
  745. // Validate source and destination fields actually exist
  746. if (!is_null($source_field) && !isset($source_fields[$source_field])) {
  747. $problems['sources_missing'][] = $source_field;
  748. }
  749. if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) {
  750. $problems['destinations_missing'][] = $destination_field;
  751. }
  752. }
  753. foreach (array_diff_key($source_fields, $used_sources) as $name => $description) {
  754. $problems['sources_unmapped'][] = array('Field' => $name, 'Description' => $description);
  755. }
  756. foreach (array_diff_key($destination_fields, $used_destinations) as $name => $description) {
  757. $problems['destinations_unmapped'][] = array('Field' => $name, 'Description' => $description);
  758. }
  759. $problems = array_filter($problems);
  760. if (empty($problems)) {
  761. drush_print(dt('No problems found.') . "\n", 1);
  762. }
  763. else {
  764. foreach ($problems as $type => $some_problems) {
  765. drush_print($problem_descriptions[$type]);
  766. // If the contents of each row are arrays print it as a table.
  767. if (is_array($some_problems[0])) {
  768. $table = array_merge(array(array_keys($some_problems[0])), $some_problems);
  769. drush_print_table($table, TRUE);
  770. }
  771. else {
  772. foreach ($some_problems as $problem) {
  773. drush_print($problem, 1);
  774. }
  775. // Add an extra new line to keep the spacing consistent with the
  776. // tables.
  777. drush_print();
  778. }
  779. }
  780. }
  781. }
  782. }
  783. catch (MigrateException $e) {
  784. drush_print($e->getMessage());
  785. exit;
  786. }
  787. }
  788. /**
  789. * Roll back one specified migration
  790. */
  791. function drush_migrate_rollback($args = NULL) {
  792. try {
  793. if (drush_get_option('notify', FALSE)) {
  794. // Capture non-informational output for mailing
  795. ob_start();
  796. ob_implicit_flush(FALSE);
  797. }
  798. $migrations = drush_migrate_get_migrations($args);
  799. // Rollback in reverse order
  800. $migrations = array_reverse($migrations, TRUE);
  801. $options = array();
  802. if ($idlist = drush_get_option('idlist', FALSE)) {
  803. $options['idlist'] = $idlist;
  804. }
  805. if (drush_get_option('force', FALSE) == 1) {
  806. $options['force'] = TRUE;
  807. }
  808. $limit = drush_get_option('limit');
  809. if ($limit) {
  810. $parts = explode(' ', $limit);
  811. $options['limit']['value'] = $parts[0];
  812. $options['limit']['unit'] = $parts[1];
  813. if (!$options['limit']['unit']) {
  814. $options['limit']['unit'] = 'items';
  815. }
  816. elseif ($options['limit']['unit'] != 'seconds' &&
  817. $options['limit']['unit'] != 'second' &&
  818. $options['limit']['unit'] != 'items' &&
  819. $options['limit']['unit'] != 'item') {
  820. drush_set_error(NULL, dt("Invalid limit unit '!unit'",
  821. array('!unit' => $options['limit']['unit'])));
  822. return;
  823. }
  824. }
  825. $feedback = drush_get_option('feedback');
  826. if ($feedback) {
  827. $parts = explode(' ', $feedback);
  828. $options['feedback']['value'] = $parts[0];
  829. $options['feedback']['unit'] = $parts[1];
  830. if ($options['feedback']['unit'] != 'seconds' &&
  831. $options['feedback']['unit'] != 'second' &&
  832. $options['feedback']['unit'] != 'items' &&
  833. $options['feedback']['unit'] != 'item') {
  834. drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'",
  835. array('!unit' => $options['feedback']['unit'])));
  836. return;
  837. }
  838. }
  839. $instrument = drush_get_option('instrument');
  840. global $_migrate_track_memory, $_migrate_track_timer;
  841. switch ($instrument) {
  842. case 'timer':
  843. $_migrate_track_timer = TRUE;
  844. break;
  845. case 'memory':
  846. $_migrate_track_memory = TRUE;
  847. break;
  848. case 'all':
  849. $_migrate_track_timer = TRUE;
  850. $_migrate_track_memory = TRUE;
  851. break;
  852. }
  853. foreach ($migrations as $migration) {
  854. drush_log(dt("Rolling back '!description' migration",
  855. array('!description' => $migration->getMachineName())));
  856. $return = $migration->processRollback($options);
  857. // If it couldn't finish (presumably because it was appraoching memory_limit),
  858. // continue in a subprocess
  859. if ($return == MigrationBase::RESULT_INCOMPLETE) {
  860. drush_migrate_invoke_process();
  861. }
  862. // If stopped, don't process any further
  863. elseif ($return == MigrationBase::RESULT_STOPPED) {
  864. break;
  865. }
  866. elseif ($return == MigrationBase::RESULT_SKIPPED) {
  867. drush_log(dt("Skipping migration !name due to unfulfilled dependencies, use the --force option to run it anyway.",
  868. array('!name' => $migration->getMachineName())),
  869. 'warning');
  870. }
  871. }
  872. }
  873. catch (MigrateException $e) {
  874. drush_print($e->getMessage());
  875. exit;
  876. }
  877. if ($_migrate_track_memory) {
  878. drush_migrate_print_memory();
  879. }
  880. if ($_migrate_track_timer && !drush_get_context('DRUSH_DEBUG')) {
  881. drush_print_timers();
  882. }
  883. // Notify user
  884. if (drush_get_option('notify')) {
  885. _drush_migrate_notify();
  886. }
  887. }
  888. /**
  889. * Send email notification to the user running the operation.
  890. */
  891. function _drush_migrate_notify() {
  892. global $user;
  893. if ($user->uid) {
  894. $uid = $user->uid;
  895. }
  896. else {
  897. $uid = 1;
  898. }
  899. $account = user_load($uid);
  900. $params['account'] = $account;
  901. $params['output'] = ob_get_contents();
  902. drush_print_r(ob_get_status());
  903. ob_end_flush();
  904. drupal_mail('migrate_ui', 'import_complete', $account->mail,
  905. user_preferred_language($account), $params);
  906. }
  907. function drush_migrate_get_migrations($args) {
  908. $migration_objects = migrate_migrations();
  909. if ($start = drush_get_option('all')) {
  910. // Handle custom first migration when --all=foo is supplied.
  911. $seen = $start === TRUE ? TRUE : FALSE;
  912. foreach ($migration_objects as $name => $migration) {
  913. if (!$seen && (drupal_strtolower($start) == drupal_strtolower($name))) {
  914. // We found our starting migration. $seen is always TRUE now.
  915. $seen = TRUE;
  916. }
  917. if (!$migration->getEnabled() || !$seen) {
  918. // This migration is disabled or is before our starting migration.
  919. unset($migration_objects[$name]);
  920. }
  921. }
  922. }
  923. elseif ($group = drush_get_option('group')) {
  924. foreach ($migration_objects as $name => $migration) {
  925. if (drupal_strtolower($group) !=
  926. drupal_strtolower($migration->getGroup()->getName()) ||
  927. !$migration->getEnabled()) {
  928. unset($migration_objects[$name]);
  929. }
  930. }
  931. }
  932. elseif ($wildcard = drush_get_option('wildcard')) {
  933. foreach ($migration_objects as $name => $migration) {
  934. if (!fnmatch(drupal_strtolower($wildcard), drupal_strtolower($name)) ||
  935. !$migration->getEnabled()) {
  936. unset($migration_objects[$name]);
  937. }
  938. }
  939. }
  940. else {
  941. $named_migrations = array();
  942. foreach (explode(',', $args) as $name) {
  943. $found = FALSE;
  944. foreach ($migration_objects as $machine_name => $migration) {
  945. if (drupal_strtolower($name) == drupal_strtolower($machine_name)) {
  946. if ($migration->getEnabled()) {
  947. $named_migrations[$name] = $migration;
  948. $found = TRUE;
  949. break;
  950. }
  951. else {
  952. drush_log(dt('Migration !name is disabled', array('!name' => $name)), 'warning');
  953. }
  954. }
  955. }
  956. if (!$found) {
  957. drush_log(dt('No migration with machine name !name found', array('!name' => $name)), 'error');
  958. }
  959. }
  960. $migration_objects = $named_migrations;
  961. }
  962. return $migration_objects;
  963. }
  964. // Implement drush_hook_COMMAND_validate().
  965. function drush_migrate_fields_destination_validate($args = NULL) {
  966. return drush_migrate_validate_common($args);
  967. }
  968. // Implement drush_hook_COMMAND_validate().
  969. function drush_migrate_fields_source_validate($args = NULL) {
  970. return drush_migrate_validate_common($args);
  971. }
  972. // Implement drush_hook_COMMAND_validate().
  973. function drush_migrate_mappings_validate($args = NULL) {
  974. return drush_migrate_validate_common($args);
  975. }
  976. // Implement drush_hook_COMMAND_validate().
  977. function drush_migrate_analyze_validate($args = NULL) {
  978. return drush_migrate_validate_common($args);
  979. }
  980. // Implement drush_hook_COMMAND_validate().
  981. function drush_migrate_audit_validate($args = NULL) {
  982. return drush_migrate_validate_common($args);
  983. }
  984. // Implement drush_hook_COMMAND_validate().
  985. function drush_migrate_import_validate($args = NULL) {
  986. return drush_migrate_validate_common($args);
  987. }
  988. // Implement drush_hook_COMMAND_validate().
  989. function drush_migrate_stop_validate($args = NULL) {
  990. return drush_migrate_validate_common($args);
  991. }
  992. // Implement drush_hook_COMMAND_validate().
  993. function drush_migrate_reset_status_validate($args = NULL) {
  994. return drush_migrate_validate_common($args);
  995. }
  996. // Implement drush_hook_COMMAND_validate().
  997. function drush_migrate_rollback_validate($args = NULL) {
  998. return drush_migrate_validate_common($args);
  999. }
  1000. function drush_migrate_validate_common($args) {
  1001. if (drush_get_option('all')) {
  1002. if (!empty($args) || drush_get_option('group') || drush_get_option('wildcard')) {
  1003. return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard'));
  1004. }
  1005. }
  1006. elseif (drush_get_option('group')) {
  1007. if (!empty($args) || drush_get_option('all') || drush_get_option('wildcard')) {
  1008. return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard'));
  1009. }
  1010. }
  1011. elseif (drush_get_option('wildcard')) {
  1012. if (!empty($args) || drush_get_option('all') || drush_get_option('group')) {
  1013. return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard'));
  1014. }
  1015. }
  1016. else {
  1017. if (empty($args)) {
  1018. return drush_set_error(NULL, dt('You must specify exactly one of a migration name, --all, or --group, or --wildcard'));
  1019. }
  1020. $machine_names = explode(',', $args);
  1021. foreach ($machine_names as $machine_name) {
  1022. $machine_name = trim($machine_name);
  1023. $class_name = db_select('migrate_status', 'ms')
  1024. ->fields('ms', array('class_name'))
  1025. ->condition('machine_name', $machine_name)
  1026. ->execute()
  1027. ->fetchField();
  1028. if (!$class_name || !class_exists($class_name)) {
  1029. drush_set_error(dt('Unrecognized migration: !name', array('!name' => $machine_name)));
  1030. }
  1031. }
  1032. }
  1033. $feedback = drush_get_option('feedback');
  1034. if ($feedback) {
  1035. $parts = explode(' ', $feedback);
  1036. $options['feedback']['value'] = $parts[0];
  1037. $options['feedback']['unit'] = $parts[1];
  1038. if ($options['feedback']['unit'] != 'seconds' &&
  1039. $options['feedback']['unit'] != 'second' &&
  1040. $options['feedback']['unit'] != 'items' &&
  1041. $options['feedback']['unit'] != 'item') {
  1042. drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'",
  1043. array('!unit' => $options['feedback']['unit'])));
  1044. return;
  1045. }
  1046. }
  1047. }
  1048. /*
  1049. * A 'pre' callback for migrate-import command.
  1050. * Call migrate-stop and migrate-rollback commands if requested.
  1051. */
  1052. function drush_migrate_pre_migrate_import($args = NULL) {
  1053. if (drush_get_option('stop')) {
  1054. drush_unset_option('stop');
  1055. try {
  1056. /** @var Migration[] $migrations */
  1057. $migrations = drush_migrate_get_migrations($args);
  1058. foreach ($migrations as $migration) {
  1059. $status = $migration->getStatus();
  1060. if ($status == MigrationBase::STATUS_IMPORTING ||
  1061. $status == MigrationBase::STATUS_ROLLING_BACK) {
  1062. drush_log(dt("Stopping '!description' migration", array('!description' => $migration->getMachineName())));
  1063. $migration->stopProcess();
  1064. // Give the process a chance to stop.
  1065. $count = 0;
  1066. while ($migration->getStatus() != MigrationBase::STATUS_IDLE
  1067. && $count++ < 5) {
  1068. sleep(1);
  1069. }
  1070. }
  1071. }
  1072. }
  1073. catch (MigrateException $e) {
  1074. drush_print($e->getMessage());
  1075. exit;
  1076. }
  1077. }
  1078. if (drush_get_option('rollback')) {
  1079. drush_unset_option('rollback');
  1080. drush_invoke('migrate-rollback', $args);
  1081. }
  1082. }
  1083. /**
  1084. * Perform import on one or more migrations.
  1085. *
  1086. * @param $machine_names
  1087. * A comma delimited list of machine names, or the special name 'all'
  1088. */
  1089. function drush_migrate_import($args = NULL) {
  1090. try {
  1091. if (drush_get_option('notify', FALSE)) {
  1092. // Capture non-informational output for mailing
  1093. ob_start();
  1094. ob_implicit_flush(FALSE);
  1095. }
  1096. $migrations = drush_migrate_get_migrations($args);
  1097. $options = array();
  1098. if ($idlist = drush_get_option('idlist', FALSE)) {
  1099. $options['idlist'] = $idlist;
  1100. }
  1101. if ($file_function = drush_get_option('file_function', '')) {
  1102. $options['file_function'] = $file_function;
  1103. }
  1104. if (drush_get_option('force', FALSE) == 1) {
  1105. $options['force'] = TRUE;
  1106. }
  1107. $limit = drush_get_option('limit');
  1108. if ($limit) {
  1109. $parts = explode(' ', $limit);
  1110. $options['limit']['value'] = $parts[0];
  1111. $options['limit']['unit'] = $parts[1];
  1112. if (!$options['limit']['unit']) {
  1113. $options['limit']['unit'] = 'items';
  1114. }
  1115. elseif ($options['limit']['unit'] != 'seconds' &&
  1116. $options['limit']['unit'] != 'second' &&
  1117. $options['limit']['unit'] != 'items' &&
  1118. $options['limit']['unit'] != 'item') {
  1119. drush_set_error(NULL, dt("Invalid limit unit '!unit'",
  1120. array('!unit' => $options['limit']['unit'])));
  1121. return;
  1122. }
  1123. }
  1124. $feedback = drush_get_option('feedback');
  1125. if ($feedback) {
  1126. $parts = explode(' ', $feedback);
  1127. $options['feedback']['value'] = $parts[0];
  1128. $options['feedback']['unit'] = $parts[1];
  1129. if ($options['feedback']['unit'] != 'seconds' &&
  1130. $options['feedback']['unit'] != 'second' &&
  1131. $options['feedback']['unit'] != 'items' &&
  1132. $options['feedback']['unit'] != 'item') {
  1133. drush_set_error(NULL, dt("Invalid feedback frequency unit '!unit'",
  1134. array('!unit' => $options['feedback']['unit'])));
  1135. return;
  1136. }
  1137. }
  1138. $instrument = drush_get_option('instrument');
  1139. global $_migrate_track_memory, $_migrate_track_timer;
  1140. switch ($instrument) {
  1141. case 'timer':
  1142. $_migrate_track_timer = TRUE;
  1143. break;
  1144. case 'memory':
  1145. $_migrate_track_memory = TRUE;
  1146. break;
  1147. case 'all':
  1148. $_migrate_track_timer = TRUE;
  1149. $_migrate_track_memory = TRUE;
  1150. break;
  1151. }
  1152. $stop = FALSE;
  1153. foreach ($migrations as $machine_name => $migration) {
  1154. drush_log(dt("Importing '!description' migration",
  1155. array('!description' => $machine_name)));
  1156. if (drush_get_option('update') && !$idlist) {
  1157. $migration->prepareUpdate();
  1158. if (drush_get_option('ignore-highwater')) {
  1159. $migration->setHighwaterField(array());
  1160. }
  1161. }
  1162. if (drush_get_option('needs-update')) {
  1163. $map_rows = $migration->getMap()->getRowsNeedingUpdate(10000);
  1164. $idlist = array();
  1165. foreach ($map_rows as $row) {
  1166. $idlist[] = $row->sourceid1;
  1167. }
  1168. $options['idlist'] = implode(',', $idlist);
  1169. }
  1170. // The goal here is to do one migration in the parent process and then
  1171. // spawn subshells as needed when memory is depleted. We show feedback
  1172. // after each subshell depletes itself. Best we can do in PHP.
  1173. if (!drush_get_context('DRUSH_BACKEND')) {
  1174. // Our first pass and in the parent process. Run a migration right here.
  1175. $status = $migration->processImport($options);
  1176. if ($status == MigrationBase::RESULT_SKIPPED) {
  1177. drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n !depends\nUse the --force option to run it anyway.",
  1178. array(
  1179. '!name' => $machine_name,
  1180. '!depends' => implode("\n ", $migration->incompleteDependencies()),
  1181. )),
  1182. 'warning');
  1183. }
  1184. elseif ($status == MigrationBase::RESULT_STOPPED) {
  1185. break;
  1186. }
  1187. elseif ($status == MigrationBase::RESULT_INCOMPLETE) {
  1188. $stop = TRUE;
  1189. }
  1190. // Subsequent run in the parent process. Spawn subshells ad infinitum.
  1191. $migration_string = implode(',', array_keys($migrations));
  1192. while ($status == MigrationBase::RESULT_INCOMPLETE) {
  1193. $return = drush_migrate_invoke_process($migration_string);
  1194. // 'object' holds the return code we care about.
  1195. $status = $return['object']['status'];
  1196. $migration_string = $return['object']['migrations'];
  1197. if ($status == MigrationBase::RESULT_SKIPPED) {
  1198. drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n !depends\nUse the --force option to run it anyway.",
  1199. array(
  1200. '!name' => $machine_name,
  1201. '!depends' => implode("\n ", $migration->incompleteDependencies()),
  1202. )),
  1203. 'warning');
  1204. }
  1205. elseif ($status == MigrationBase::RESULT_STOPPED) {
  1206. $stop = TRUE;
  1207. break;
  1208. }
  1209. }
  1210. }
  1211. else {
  1212. // I'm in a subshell. Import then set return value so parent process can respawn or move on.
  1213. $status = $migration->processImport($options);
  1214. if ($status == MigrationBase::RESULT_SKIPPED) {
  1215. drush_log(dt("Skipping migration !name due to unfulfilled dependencies:\n !depends\n",
  1216. array(
  1217. '!name' => $machine_name,
  1218. '!depends' => implode("\n ", $migration->incompleteDependencies()),
  1219. )),
  1220. 'warning');
  1221. }
  1222. elseif ($status == MigrationBase::RESULT_INCOMPLETE) {
  1223. $stop = TRUE;
  1224. }
  1225. drush_backend_set_result(array('status' => $status,
  1226. 'migrations' => implode(',', array_keys($migrations))));
  1227. }
  1228. if ($stop) {
  1229. break;
  1230. }
  1231. unset($migrations[$machine_name]);
  1232. }
  1233. }
  1234. catch (MigrateException $e) {
  1235. drush_print($e->getMessage());
  1236. exit;
  1237. }
  1238. if ($_migrate_track_memory) {
  1239. drush_migrate_print_memory();
  1240. }
  1241. if ($_migrate_track_timer && !drush_get_context('DRUSH_DEBUG')) {
  1242. drush_print_timers();
  1243. }
  1244. // Notify user
  1245. if (drush_get_option('notify')) {
  1246. _drush_migrate_notify();
  1247. }
  1248. }
  1249. /**
  1250. * Stop clearing or importing a given content set.
  1251. *
  1252. * @param $content_set
  1253. * The name of the Migration
  1254. */
  1255. function drush_migrate_stop($args = NULL) {
  1256. try {
  1257. $migrations = drush_migrate_get_migrations($args);
  1258. foreach ($migrations as $migration) {
  1259. drush_log(dt("Stopping '!description' migration", array('!description' => $migration->getMachineName())));
  1260. $migration->stopProcess();
  1261. }
  1262. }
  1263. catch (MigrateException $e) {
  1264. drush_print($e->getMessage());
  1265. exit;
  1266. }
  1267. }
  1268. /**
  1269. * Reset the status of a given migration.
  1270. */
  1271. function drush_migrate_reset_status($args = NULL) {
  1272. try {
  1273. $migrations = drush_migrate_get_migrations($args);
  1274. foreach ($migrations as $migration) {
  1275. drush_log(dt("Resetting '!description' migration",
  1276. array('!description' => $migration->getMachineName())));
  1277. $migration->resetStatus();
  1278. }
  1279. }
  1280. catch (MigrateException $e) {
  1281. drush_print($e->getMessage());
  1282. exit;
  1283. }
  1284. }
  1285. /**
  1286. * Deregister a given migration, migration group, or all orphaned migrations.
  1287. * Note that the migration might no longer "exist" (the class implementation
  1288. * might be gone), so we can't count on being able to instantiate it, or use
  1289. * migrate_migrations().
  1290. */
  1291. function drush_migrate_deregister($args = NULL) {
  1292. try {
  1293. $orphans = drush_get_option('orphans');
  1294. $group = drush_get_option('group');
  1295. if ($group) {
  1296. MigrateGroup::deregister($group);
  1297. drush_log(dt("Deregistered group '!description' and all its migrations",
  1298. array('!description' => $group)), 'success');
  1299. }
  1300. else {
  1301. if ($orphans) {
  1302. $migrations = array();
  1303. $result = db_select('migrate_status', 'ms')
  1304. ->fields('ms', array('class_name', 'machine_name'))
  1305. ->execute();
  1306. foreach ($result as $row) {
  1307. if (!class_exists($row->class_name)) {
  1308. $migrations[] = $row->machine_name;
  1309. }
  1310. }
  1311. }
  1312. else {
  1313. $migrations = explode(',', $args);
  1314. }
  1315. foreach ($migrations as $machine_name) {
  1316. drush_migrate_deregister_migration(drupal_strtolower($machine_name));
  1317. drush_log(dt("Deregistered '!description' migration",
  1318. array('!description' => $machine_name)), 'success');
  1319. }
  1320. }
  1321. }
  1322. catch (MigrateException $e) {
  1323. drush_print($e->getMessage());
  1324. exit;
  1325. }
  1326. }
  1327. /**
  1328. * Given a migration machine name, remove its tracking from the database.
  1329. *
  1330. * @param $machine_name
  1331. */
  1332. function drush_migrate_deregister_migration($machine_name) {
  1333. // The class is gone, so we'll manually clear migrate_status, and make
  1334. // the default assumptions about the map/message tables.
  1335. db_drop_table('migrate_map_' . $machine_name);
  1336. db_drop_table('migrate_message_' . $machine_name);
  1337. db_delete('migrate_status')
  1338. ->condition('machine_name', $machine_name)
  1339. ->execute();
  1340. db_delete('migrate_field_mapping')
  1341. ->condition('machine_name', $machine_name)
  1342. ->execute();
  1343. }
  1344. /**
  1345. * Auto-registration is no longer supported. This command should be removed
  1346. * entirely in a future point release.
  1347. *
  1348. * @deprecated
  1349. */
  1350. function drush_migrate_auto_register($args = NULL) {
  1351. drush_log(dt('The auto-registration feature has been removed. Migrations '
  1352. . 'must now be explicitly registered.'), 'error');
  1353. }
  1354. /**
  1355. * Register any migrations defined in hook_migrate_api().
  1356. */
  1357. function drush_migrate_register($args = NULL) {
  1358. migrate_static_registration();
  1359. drush_log(dt('All statically defined migrations have been (re)registered.'), 'success');
  1360. }
  1361. /**
  1362. * A drush command callback.
  1363. */
  1364. function drush_migrate_wipe() {
  1365. $types = func_get_args();
  1366. $nids = db_select('node', 'n')
  1367. ->fields('n', array('nid'))
  1368. ->condition('type', $types, 'IN')
  1369. ->execute()
  1370. ->fetchCol();
  1371. $chunks = array_chunk($nids, 50);
  1372. foreach ($chunks as $chunk) {
  1373. node_delete_multiple($chunk);
  1374. }
  1375. migrate_instrument_stop('node_delete');
  1376. }
  1377. // Print all timers for the request.
  1378. function drush_migrate_print_memory() {
  1379. global $_migrate_memory;
  1380. $temparray = array();
  1381. foreach ((array)$_migrate_memory as $name => $memoryrec) {
  1382. // We have to use timer_read() for active timers, and check the record for others
  1383. if (isset($memoryrec['start'])) {
  1384. $temparray[$name] = migrate_memory_read($name);
  1385. }
  1386. else {
  1387. $temparray[$name] = $memoryrec['bytes'];
  1388. }
  1389. }
  1390. // Go no farther if there were no timers
  1391. if (count($temparray) > 0) {
  1392. // Put the highest cumulative times first
  1393. arsort($temparray);
  1394. $table = array();
  1395. $table[] = array('Name', 'Cum (bytes)', 'Count', 'Avg (bytes)');
  1396. foreach ($temparray as $name => $memory) {
  1397. $count = $_migrate_memory[$name]['count'];
  1398. if ($count > 0) {
  1399. $avg = round($memory/$count, 0);
  1400. }
  1401. else {
  1402. $avg = 'N/A';
  1403. }
  1404. $table[] = array($name, $memory, $count, $avg);
  1405. }
  1406. drush_print_table($table, TRUE);
  1407. }
  1408. }
  1409. /**
  1410. * Command argument complete callback.
  1411. *
  1412. * @return
  1413. * List of migrations.
  1414. */
  1415. function migrate_migrate_status_complete() {
  1416. return array('values' => drush_migrate_migrations());
  1417. }
  1418. function migrate_migrate_import_complete() {
  1419. return array('values' => drush_migrate_migrations());
  1420. }
  1421. function migrate_migrate_rollback_complete() {
  1422. return array('values' => drush_migrate_migrations());
  1423. }
  1424. function migrate_migrate_fields_destination_complete() {
  1425. return array('values' => drush_migrate_migrations());
  1426. }
  1427. function migrate_migrate_fields_source_complete() {
  1428. return array('values' => drush_migrate_migrations());
  1429. }
  1430. function migrate_migrate_mappings_complete() {
  1431. return array('values' => drush_migrate_migrations());
  1432. }
  1433. function migrate_migrate_reset_status_complete() {
  1434. return array('values' => drush_migrate_migrations());
  1435. }
  1436. function migrate_migrate_stop_complete() {
  1437. return array('values' => drush_migrate_migrations());
  1438. }
  1439. function drush_migrate_migrations() {
  1440. drush_bootstrap(DRUPAL_BOOTSTRAP_FULL);
  1441. $migrations = migrate_migrations();
  1442. foreach ($migrations as $migration) {
  1443. $values[] = $migration->getMachineName();
  1444. }
  1445. return $values;
  1446. }