migrate.drush.inc 52 KB

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