migrate_ui.pages.inc 67 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048
  1. <?php
  2. /**
  3. * @file
  4. * Pages for managing migration processes.
  5. */
  6. /**
  7. * Form for managing migration groups.
  8. */
  9. function migrate_ui_migrate_dashboard($form, &$form_state) {
  10. drupal_set_title(t('Migrate dashboard'));
  11. $build = array();
  12. $build['overview'] = array(
  13. '#prefix' => '<div>',
  14. '#markup' => filter_xss_admin(migrate_overview()),
  15. '#suffix' => '</div>',
  16. );
  17. $header = array(
  18. 'machinename' => array('data' => t('Group')),
  19. 'status' => array('data' => t('Status')),
  20. 'source_system' => array('data' => t('Source system')),
  21. );
  22. $result = db_select('migrate_group', 'mg')
  23. ->fields('mg', array('name', 'title', 'arguments'))
  24. ->execute();
  25. $rows = array();
  26. foreach ($result as $group_row) {
  27. $row = array();
  28. $migration_result = db_select('migrate_status', 'ms')
  29. ->fields('ms', array('machine_name', 'status', 'arguments'))
  30. ->condition('group_name', $group_row->name)
  31. ->execute();
  32. if (!$migration_result) {
  33. continue;
  34. }
  35. $status = t('Idle');
  36. $idle = TRUE;
  37. $machine_names = array();
  38. foreach ($migration_result as $migration_row) {
  39. switch ($migration_row->status) {
  40. case MigrationBase::STATUS_IMPORTING:
  41. $status = t('Importing');
  42. $idle = FALSE;
  43. break;
  44. case MigrationBase::STATUS_ROLLING_BACK:
  45. $status = t('Rolling back');
  46. $idle = FALSE;
  47. break;
  48. case MigrationBase::STATUS_STOPPING:
  49. $status = t('Stopping');
  50. $idle = FALSE;
  51. break;
  52. }
  53. $machine_names[] = $migration_row->machine_name;
  54. }
  55. // If we're not in the middle of an operaton, what's the status of the import?
  56. if ($idle) {
  57. $ready = TRUE;
  58. $complete = TRUE;
  59. foreach ($machine_names as $machine_name) {
  60. $migration = Migration::getInstance($machine_name);
  61. if (!$migration) {
  62. continue;
  63. }
  64. if (method_exists($migration, 'sourceCount') && method_exists($migration, 'processedCount')) {
  65. $source_count = $migration->sourceCount();
  66. $processed_count = $migration->processedCount();
  67. if ($processed_count > 0) {
  68. $ready = FALSE;
  69. if (!$complete) {
  70. break;
  71. }
  72. }
  73. if ($processed_count != $source_count) {
  74. $complete = FALSE;
  75. if (!$ready) {
  76. break;
  77. }
  78. }
  79. }
  80. }
  81. if ($ready) {
  82. $status = t('Ready to import');
  83. }
  84. elseif ($complete) {
  85. $status = t('Import complete');
  86. }
  87. else {
  88. $status = t('Import incomplete, not currently running');
  89. }
  90. }
  91. $row['status'] = $status;
  92. $row['machinename'] =
  93. l($group_row->title, 'admin/content/migrate/groups/' . $group_row->name);
  94. $arguments = unserialize($group_row->arguments);
  95. if (!empty($arguments['source_system'])) {
  96. $row['source_system'] = filter_xss_admin($arguments['source_system']);
  97. }
  98. else {
  99. $row['source_system'] = '';
  100. }
  101. $rows[$group_row->name] = $row;
  102. }
  103. $build['dashboard']['tasks'] = array(
  104. '#type' => 'tableselect',
  105. '#header' => $header,
  106. '#options' => $rows,
  107. '#tree' => TRUE,
  108. '#empty' => t('No migration groups defined.'),
  109. );
  110. $build['operations'] = _migrate_ui_migrate_operations();
  111. return $build;
  112. }
  113. /**
  114. * Menu callback
  115. */
  116. function migrate_ui_migrate_group($form, &$form_state, $group_name) {
  117. $group = MigrateGroup::getInstance($group_name);
  118. $build = array();
  119. $header = array(
  120. 'machinename' => array('data' => t('Task')),
  121. 'status' => array('data' => t('Status')),
  122. 'importrows' => array('data' => t('Items')),
  123. 'imported' => array('data' => t('Imported')),
  124. 'unprocessed' => array('data' => t('Unprocessed')),
  125. );
  126. if (user_access(MIGRATE_ACCESS_ADVANCED)) {
  127. $header += array(
  128. 'messages' => array('data' => t('Messages')),
  129. 'lastthroughput' => array('data' => t('Throughput')),
  130. 'lastimported' => array('data' => t('Last imported')),
  131. );
  132. }
  133. $migrations = migrate_migrations();
  134. $rows = array();
  135. foreach ($migrations as $migration) {
  136. $current_group = $migration->getGroup()->getName();
  137. if ($current_group != $group_name) {
  138. continue;
  139. }
  140. $row = array();
  141. $has_counts = TRUE;
  142. if (method_exists($migration, 'sourceCount')) {
  143. $total = $migration->sourceCount();
  144. if ($total < 0) {
  145. $has_counts = FALSE;
  146. $total = t('N/A');
  147. }
  148. }
  149. else {
  150. $has_counts = FALSE;
  151. $total = t('N/A');
  152. }
  153. if (method_exists($migration, 'importedCount')) {
  154. $imported = $migration->importedCount();
  155. $processed = $migration->processedCount();
  156. }
  157. else {
  158. $has_counts = FALSE;
  159. $imported = t('N/A');
  160. }
  161. if ($has_counts) {
  162. $unprocessed = $total - $processed;
  163. }
  164. else {
  165. $unprocessed = t('N/A');
  166. }
  167. $status = $migration->getStatus();
  168. switch ($status) {
  169. case MigrationBase::STATUS_IDLE:
  170. $status = t('Idle');
  171. break;
  172. case MigrationBase::STATUS_IMPORTING:
  173. $status = t('Importing');
  174. break;
  175. case MigrationBase::STATUS_ROLLING_BACK:
  176. $status = t('Rolling back');
  177. break;
  178. case MigrationBase::STATUS_STOPPING:
  179. $status = t('Stopping');
  180. break;
  181. case MigrationBase::STATUS_DISABLED:
  182. $status = t('Disabled');
  183. break;
  184. default:
  185. $status = t('Unknown');
  186. break;
  187. }
  188. $row['status'] = $status;
  189. $machine_name = $migration->getMachineName();
  190. $name_length = strlen($machine_name);
  191. $group_length = strlen($group_name);
  192. if ($name_length != $group_length &&
  193. !strncasecmp($group_name, $machine_name, $group_length)) {
  194. $display_name = substr($machine_name, $group_length);
  195. }
  196. else {
  197. $display_name = $machine_name;
  198. }
  199. $row['machinename'] =
  200. l($display_name, "admin/content/migrate/groups/$group_name/$machine_name");
  201. $row['importrows'] = (int) $total;
  202. $row['imported'] = (int) $imported;
  203. $row['unprocessed'] = (int) $unprocessed;
  204. if (user_access(MIGRATE_ACCESS_ADVANCED)) {
  205. if (is_subclass_of($migration, 'Migration')) {
  206. $num_messages = $migration->messageCount();
  207. $row['messages'] = $num_messages ?
  208. l($num_messages, "admin/content/migrate/groups/$group_name/$machine_name/messages")
  209. : 0;
  210. }
  211. else {
  212. $row['messages'] = t('N/A');
  213. }
  214. if (method_exists($migration, 'getLastThroughput')) {
  215. $rate = $migration->getLastThroughput();
  216. if ($rate == '') {
  217. $row['lastthroughput'] = t('Unknown');
  218. }
  219. else {
  220. $row['lastthroughput'] = t('@rate/min', array('@rate' => $rate));
  221. }
  222. }
  223. else {
  224. $row['lastthroughput'] = t('N/A');
  225. }
  226. $row['lastimported'] = check_plain($migration->getLastImported());
  227. }
  228. $rows[$machine_name] = $row;
  229. }
  230. $build['dashboard']['migrations'] = array(
  231. '#type' => 'tableselect',
  232. '#header' => $header,
  233. '#options' => $rows,
  234. '#tree' => TRUE,
  235. '#empty' => t('No tasks are defined for this migration group.'),
  236. );
  237. $build['operations'] = _migrate_ui_migrate_operations();
  238. return $build;
  239. }
  240. /**
  241. * @return array
  242. */
  243. function _migrate_ui_migrate_operations() {
  244. // Build the 'Update options' form.
  245. $operations = array(
  246. '#type' => 'fieldset',
  247. '#title' => t('Operations'),
  248. );
  249. //
  250. switch (variable_get('migrate_import_method', 0)) {
  251. case 1:
  252. $options = array(
  253. 'import_background' => t('Import'),
  254. 'rollback_background' => t('Rollback'),
  255. );
  256. break;
  257. case 2:
  258. $options = array(
  259. 'import_immediate' => t('Import immediately'),
  260. 'import_background' => t('Import in background'),
  261. 'rollback_immediate' => t('Rollback immediately'),
  262. 'rollback_background' => t('Rollback in background'),
  263. );
  264. break;
  265. case 0:
  266. default:
  267. $options = array(
  268. 'import_immediate' => t('Import'),
  269. 'rollback_immediate' => t('Rollback'),
  270. );
  271. break;
  272. }
  273. $options += array(
  274. 'stop' => t('Stop'),
  275. 'reset' => t('Reset'),
  276. 'deregister' => t('Remove migration settings'),
  277. );
  278. $operations['operation'] = array(
  279. '#type' => 'select',
  280. '#title' => t('Operation'),
  281. '#title_display' => 'invisible',
  282. '#options' => $options,
  283. );
  284. $operations['submit'] = array(
  285. '#type' => 'submit',
  286. '#value' => t('Execute'),
  287. '#validate' => array('migrate_ui_migrate_validate'),
  288. '#submit' => array('migrate_ui_migrate_submit'),
  289. );
  290. $operations['description'] = array(
  291. '#prefix' => '<p>',
  292. '#markup' => t(
  293. 'Choose an operation to run on all selections above:
  294. <ul>
  295. <li>Import<br /> Imports all previously unprocessed records from the source, plus
  296. any records marked for update, into destination Drupal objects.</li>
  297. <li>Rollback<br /> Deletes all Drupal objects created by the import.</li>
  298. <li>Stop<br /> Cleanly interrupts any import or rollback processes that may
  299. currently be running.</li>
  300. <li>Reset<br /> Sometimes a process may fail to stop cleanly, and be
  301. left stuck in an Importing or Rolling Back status. Choose Reset to clear
  302. the status and permit other operations to proceed.</li>
  303. <li>Remove migration settings<br /> Removes all information about a migration group
  304. or task, while preserving any content that has already been imported.</li>
  305. </ul>'
  306. ),
  307. '#postfix' => '</p>',
  308. );
  309. $operations['options'] = array(
  310. '#type' => 'fieldset',
  311. '#title' => t('Options'),
  312. '#collapsible' => TRUE,
  313. '#collapsed' => TRUE,
  314. '#access' => user_access(MIGRATE_ACCESS_ADVANCED),
  315. );
  316. $operations['options']['update'] = array(
  317. '#type' => 'checkbox',
  318. '#title' => t('Update'),
  319. '#description' => t('Check this box to update all previously-imported content
  320. in addition to importing new content. Leave unchecked to only import
  321. new content'),
  322. );
  323. $operations['options']['force'] = array(
  324. '#type' => 'checkbox',
  325. '#title' => t('Ignore dependencies'),
  326. '#description' => t('Check this box to ignore dependencies when running imports
  327. - all tasks will run whether or not their dependent tasks have
  328. completed.'),
  329. );
  330. $operations['options']['limit'] = array(
  331. '#tree' => TRUE,
  332. '#type' => 'fieldset',
  333. '#attributes' => array('class' => array('container-inline')),
  334. 'value' => array(
  335. '#type' => 'textfield',
  336. '#title' => t('Limit to:'),
  337. '#size' => 10,
  338. ),
  339. 'unit' => array(
  340. '#type' => 'select',
  341. '#options' => array(
  342. 'items' => t('items'),
  343. 'seconds' => t('seconds'),
  344. ),
  345. '#description' => t('Set a limit of how many items to process for
  346. each migration task, or how long each should run.'),
  347. ),
  348. );
  349. return $operations;
  350. }
  351. /**
  352. * Validate callback for the dashboard form.
  353. */
  354. function migrate_ui_migrate_validate($form, &$form_state) {
  355. $migrations = _migrate_ui_selected_migrations($form_state['values']);
  356. if (empty($migrations)) {
  357. if (isset($form_state['values']['migrations'])) {
  358. form_set_error('', t('No items selected.'));
  359. }
  360. }
  361. }
  362. /**
  363. * Determine what migrations are selected (whether directly on a group page, or
  364. * via group selections on the dashboard).
  365. *
  366. * @param $values
  367. *
  368. * @return array
  369. */
  370. function _migrate_ui_selected_migrations($values) {
  371. if (isset($values['migrations'])) {
  372. // From the specific group page, just take them in order (they were already
  373. // sorted by dependencies).
  374. $machine_names = array_filter($values['migrations']);
  375. }
  376. else {
  377. // From the groups page, we need to use migrate_migrations to be sure we've
  378. // got the tasks for each group in dependency order.
  379. $tasks = array_filter($values['tasks']);
  380. $migrations = migrate_migrations();
  381. $machine_names = array();
  382. foreach ($migrations as $migration) {
  383. $group_name = $migration->getGroup()->getName();
  384. if (in_array($group_name, $tasks)) {
  385. $machine_names[] = $migration->getMachineName();
  386. }
  387. }
  388. }
  389. return $machine_names;
  390. }
  391. /**
  392. * Submit callback for the dashboard form.
  393. */
  394. function migrate_ui_migrate_submit($form, &$form_state) {
  395. $values = $form_state['values'];
  396. $operation = $values['operation'];
  397. $limit = $values['limit'];
  398. if (isset($values['update'])) {
  399. $update = $values['update'];
  400. }
  401. else {
  402. $update = 0;
  403. }
  404. if (isset($values['force'])) {
  405. $force = $values['force'];
  406. }
  407. else {
  408. $force = 0;
  409. }
  410. $machine_names = _migrate_ui_selected_migrations($values);
  411. $operations = array();
  412. // Rollback in reverse order.
  413. if (in_array($operation, array('rollback_immediate', 'deregister'))) {
  414. $machine_names = array_reverse($machine_names);
  415. }
  416. // Special case: when deregistering a group, go through the group API
  417. if ($operation == 'deregister' && isset($values['tasks'])) {
  418. foreach ($values['tasks'] as $task) {
  419. MigrateGroup::deregister($task);
  420. }
  421. return;
  422. }
  423. $drush_arguments = array();
  424. foreach ($machine_names as $machine_name) {
  425. $migration = Migration::getInstance($machine_name);
  426. if ($migration) {
  427. switch ($operation) {
  428. case 'import_immediate':
  429. // Update (if necessary) once, before starting
  430. if ($update && method_exists($migration, 'prepareUpdate')) {
  431. $migration->prepareUpdate();
  432. }
  433. $operations[] = array('migrate_ui_batch', array('import', $machine_name, $limit, $force));
  434. break;
  435. case 'rollback_immediate':
  436. $operations[] = array('migrate_ui_batch', array('rollback', $machine_name, $limit, $force));
  437. break;
  438. case 'import_background':
  439. case 'rollback_background':
  440. $drush_arguments[] = $machine_name;
  441. break;
  442. case 'stop':
  443. $migration->stopProcess();
  444. break;
  445. case 'reset':
  446. $migration->resetStatus();
  447. break;
  448. case 'deregister':
  449. migrate_ui_deregister_migration($machine_name);
  450. break;
  451. }
  452. }
  453. }
  454. // Only immediate rollback and import operations will need to go through Batch API.
  455. if (count($operations) > 0) {
  456. $batch = array(
  457. 'operations' => $operations,
  458. 'title' => t('Import processing'),
  459. 'file' => drupal_get_path('module', 'migrate_ui') . '/migrate_ui.pages.inc',
  460. 'init_message' => t('Starting import process'),
  461. 'progress_message' => t(''),
  462. 'error_message' => t('An error occurred. Some or all of the import processing has failed.'),
  463. 'finished' => 'migrate_ui_batch_finish',
  464. );
  465. batch_set($batch);
  466. }
  467. elseif (count($drush_arguments) > 0) {
  468. $drush_path = trim(variable_get('migrate_drush_path', ''));
  469. // Check that $drush_path works. See migrate_ui_configure_form().
  470. if (!is_executable($drush_path)) {
  471. $message = t('To enable running operations in the background with <a href="@drush">drush</a>, (which is <a href="@recommended">recommended</a>), some configuration must be done on the server. See the <a href="@config">documentation</a> on <a href="@dorg">drupal.org</a>.',
  472. array(
  473. '@drush' => 'http://drupal.org/project/drush',
  474. '@recommended' => 'http://drupal.org/node/1806824',
  475. '@config' => 'http://drupal.org/node/1958170',
  476. '@dorg' => 'http://drupal.org/',
  477. )
  478. );
  479. drupal_set_message($message);
  480. return;
  481. }
  482. $uri = $GLOBALS['base_url'];
  483. $uid = $GLOBALS['user']->uid;
  484. if ($operation == 'import_background') {
  485. $command = 'mi';
  486. $log_suffix = '.import.log';
  487. }
  488. else {
  489. $command = 'mr';
  490. $log_suffix = '.rollback.log';
  491. }
  492. $migrations = implode(',', $drush_arguments);
  493. $drush_command = "$drush_path $command $migrations --user=$uid --uri=$uri " .
  494. '--root=' . DRUPAL_ROOT;
  495. if ($force) {
  496. $drush_command .= ' --force';
  497. }
  498. if ($update) {
  499. $drush_command .= ' --update';
  500. }
  501. if (variable_get('migrate_drush_mail', 0)) {
  502. $drush_command .= ' --notify';
  503. }
  504. if (!empty($limit['value'])) {
  505. $limit = $limit['value'] . ' ' . $limit['unit'];
  506. $drush_command .= " --limit=\"$limit\"";
  507. }
  508. $log_file = drupal_realpath('temporary://' . $drush_arguments[0] . $log_suffix);
  509. $drush_command .= " >$log_file 2>&1 &";
  510. exec($drush_command, $output, $status);
  511. if (variable_get('migrate_drush_mail', 0)) {
  512. drupal_set_message('Your operation is running in the background. You will receive an email message when it is complete.');
  513. }
  514. else {
  515. drupal_set_message('Your operation is running in the background. You may '.
  516. 'refresh the dashboard page to check on its progress.');
  517. }
  518. }
  519. }
  520. /**
  521. * Implements callback_batch_operation().
  522. *
  523. * Process all enabled migration processes in a browser, using the Batch API
  524. * to break it into manageable chunks.
  525. *
  526. * @param $operation
  527. * Operation to perform - 'import', 'rollback', 'stop', or 'reset'.
  528. * @param $machine_name
  529. * Machine name of migration to process.
  530. * @param $limit
  531. * An array indicating the number of items to import or rollback, or the
  532. * number of seconds to process. Should include 'unit' (either 'items' or
  533. * 'seconds') and 'value'.
  534. * @param $context
  535. * Batch API context structure
  536. */
  537. function migrate_ui_batch($operation, $machine_name, $limit, $force = FALSE, &$context) {
  538. // If we got a stop message, skip everything else
  539. if (isset($context['results']['stopped'])) {
  540. $context['finished'] = 1;
  541. return;
  542. }
  543. $migration = Migration::getInstance($machine_name);
  544. if (!$migration) {
  545. $context['finished'] = 1;
  546. return;
  547. }
  548. // Messages generated by migration processes will be captured in this global
  549. global $_migrate_messages;
  550. $_migrate_messages = array();
  551. Migration::setDisplayFunction('migrate_ui_capture_message');
  552. // Try to allocate enough time to run the full operation.
  553. $migration->setBatchTimeLimit();
  554. // Perform the requested operation
  555. switch ($operation) {
  556. case 'import':
  557. $result = $migration->processImport(array('limit' => $limit, 'force' => $force));
  558. break;
  559. case 'rollback':
  560. $result = $migration->processRollback(array('limit' => $limit, 'force' => $force));
  561. break;
  562. }
  563. switch ($result) {
  564. case Migration::RESULT_INCOMPLETE:
  565. // Default to half-done, in case we can't get a more precise fix
  566. $context['finished'] = .5;
  567. if (method_exists($migration, 'sourceCount')) {
  568. $total = $migration->sourceCount();
  569. if ($total > 0 && method_exists($migration, 'importedCount')) {
  570. $processed = $migration->processedCount();
  571. switch ($operation) {
  572. case 'import':
  573. $to_update = $migration->updateCount();
  574. $context['finished'] = ($processed-$to_update)/$total;
  575. break;
  576. case 'rollback':
  577. $context['finished'] = ($total - $migration->importedCount())/$total;
  578. break;
  579. }
  580. }
  581. }
  582. break;
  583. case MigrationBase::RESULT_SKIPPED:
  584. $_migrate_messages[] = array(
  585. 'message' => t("Skipped !name due to unfulfilled dependencies: !depends",
  586. array(
  587. '!name' => $machine_name,
  588. '!depends' => implode(", ", $migration->incompleteDependencies()),
  589. ))
  590. );
  591. $context['finished'] = 1;
  592. break;
  593. case MigrationBase::RESULT_STOPPED:
  594. $context['finished'] = 1;
  595. // Skip any further operations
  596. $context['results']['stopped'] = TRUE;
  597. break;
  598. default:
  599. $context['finished'] = 1;
  600. break;
  601. }
  602. // Add any messages generated in this batch to the cumulative list
  603. foreach ($_migrate_messages as $message_data) {
  604. $context['results'][] = $message_data;
  605. }
  606. // While in progress, show the cumulative list of messages
  607. $full_message = '';
  608. foreach ($context['results'] as $message_data) {
  609. $full_message .= $message_data['message'] . '<br />';
  610. }
  611. $context['message'] = $full_message;
  612. }
  613. /**
  614. * Implements callback_batch_finished().
  615. *
  616. * Report results.
  617. *
  618. * @param $success
  619. * Ignored
  620. * @param $results
  621. * List of results from batch processing
  622. * @param $operations
  623. * Ignored
  624. */
  625. function migrate_ui_batch_finish($success, $results, $operations) {
  626. unset($results['stopped']);
  627. foreach ($results as $result) {
  628. // Migration::progressMessage() uses message levels that don't match what
  629. // drupal_set_message() expects.
  630. if (isset($result['level'])) {
  631. if ($result['level'] == 'completed') {
  632. $level = 'status';
  633. }
  634. elseif ($result['level'] == 'failed') {
  635. $level = 'error';
  636. }
  637. else {
  638. $level = $result['level'];
  639. }
  640. }
  641. else {
  642. $level = NULL;
  643. }
  644. drupal_set_message($result['message'], $level);
  645. }
  646. }
  647. /**
  648. * Store a message to be displayed later.
  649. *
  650. * Ignore the message if $level is set to 'debug'.
  651. *
  652. * @param string $message
  653. * the message to be displayed
  654. * @param string $level
  655. * the type of the message: 'debug', 'completed', 'failed', or a valid $type
  656. * used by drupal_set_message()
  657. */
  658. function migrate_ui_capture_message($message, $level) {
  659. if ($level != 'debug') {
  660. // Store each message as an array with keys 'message' and 'level'.
  661. global $_migrate_messages;
  662. $_migrate_messages[] = array(
  663. 'message' => filter_xss_admin($message),
  664. 'level' => check_plain($level),
  665. );
  666. }
  667. }
  668. /**
  669. * Menu callback function for migration view page.
  670. */
  671. function migrate_migration_info($form, $form_state, $group_name, $migration_name) {
  672. $migration = Migration::getInstance($migration_name);
  673. $has_mappings = $migration && method_exists($migration, 'getFieldMappings');
  674. $form = array();
  675. if ($has_mappings) {
  676. $field_mappings = $migration->getFieldMappings();
  677. // Identify what destination and source fields are mapped
  678. foreach ($field_mappings as $mapping) {
  679. $source_field = $mapping->getSourceField();
  680. $destination_field = $mapping->getDestinationField();
  681. $source_fields[$source_field] = $source_field;
  682. $destination_fields[$destination_field] = $destination_field;
  683. }
  684. $form['detail'] = array(
  685. '#type' => 'vertical_tabs',
  686. '#attached' => array(
  687. 'js' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.js'),
  688. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  689. ),
  690. );
  691. }
  692. else {
  693. $form['detail'] = array(
  694. '#type' => 'fieldset',
  695. );
  696. }
  697. $form['overview'] = array(
  698. '#type' => 'fieldset',
  699. '#title' => t('Overview'),
  700. '#group' => 'detail',
  701. );
  702. if (!$migration) {
  703. return $form;
  704. }
  705. $team = array();
  706. foreach ($migration->getTeam() as $member) {
  707. $email_address = $member->getEmailAddress();
  708. $team[$member->getGroup()][] =
  709. $member->getName() . ' <' . l($email_address, 'mailto:' . $email_address) . '>';
  710. }
  711. foreach ($team as $group => $list) {
  712. $form['overview'][$group] = array(
  713. '#type' => 'item',
  714. '#title' => filter_xss_admin($group),
  715. '#markup' => filter_xss_admin(implode(', ', $list)),
  716. );
  717. }
  718. $dependencies = $migration->getHardDependencies();
  719. if (count($dependencies) > 0) {
  720. $form['overview']['dependencies'] = array(
  721. '#title' => t('Dependencies') ,
  722. '#markup' => filter_xss_admin(implode(', ', $dependencies)),
  723. '#type' => 'item',
  724. );
  725. }
  726. $soft_dependencies = $migration->getSoftDependencies();
  727. if (count($soft_dependencies) > 0) {
  728. $form['overview']['soft_dependencies'] = array(
  729. '#title' => t('Soft Dependencies'),
  730. '#markup' => filter_xss_admin(implode(', ', $soft_dependencies)),
  731. '#type' => 'item',
  732. );
  733. }
  734. $form['overview']['group'] = array(
  735. '#title' => t('Group:'),
  736. '#markup' => filter_xss_admin($migration->getGroup()->getTitle()),
  737. '#type' => 'item',
  738. );
  739. if ($has_mappings) {
  740. switch ($migration->getSystemOfRecord()) {
  741. case Migration::SOURCE:
  742. $system_of_record = t('Source data');
  743. break;
  744. case Migration::DESTINATION:
  745. $system_of_record = t('Destination data');
  746. break;
  747. default:
  748. $system_of_record = t('Unknown');
  749. break;
  750. }
  751. $form['overview']['system_of_record'] = array(
  752. '#type' => 'item',
  753. '#title' => t('System of record:'),
  754. '#markup' => $system_of_record,
  755. );
  756. }
  757. $form['overview']['description'] = array(
  758. '#title' => t('Description:'),
  759. '#markup' => filter_xss_admin($migration->getDescription()),
  760. '#type' => 'item',
  761. );
  762. if ($has_mappings) {
  763. // Destination field information
  764. $form['destination'] = array(
  765. '#type' => 'fieldset',
  766. '#title' => t('Destination'),
  767. '#group' => 'detail',
  768. '#description' =>
  769. t('<p>These are the fields available in the destination of this migration
  770. task. The machine names listed here are those available to be used
  771. as the first parameter to $this->addFieldMapping() in your Migration
  772. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  773. );
  774. $destination = $migration->getDestination();
  775. $form['destination']['type'] = array(
  776. '#type' => 'item',
  777. '#title' => t('Type'),
  778. '#markup' => filter_xss_admin((string) $destination),
  779. );
  780. $dest_key = $destination->getKeySchema();
  781. $header = array(t('Machine name'), t('Description'));
  782. $rows = array();
  783. foreach ($destination->fields($migration) as $machine_name => $description) {
  784. $classes = array();
  785. if (isset($dest_key[$machine_name])) {
  786. // Identify primary key
  787. $machine_name .= ' ' . t('(PK)');
  788. }
  789. else {
  790. // Add class for mapped/unmapped. Used in summary.
  791. $classes[] = !isset($destination_fields[$machine_name]) ? 'migrate-error' : '';
  792. }
  793. $rows[] = array(
  794. array('data' => check_plain($machine_name), 'class' => $classes),
  795. array('data' => filter_xss_admin($description), 'class' => $classes),
  796. );
  797. }
  798. $classes = array();
  799. $form['destination']['fields'] = array(
  800. '#theme' => 'table',
  801. '#header' => $header,
  802. '#rows' => $rows,
  803. '#empty' => t('No fields'),
  804. );
  805. // TODO: Get source_fields from arguments
  806. $form['source'] = array(
  807. '#type' => 'fieldset',
  808. '#title' => t('Source'),
  809. '#group' => 'detail',
  810. '#description' =>
  811. t('<p>These are the fields available from the source of this migration
  812. task. The machine names listed here are those available to be used
  813. as the second parameter to $this->addFieldMapping() in your Migration
  814. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  815. );
  816. $source = $migration->getSource();
  817. $form['source']['query'] = array(
  818. '#type' => 'item',
  819. '#title' => t('Query'),
  820. '#markup' => '<pre>' . filter_xss_admin($source) . '</pre>',
  821. );
  822. $source_key = $migration->getMap()->getSourceKey();
  823. $header = array(t('Machine name'), t('Description'));
  824. $rows = array();
  825. foreach ($source->fields() as $machine_name => $description) {
  826. if (isset($source_key[$machine_name])) {
  827. // Identify primary key
  828. $machine_name .= ' ' . t('(PK)');
  829. }
  830. else {
  831. // Add class for mapped/unmapped. Used in summary.
  832. $classes = !isset($source_fields[$machine_name]) ? 'migrate-error' : '';
  833. }
  834. $rows[] = array(
  835. array('data' => check_plain($machine_name), 'class' => $classes),
  836. array('data' => filter_xss_admin($description), 'class' => $classes),
  837. );
  838. }
  839. $classes = array();
  840. $form['source']['fields'] = array(
  841. '#theme' => 'table',
  842. '#header' => $header,
  843. '#rows' => $rows,
  844. '#empty' => t('No fields'),
  845. );
  846. $header = array(t('Destination'), t('Source'), t('Default'), t('Description'), t('Priority'));
  847. // First group the mappings
  848. $descriptions = array();
  849. $source_fields = $source->fields();
  850. $destination_fields = $destination->fields($migration);
  851. foreach ($field_mappings as $mapping) {
  852. // Validate source and destination fields actually exist
  853. $source_field = $mapping->getSourceField();
  854. $destination_field = $mapping->getDestinationField();
  855. if (!is_null($source_field) && !isset($source_fields[$source_field])) {
  856. drupal_set_message(t('"!source" was used as source field in the
  857. "!destination" mapping but is not in list of source fields', array(
  858. '!source' => filter_xss_admin($source_field),
  859. '!destination' => filter_xss_admin($destination_field),
  860. )),
  861. 'warning');
  862. }
  863. if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) {
  864. drupal_set_message(t('"!destination" was used as destination field in
  865. "!source" mapping but is not in list of destination fields', array(
  866. '!source' => filter_xss_admin($source_field),
  867. '!destination' => filter_xss_admin($destination_field))),
  868. 'warning');
  869. }
  870. $descriptions[$mapping->getIssueGroup()][] = $mapping;
  871. }
  872. // Put out each group header
  873. foreach ($descriptions as $group => $mappings) {
  874. $form[$group] = array(
  875. '#type' => 'fieldset',
  876. '#title' => t('Mapping: !group', array('!group' => filter_xss_admin($group))),
  877. '#group' => 'detail',
  878. '#attributes' => array('class' => array('migrate-mapping')),
  879. );
  880. $rows = array();
  881. foreach ($mappings as $mapping) {
  882. $default = $mapping->getDefaultValue();
  883. if (is_array($default)) {
  884. $default = implode(',', $default);
  885. }
  886. $issue_priority = $mapping->getIssuePriority();
  887. if (!is_null($issue_priority)) {
  888. $classes[] = 'migrate-priority-' . drupal_html_class($issue_priority);
  889. $priority = MigrateFieldMapping::$priorities[$issue_priority];
  890. $issue_pattern = $migration->getIssuePattern();
  891. $issue_number = $mapping->getIssueNumber();
  892. if (!is_null($issue_pattern) && !is_null($issue_number)) {
  893. $priority .= ' (' . l(t('#') . $issue_number, str_replace(':id:', $issue_number,
  894. $issue_pattern)) . ')';
  895. }
  896. if ($issue_priority != MigrateFieldMapping::ISSUE_PRIORITY_OK) {
  897. $classes[] = 'migrate-error';
  898. }
  899. }
  900. else {
  901. $priority = t('OK');
  902. $classes[] = 'migrate-priority-' . 1;
  903. }
  904. $destination_field = $mapping->getDestinationField();
  905. $source_field = $mapping->getSourceField();
  906. // Highlight any mappings overridden in the database.
  907. if ($mapping->getMappingSource() == MigrateFieldMapping::MAPPING_SOURCE_DB) {
  908. $destination_field = "<em>$destination_field</em>";
  909. $source_field = "<em>$source_field</em>";
  910. }
  911. $row = array(
  912. array('data' => filter_xss_admin($destination_field), 'class' => $classes),
  913. array('data' => filter_xss_admin($source_field), 'class' => $classes),
  914. array('data' => filter_xss_admin($default), 'class' => $classes),
  915. array('data' => filter_xss_admin($mapping->getDescription()), 'class' => $classes),
  916. array('data' => filter_xss_admin($priority), 'class' => $classes),
  917. );
  918. $rows[] = $row;
  919. $classes = array();
  920. }
  921. $form[$group]['table'] = array(
  922. '#theme' => 'table',
  923. '#header' => $header,
  924. '#rows' => $rows,
  925. );
  926. }
  927. }
  928. return $form;
  929. }
  930. /**
  931. * Page callback to edit field mappings for a given migration.
  932. *
  933. * @param $form
  934. * @param $form_state
  935. * @param $group_name
  936. * @param $migration_name
  937. *
  938. * @return array
  939. */
  940. function migrate_ui_edit_mappings($form, $form_state, $group_name,
  941. $migration_name) {
  942. drupal_set_title(t('Edit !migration', array('!migration' => filter_xss_admin($migration_name))));
  943. $form = array();
  944. $form['#tree'] = TRUE;
  945. $form['machine_name'] = array(
  946. '#type' => 'value',
  947. '#value' => $migration_name,
  948. );
  949. $migration = Migration::getInstance($migration_name);
  950. if (!$migration) {
  951. return $form;
  952. }
  953. $source_migration_options = array('-1' => t(''),);
  954. $all_dependencies = array();
  955. foreach (migrate_migrations() as $machine_name => $migration_info) {
  956. $source_migration_options[$machine_name] = $machine_name;
  957. $dependencies = $migration_info->getDependencies();
  958. if (!empty($dependencies)) {
  959. $all_dependencies[$machine_name] = $dependencies;
  960. }
  961. }
  962. if (is_a($migration, 'Migration')) {
  963. $source_fields = $migration->getSource()->fields();
  964. $dest_fields = $migration->getDestination()->fields($migration);
  965. $dest_key = $migration->getDestination()->getKeySchema();
  966. $field_mappings = $migration->getFieldMappings();
  967. $form['field_mappings'] = array(
  968. '#type' => 'fieldset',
  969. '#title' => t('Field mappings'),
  970. '#collapsible' => TRUE,
  971. '#description' => t('For each field available in your Drupal destination, select the source field used to populate it. You can enter a default value to be applied to the destination when there is no source field, or the source field is empty in a given source item. Check the DNM (Do Not Migrate) box for destination fields you do not want populated by migration. Note that any changes you make here override any field mappings defined by the underlying migration module. Clicking the Revert button will remove all such overrides.'),
  972. '#theme' => array('migrate_ui_field_mapping_form'),
  973. '#prefix' => '<div id="field-mappings">',
  974. '#suffix' => '</div>',
  975. );
  976. // So the theme function knows whether to include the xpath column.
  977. $form['field_mappings']['#is_xml_migration'] = is_a($migration, 'XMLMigration');
  978. $form['source_fields'] = array(
  979. '#type' => 'fieldset',
  980. '#title' => t('Source fields'),
  981. '#collapsible' => TRUE,
  982. '#description' => t('Check off any source fields that are not being mapped to destination fields. They will be removed from the select lists above.'),
  983. );
  984. $base_options = array(
  985. '-1' => t(''),
  986. );
  987. $field_options = array();
  988. foreach ($source_fields as $name => $description) {
  989. // Clean up the source field description
  990. if (is_array($description)) {
  991. $description = reset($description);
  992. }
  993. $description = strip_tags($description);
  994. // Limit the description length
  995. if (strlen($description) > 50) {
  996. $short_description = substr($description, 0, 50) . '...';
  997. }
  998. else {
  999. $short_description = $description;
  1000. }
  1001. if (user_access(MIGRATE_ACCESS_ADVANCED)) {
  1002. $label_format = '!description [!source_field]';
  1003. }
  1004. else {
  1005. $label_format = '!description';
  1006. }
  1007. $label = t($label_format, array(
  1008. '!source_field' => filter_xss_admin($name),
  1009. '!description' => filter_xss_admin($description),
  1010. ));
  1011. $short_label = t($label_format, array(
  1012. '!source_field' => filter_xss_admin($name),
  1013. '!description' => filter_xss_admin($short_description),
  1014. ));
  1015. $dnm_value = 0;
  1016. // Check for a click of the DNM box...
  1017. if (isset($form_state['values']) &&
  1018. $form_state['values']['source_fields'][$name] == 1) {
  1019. $dnm_value = 1;
  1020. }
  1021. else {
  1022. // ... or a source-only mapping with the DNM issue group.
  1023. foreach ($field_mappings as $mapping) {
  1024. if ($mapping->getSourceField() == $name &&
  1025. $mapping->getIssueGroup() == t('DNM')) {
  1026. $dnm_value = 1;
  1027. }
  1028. }
  1029. }
  1030. // So, if this field is not marked DNM, include it in possible source
  1031. // options in the field mappings.
  1032. if (!$dnm_value) {
  1033. $field_options[$name] = $short_label;
  1034. }
  1035. $form['source_fields'][$name] = array(
  1036. '#type' => 'checkbox',
  1037. '#title' => $label,
  1038. '#default_value' => $dnm_value,
  1039. );
  1040. }
  1041. if (isset($field_mappings['is_new'])) {
  1042. $default_is_new = $field_mappings['is_new']->getDefaultValue();
  1043. } else {
  1044. $default_is_new = 0;
  1045. }
  1046. foreach ($dest_fields as $name => $description) {
  1047. // Don't map the destination key unless is_new = 1 (ie, TRUE)
  1048. if (isset($dest_key[$name]) && $default_is_new == 0) {
  1049. continue;
  1050. }
  1051. if (is_array($description)) {
  1052. $description = reset($description);
  1053. }
  1054. $options = $base_options + $field_options;
  1055. $default_mapping = '-1';
  1056. $default_value = '';
  1057. if (isset($field_mappings[$name])) {
  1058. $mapping = $field_mappings[$name];
  1059. }
  1060. else {
  1061. $mapping = NULL;
  1062. }
  1063. // If the DNM box has been clicked, make sure we clear the mapping and
  1064. // default value fields.
  1065. if (isset($form_state['values']) &&
  1066. $form_state['values']['field_mappings'][$name]['issue_group'] == 1) {
  1067. $dnm_value = 1;
  1068. }
  1069. else {
  1070. // Determine the default field mapping - if we have an existing one, use
  1071. // that, otherwise try to match on name.
  1072. if (isset($field_mappings[$name])) {
  1073. if ($mapping->getSourceField()) {
  1074. $default_mapping = $mapping->getSourceField();
  1075. }
  1076. $default_value = $mapping->getDefaultValue();
  1077. $dnm_value = ($mapping->getIssueGroup() == t('DNM'));
  1078. }
  1079. else {
  1080. $dnm_value = 0;
  1081. }
  1082. }
  1083. // Only show the machine name to advanced users.
  1084. if (user_access(MIGRATE_ACCESS_ADVANCED)) {
  1085. $label = '!description [!field_name]';
  1086. }
  1087. else {
  1088. $label = '!description';
  1089. }
  1090. // Indent subfields and options to show their relationship to the parent.
  1091. if (strpos($name, ':') > 0) {
  1092. $description = '&nbsp;&nbsp;' . $description;
  1093. }
  1094. $form['field_mappings'][$name]['issue_group'] = array(
  1095. '#type' => 'checkbox',
  1096. '#default_value' => $dnm_value,
  1097. );
  1098. $form['field_mappings'][$name]['mapping'] = array(
  1099. '#type' => 'select',
  1100. '#title' => t($label,
  1101. array('!description' => $description, '!field_name' => $name)),
  1102. '#options' => $options,
  1103. '#default_value' => $default_mapping,
  1104. );
  1105. $form['field_mappings'][$name]['default_value'] = array(
  1106. '#type' => 'textfield',
  1107. '#default_value' => $default_value,
  1108. '#size' => 20,
  1109. );
  1110. $source_migration = NULL;
  1111. if ($mapping) {
  1112. $source_migration = $mapping->getSourceMigration();
  1113. }
  1114. if (!$source_migration) {
  1115. $source_migration = '-1';
  1116. }
  1117. $form['field_mappings'][$name]['source_migration'] = array(
  1118. '#type' => 'select',
  1119. '#options' => $source_migration_options,
  1120. '#default_value' => $source_migration,
  1121. );
  1122. if (is_a($mapping, 'MigrateXMLFieldMapping')) {
  1123. /** @var MigrateXMLFieldMapping $mapping */
  1124. $form['field_mappings'][$name]['xpath'] = array(
  1125. '#type' => 'textfield',
  1126. '#default_value' => $mapping->getXpath(),
  1127. '#size' => 20,
  1128. );
  1129. }
  1130. }
  1131. }
  1132. unset($source_migration_options[-1]);
  1133. unset($source_migration_options[$migration_name]);
  1134. $form['dependencies'] = array(
  1135. '#type' => 'fieldset',
  1136. '#title' => t('Dependencies'),
  1137. '#collapsible' => TRUE,
  1138. '#collapsed' => is_a($migration, 'Migration'),
  1139. '#theme' => 'migrate_ui_field_mapping_dependencies',
  1140. '#description' => t('Set any dependencies on other migrations here. A <em>hard dependency</em> means that this migration is not allowed to run until the dependent migration has completed (without forcing it). A <em>soft dependency</em> places no such requirement - it simply affects the default order of migration. Note that any migrations that would lead to circular dependencies are not listed here.'),
  1141. );
  1142. // Get the list of possible dependencies - it's the same as the list of
  1143. // possible source migrations, minus the empty choice and ourselves.
  1144. $dependency_options = array(
  1145. 0 => t('None'),
  1146. 1 => t('Hard'),
  1147. 2 => t('Soft'),
  1148. );
  1149. // Remove any migrations depending on us, directly or indirectly. First, get
  1150. // a list of such migrations.
  1151. $descendent_migrations = _migrate_ui_get_descendents($migration_name,
  1152. $all_dependencies);
  1153. $source_migration_options = array_diff_key($source_migration_options,
  1154. $descendent_migrations);
  1155. foreach ($source_migration_options as $machine_name) {
  1156. if (in_array($machine_name, $migration->getHardDependencies())) {
  1157. $default_value = 1;
  1158. }
  1159. elseif (in_array($machine_name, $migration->getSoftDependencies())) {
  1160. $default_value = 2;
  1161. }
  1162. else {
  1163. $default_value = 0;
  1164. }
  1165. $form['dependencies'][$machine_name] = array(
  1166. '#type' => 'select',
  1167. '#title' => check_plain($machine_name),
  1168. '#default_value' => $default_value,
  1169. '#options' => $dependency_options,
  1170. );
  1171. }
  1172. $form['submit'] = array(
  1173. '#type' => 'submit',
  1174. '#value' => t('Apply changes'),
  1175. );
  1176. $form['revert'] = array(
  1177. '#type' => 'submit',
  1178. '#value' => t('Revert'),
  1179. '#submit' => array('migrate_ui_edit_mappings_revert'),
  1180. );
  1181. return $form;
  1182. }
  1183. /**
  1184. * Generate a list of all migrations dependent on a given migration.
  1185. *
  1186. * @param $migration_name
  1187. * @param array $all_dependencies
  1188. *
  1189. * @return array
  1190. */
  1191. function _migrate_ui_get_descendents($migration_name, array $all_dependencies) {
  1192. $descendents = array();
  1193. foreach ($all_dependencies as $machine_name => $dependencies) {
  1194. if (in_array($migration_name, $dependencies)) {
  1195. $descendents[$machine_name] = $machine_name;
  1196. $descendents += _migrate_ui_get_descendents($machine_name,
  1197. $all_dependencies);
  1198. }
  1199. }
  1200. return $descendents;
  1201. }
  1202. /**
  1203. * Submit callback for the edit mappings form. Save any choices made in the
  1204. * UI which override mappings made in code.
  1205. *
  1206. * @param $form
  1207. * @param $form_state
  1208. */
  1209. function migrate_ui_edit_mappings_submit(&$form, &$form_state) {
  1210. $machine_name = $form_state['values']['machine_name'];
  1211. $row = db_select('migrate_status', 'ms')
  1212. ->fields('ms', array('arguments', 'class_name', 'group_name'))
  1213. ->condition('machine_name', $machine_name)
  1214. ->execute()
  1215. ->fetchObject();
  1216. $class_name = $row->class_name;
  1217. $group_name = $row->group_name;
  1218. $arguments = unserialize($row->arguments);
  1219. $arguments['field_mappings'] = array();
  1220. $arguments['group_name'] = $group_name;
  1221. $field_mappings = array();
  1222. $default_values = array();
  1223. $issue_group_values = array();
  1224. $xpaths = array();
  1225. $migration = Migration::getInstance($machine_name);
  1226. if (is_a($migration, 'Migration')) {
  1227. $xml = is_a($migration, 'XMLMigration') ? TRUE : FALSE;
  1228. $existing_mappings = $migration->getFieldMappings();
  1229. $coded_mappings = $migration->getCodedFieldMappings();
  1230. foreach ($form_state['values']['field_mappings'] as $destination_field => $info) {
  1231. // Treat empty strings as NULL.
  1232. if ($info['default_value'] === '') {
  1233. $info['default_value'] = NULL;
  1234. }
  1235. if ($xml && $info['xpath'] === '') {
  1236. $info['xpath'] = NULL;
  1237. }
  1238. // If this mapping matches a coded mapping but not a stored mapping, remove
  1239. // it entirely (don't store it in the database) so the coded mapping is not
  1240. // overwritten.
  1241. if (isset($coded_mappings[$destination_field])) {
  1242. $coded_source_field =
  1243. $coded_mappings[$destination_field]->getSourceField();
  1244. $coded_default_value =
  1245. $coded_mappings[$destination_field]->getDefaultValue();
  1246. $coded_source_migration =
  1247. $coded_mappings[$destination_field]->getSourceMigration();
  1248. if ($xml) {
  1249. $coded_xpath =
  1250. $coded_mappings[$destination_field]->getXpath();
  1251. }
  1252. if ($info['mapping'] == '-1') {
  1253. $info['mapping'] = NULL;
  1254. }
  1255. if ($info['source_migration'] == '-1') {
  1256. $info['source_migration'] = NULL;
  1257. }
  1258. if ($info['issue_group'] == 0 && $coded_mappings[$destination_field]->getIssueGroup() != t('DNM') ||
  1259. $info['issue_group'] == 1 && $coded_mappings[$destination_field]->getIssueGroup() == t('DNM')) {
  1260. $dnm_matches = TRUE;
  1261. }
  1262. else {
  1263. $dnm_matches = FALSE;
  1264. }
  1265. if ($info['mapping'] == $coded_source_field &&
  1266. $info['default_value'] == $coded_default_value &&
  1267. $info['source_migration'] == $coded_source_migration &&
  1268. (!$xml || ($xml && ($info['xpath'] == $coded_xpath))) &&
  1269. $dnm_matches) {
  1270. continue;
  1271. }
  1272. }
  1273. // This is not a match for a coded mapping, so we will want to save it.
  1274. $field_mappings[$destination_field] = $info['mapping'];
  1275. $default_values[$destination_field] = $info['default_value'];
  1276. $source_migrations[$destination_field] = $info['source_migration'];
  1277. $issue_group_values[$destination_field] = $info['issue_group'];
  1278. if ($xml) {
  1279. $xpaths[$destination_field] = $info['xpath'];
  1280. }
  1281. }
  1282. foreach ($field_mappings as $destination_field => $source_field) {
  1283. if ($source_field == -1) {
  1284. $source_field = NULL;
  1285. }
  1286. if ($issue_group_values[$destination_field]) {
  1287. $source_field = NULL;
  1288. $default = NULL;
  1289. }
  1290. else {
  1291. $default = $default_values[$destination_field];
  1292. }
  1293. // Until we can provide editing of all the options that go along with
  1294. // field mappings, we want to avoid overwriting pre-existing mappings and
  1295. // losing important bits.
  1296. $mapping = NULL;
  1297. if (isset($existing_mappings[$destination_field]) &&
  1298. $issue_group_values[$destination_field] != 0) {
  1299. /** @var MigrateFieldMapping $old_mapping */
  1300. $old_mapping = $existing_mappings[$destination_field];
  1301. if ($source_field == $old_mapping->getSourceField() &&
  1302. $default_values[$destination_field] == $old_mapping->getDefaultValue() &&
  1303. $source_migrations[$destination_field] == $old_mapping->getSourceMigration() &&
  1304. (!$xml || ($xml && ($xpaths[$destination_field] == $old_mapping->getXpath())))) {
  1305. // First, if this mapping matches a previously-stored mapping, we want to
  1306. // preserve it as it was originally stored.
  1307. if ($old_mapping->getMappingSource() ==
  1308. MigrateFieldMapping::MAPPING_SOURCE_DB) {
  1309. $mapping = $old_mapping;
  1310. }
  1311. // If it matches a coded mapping, then we don't want to save it at all.
  1312. else {
  1313. continue;
  1314. }
  1315. }
  1316. }
  1317. // We're not skipping this mapping, or preserving an old one, so create the
  1318. // new mapping.
  1319. if (!$mapping) {
  1320. $mapping = _migrate_ui_get_mapping_object($migration, $destination_field, $source_field);
  1321. $mapping->defaultValue($default);
  1322. if ($xml && $xpaths[$destination_field]) {
  1323. $mapping->xpath($xpaths[$destination_field]);
  1324. }
  1325. }
  1326. if ($issue_group_values[$destination_field]) {
  1327. $mapping->issueGroup(t('DNM'));
  1328. }
  1329. else {
  1330. $mapping->issueGroup(NULL);
  1331. }
  1332. if ($source_migrations[$destination_field] &&
  1333. $source_migrations[$destination_field] != '-1') {
  1334. $mapping->sourceMigration($source_migrations[$destination_field]);
  1335. }
  1336. $arguments['field_mappings'][] = $mapping;
  1337. }
  1338. // Handle any source fields marked DNM.
  1339. foreach ($form_state['values']['source_fields'] as $source_field => $value) {
  1340. // Is this field ignored in the code?
  1341. $code_ignored = FALSE;
  1342. foreach ($coded_mappings as $destination_field => $mapping) {
  1343. if (is_numeric($destination_field) &&
  1344. $mapping->getSourceField() == $source_field) {
  1345. $code_ignored = TRUE;
  1346. }
  1347. }
  1348. // If it is marked DNM in the UI, but is not ignored in the code,
  1349. // generate a DNM mapping.
  1350. if ($value && !$code_ignored) {
  1351. $mapping = _migrate_ui_get_mapping_object($migration, NULL, $source_field);
  1352. $mapping->issueGroup(t('DNM'));
  1353. $arguments['field_mappings'][] = $mapping;
  1354. }
  1355. // If it is marked DNM in the code, but is unchecked in the UI, see if
  1356. // it's mapped to a destination field through the UI. If not, generate
  1357. // an empty mapping without the DNM so it's available to be mapped.
  1358. elseif (!$value && $code_ignored) {
  1359. $mapping_found = FALSE;
  1360. foreach ($field_mappings as $destination_field => $mapped_source_field) {
  1361. if ($source_field == $mapped_source_field) {
  1362. $mapping_found = TRUE;
  1363. break;
  1364. }
  1365. }
  1366. if (!$mapping_found) {
  1367. $mapping = _migrate_ui_get_mapping_object($migration, NULL, $source_field);
  1368. $arguments['field_mappings'][] = $mapping;
  1369. }
  1370. }
  1371. }
  1372. }
  1373. $arguments['dependencies'] = $arguments['soft_dependencies'] = array();
  1374. if (isset($form_state['values']['dependencies'])) {
  1375. foreach ($form_state['values']['dependencies'] as $dependency => $value) {
  1376. if ($value == 1) {
  1377. $arguments['dependencies'][] = $dependency;
  1378. }
  1379. elseif ($value == 2) {
  1380. $arguments['soft_dependencies'][] = $dependency;
  1381. }
  1382. }
  1383. }
  1384. Migration::registerMigration($class_name, $machine_name, $arguments);
  1385. drupal_set_message(t('Migration changes applied.'));
  1386. $form_state['redirect'] =
  1387. "admin/content/migrate/groups/$group_name/$machine_name";
  1388. }
  1389. /**
  1390. * Create a field mapping object of the appropriate class.
  1391. *
  1392. * @param $migration
  1393. *
  1394. * @return MigrateFieldMapping
  1395. */
  1396. function _migrate_ui_get_mapping_object($migration, $destination, $source) {
  1397. if (is_a($migration, 'XMLMigration')) {
  1398. return new MigrateXMLFieldMapping($destination, $source);
  1399. }
  1400. else {
  1401. return new MigrateFieldMapping($destination, $source);
  1402. }
  1403. }
  1404. /**
  1405. * Revert callback for the edit mappings form. Remove any field mappings that
  1406. * were defined through the UI.
  1407. *
  1408. * @param $form
  1409. * @param $form_state
  1410. */
  1411. function migrate_ui_edit_mappings_revert(&$form, &$form_state) {
  1412. $machine_name = $form_state['values']['machine_name'];
  1413. db_delete('migrate_field_mapping')
  1414. ->condition('machine_name', $machine_name)
  1415. ->execute();
  1416. // Note that not all field mappings in the database came from the UI - they
  1417. // may also have been passed through a hook_migrate_api registration, so
  1418. // reregister the migration to restore those mappings.
  1419. migrate_static_registration(array($machine_name));
  1420. }
  1421. /**
  1422. * Theme function to layout field mappings in a table.
  1423. *
  1424. * @param array $variables
  1425. *
  1426. * @return string
  1427. * Rendered markup.
  1428. */
  1429. function theme_migrate_ui_field_mapping_form($variables) {
  1430. $output = '';
  1431. $form = $variables['field_mappings'];
  1432. $elements = element_children($form);
  1433. if (!empty($elements)) {
  1434. $header = array(t('DNM'), t('Destination field'), t('Source field'),
  1435. t('Default value'), t('Source migration'));
  1436. if (!empty($form['#is_xml_migration'])) {
  1437. $header[] = t('Xpath');
  1438. }
  1439. $rows = array();
  1440. foreach ($elements as $mapping_key) {
  1441. $row = array();
  1442. $title = $form[$mapping_key]['mapping']['#title'];
  1443. unset($form[$mapping_key]['mapping']['#title']);
  1444. $row[] = drupal_render($form[$mapping_key]['issue_group']);
  1445. $row[] = $title;
  1446. $row[] = drupal_render($form[$mapping_key]['mapping']);
  1447. $row[] = drupal_render($form[$mapping_key]['default_value']);
  1448. $row[] = drupal_render($form[$mapping_key]['source_migration']);
  1449. if (!empty($form['#is_xml_migration'])) {
  1450. $row[] = drupal_render($form[$mapping_key]['xpath']);
  1451. }
  1452. $rows[] = $row;
  1453. }
  1454. $output .= theme('table', array('header' => $header, 'rows' => $rows));
  1455. }
  1456. $output .= drupal_render_children($form);
  1457. return $output;
  1458. }
  1459. /**
  1460. * Theme function to layout dependencies in a table.
  1461. *
  1462. * @param array $variables
  1463. *
  1464. * @return string
  1465. * Rendered markup.
  1466. */
  1467. function theme_migrate_ui_field_mapping_dependencies($variables) {
  1468. $output = '';
  1469. $form = $variables['dependencies'];
  1470. $header = array(t('Migration'), t('Dependency'));
  1471. $rows = array();
  1472. $elements = element_children($form);
  1473. foreach ($elements as $mapping_key) {
  1474. $row = array();
  1475. $title = $form[$mapping_key]['#title'];
  1476. unset($form[$mapping_key]['#title']);
  1477. $row[] = $title;
  1478. $row[] = drupal_render($form[$mapping_key]);
  1479. $rows[] = $row;
  1480. }
  1481. $output .= theme('table', array('rows' => $rows, 'header' => $header, 'empty' => t('No other migrations were found.')));
  1482. $output .= drupal_render_children($form);
  1483. return $output;
  1484. }
  1485. /**
  1486. * Menu callback for messages page
  1487. */
  1488. function migrate_ui_messages($group_name, $migration_name) {
  1489. drupal_set_title(t('Import messages for !migration',
  1490. array('!migration' => $migration_name)));
  1491. $build = $rows = array();
  1492. $migration = Migration::getInstance($migration_name);
  1493. if (!$migration) {
  1494. return $build;
  1495. }
  1496. $source_key = $migration->getMap()->getSourceKey();
  1497. $source_key_map = $migration->getMap()->getSourceKeyMap();
  1498. $source_key_map_flipped = array_flip($source_key_map);
  1499. $header = array();
  1500. // Add a table header for each source key in the migration's map.
  1501. foreach ($source_key as $key => $map_info) {
  1502. $header[] = array(
  1503. 'data' => filter_xss_admin($map_info['description']),
  1504. 'field' => $source_key_map[$key],
  1505. 'sort' => 'asc',
  1506. );
  1507. }
  1508. $header[] = array('data' => t('Level'), 'field' => 'level');
  1509. $header[] = array('data' => t('Message'), 'field' => 'message');
  1510. // TODO: need a general MigrateMap API
  1511. $messages = $migration->getMap()->getConnection()
  1512. ->select($migration->getMap()->getMessageTable(), 'msg')
  1513. ->extend('PagerDefault')
  1514. ->extend('TableSort')
  1515. ->orderByHeader($header)
  1516. ->limit(500)
  1517. ->fields('msg')
  1518. ->execute();
  1519. foreach ($messages as $message) {
  1520. $classes[] = $message->level <= MigrationBase::MESSAGE_WARNING ? 'migrate-error' : '';
  1521. $row = array();
  1522. // Add a table column for each source key.
  1523. foreach ($source_key_map_flipped as $source_key => $source_field) {
  1524. $row[] = array(
  1525. 'data' => filter_xss_admin($message->{$source_key}),
  1526. 'class' => $classes,
  1527. );
  1528. }
  1529. $row[] = array(
  1530. 'data' => filter_xss_admin($migration->getMessageLevelName($message->level)),
  1531. 'class' => $classes,
  1532. );
  1533. $row[] = array(
  1534. 'data' => filter_xss_admin($message->message),
  1535. 'class' => $classes,
  1536. );
  1537. $rows[] = $row;
  1538. unset($classes);
  1539. }
  1540. $build['messages'] = array(
  1541. '#theme' => 'table',
  1542. '#header' => $header,
  1543. '#rows' => $rows,
  1544. '#empty' => t('No messages'),
  1545. '#attached' => array(
  1546. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  1547. ),
  1548. );
  1549. $build['migrate_ui_pager'] = array('#theme' => 'pager');
  1550. return $build;
  1551. }
  1552. /**
  1553. * Given a migration machine name, remove its tracking from the database.
  1554. *
  1555. * @param $machine_name
  1556. */
  1557. function migrate_ui_deregister_migration($machine_name) {
  1558. // The class is gone, so we'll manually clear migrate_status, and make
  1559. // the default assumptions about the map/message tables.
  1560. db_drop_table('migrate_map_' . strtolower($machine_name));
  1561. db_drop_table('migrate_message_' . strtolower($machine_name));
  1562. db_delete('migrate_status')
  1563. ->condition('machine_name', $machine_name)
  1564. ->execute();
  1565. db_delete('migrate_field_mapping')
  1566. ->condition('machine_name', $machine_name)
  1567. ->execute();
  1568. drupal_set_message(t("Deregistered '!description' task",
  1569. array('!description' => $machine_name)));
  1570. }
  1571. /**
  1572. * Menu callback
  1573. */
  1574. function migrate_ui_configure() {
  1575. drupal_set_title(t('Configure migration settings'));
  1576. return drupal_get_form('migrate_ui_configure_form');
  1577. }
  1578. /**
  1579. * Form for reviewing migrations.
  1580. */
  1581. function migrate_ui_configure_form($form, &$form_state) {
  1582. $build = array();
  1583. $description = t('To register (or reregister with updated arguments) any
  1584. migrations defined in hook_migrate_api(), click the <strong>Register</strong>
  1585. button below.');
  1586. $build['registration'] = array(
  1587. '#type' => 'fieldset',
  1588. '#title' => t('Registration'),
  1589. '#description' => $description,
  1590. );
  1591. $build['registration']['submit'] = array(
  1592. '#type' => 'submit',
  1593. '#value' => t('Register statically defined classes'),
  1594. '#submit' => array('migrate_ui_configure_register_submit'),
  1595. );
  1596. $migrations = array();
  1597. $result = db_select('migrate_status', 'ms')
  1598. ->fields('ms', array('class_name', 'machine_name'))
  1599. ->execute();
  1600. $migration_list = '';
  1601. foreach ($result as $row) {
  1602. if (!class_exists($row->class_name)) {
  1603. $migrations[] = $row->machine_name;
  1604. $migration_list .= '<li>' . t('!migration (class !class)',
  1605. array(
  1606. '!migration' => filter_xss_admin($row->machine_name),
  1607. '!class' => filter_xss_admin($row->class_name),
  1608. )) . "</li>\n";
  1609. }
  1610. }
  1611. if (!empty($migrations)) {
  1612. $description = t('No class currently exists for the following migrations: <ul>!list</ul>',
  1613. array('!list' => $migration_list));
  1614. $build['deregistration'] = array(
  1615. '#type' => 'fieldset',
  1616. '#title' => t('Orphaned migration tasks'),
  1617. '#description' => $description,
  1618. );
  1619. $build['deregistration']['submit'] = array(
  1620. '#type' => 'submit',
  1621. '#value' => t('Deregister orphans'),
  1622. '#submit' => array('migrate_ui_configure_deregister_submit'),
  1623. );
  1624. }
  1625. $build['settings'] = array(
  1626. '#type' => 'fieldset',
  1627. '#title' => t('Settings'),
  1628. );
  1629. $build['settings']['deprecation_warnings'] = array(
  1630. '#type' => 'checkbox',
  1631. '#title' => t('Warn on usage of deprecated patterns'),
  1632. '#default_value' => variable_get('migrate_deprecation_warnings', 1),
  1633. '#description' => t('When checked, you will receive warnings when using methods and patterns that are deprecated as of Migrate 2.6.'),
  1634. );
  1635. $build['settings']['submit'] = array(
  1636. '#type' => 'submit',
  1637. '#value' => t('Save settings'),
  1638. '#submit' => array('migrate_ui_configure_settings_submit'),
  1639. );
  1640. // Configure background imports if the drush command has been set.
  1641. $drush_path = trim(variable_get('migrate_drush_path', ''));
  1642. $drush_validated = FALSE;
  1643. if ($drush_path && is_executable($drush_path)) {
  1644. // Try running a drush status command to verify it's properly configured.
  1645. $uri = $GLOBALS['base_url'];
  1646. $uid = $GLOBALS['user']->uid;
  1647. $command = "$drush_path status --user=$uid --uri=$uri --root=" . DRUPAL_ROOT;
  1648. exec($command, $output, $status);
  1649. if ($status == 0) {
  1650. $version = '';
  1651. foreach ($output as $line) {
  1652. if (preg_match('|Drush version +: +(.*)|', $line, $matches)) {
  1653. $version = trim($matches[1]);
  1654. }
  1655. elseif (preg_match('|Drupal bootstrap +: +Successful *|', $line, $matches)) {
  1656. $drush_validated = TRUE;
  1657. }
  1658. }
  1659. }
  1660. if ($drush_validated) {
  1661. $description = t('Configure the use of <a href="@drush">drush</a> version @version to run import and rollback operations in the background.',
  1662. array(
  1663. '@drush' => 'http://drupal.org/project/drush',
  1664. '@version' => $version,
  1665. )
  1666. );
  1667. }
  1668. else {
  1669. $description = t('<a href="@drush">Drush</a> is misconfigured on the server. Please review the <a href="@config">documentation</a> on <a href="@dorg">drupal.org</a>, and make sure everything is set up correctly.',
  1670. array(
  1671. '@drush' => 'http://drupal.org/project/drush',
  1672. '@config' => 'http://drupal.org/node/1958170',
  1673. )
  1674. );
  1675. }
  1676. }
  1677. else {
  1678. $description = t('To enable running operations in the background with <a href="@drush">drush</a>, (which is <a href="@recommended">recommended</a>), some configuration must be done on the server. See the <a href="@config">documentation</a> on <a href="@dorg">drupal.org</a>.',
  1679. array(
  1680. '@drush' => 'http://drupal.org/project/drush',
  1681. '@recommended' => 'http://drupal.org/node/1806824',
  1682. '@config' => 'http://drupal.org/node/1958170',
  1683. '@dorg' => 'http://drupal.org/',
  1684. )
  1685. );
  1686. }
  1687. $build['drush'] = array(
  1688. '#type' => 'fieldset',
  1689. '#collapsible' => TRUE,
  1690. '#collapsed' => FALSE,
  1691. '#title' => t('Background operations'),
  1692. '#description' => $description,
  1693. );
  1694. if ($drush_validated) {
  1695. $options = array(
  1696. 0 => t('Immediate operation only'),
  1697. 1 => t('Background operation only'),
  1698. 2 => t('Allows both immediate and background operations'),
  1699. );
  1700. $build['drush']['migrate_import_method'] = array(
  1701. '#type' => 'select',
  1702. '#options' => $options,
  1703. '#default_value' => variable_get('migrate_import_method', 0),
  1704. '#title' => t('Import method'),
  1705. '#description' => t('Choose whether the dashboard will perform operations immediately, in the background via drush, or offer a choice.'),
  1706. );
  1707. $build['drush']['migrate_drush_mail'] = array(
  1708. '#type' => 'checkbox',
  1709. '#default_value' => variable_get('migrate_drush_mail', 0),
  1710. '#title' => t('Send email notification when a background operation completes.'),
  1711. );
  1712. $build['drush']['migrate_drush_mail_subject'] = array(
  1713. '#type' => 'textfield',
  1714. '#default_value' => variable_get('migrate_drush_mail_subject',
  1715. t('Migration operation completed')),
  1716. '#title' => t('Subject'),
  1717. '#states' => array(
  1718. 'visible' => array(
  1719. ':input[name="migrate_drush_mail"]' => array('checked' => TRUE),
  1720. ),
  1721. ),
  1722. );
  1723. $build['drush']['migrate_drush_mail_body'] = array(
  1724. '#type' => 'textarea',
  1725. '#rows' => 10,
  1726. '#default_value' => variable_get('migrate_drush_mail_body',
  1727. t('The migration operation is complete. Please review the results on your dashboard')),
  1728. '#title' => t('Body'),
  1729. '#states' => array(
  1730. 'visible' => array(
  1731. ':input[name="migrate_drush_mail"]' => array('checked' => TRUE),
  1732. ),
  1733. ),
  1734. );
  1735. $build['drush']['save_drush_config'] = array(
  1736. '#type' => 'submit',
  1737. '#value' => t('Save background configuration'),
  1738. '#submit' => array('migrate_ui_configure_drush_submit'),
  1739. );
  1740. }
  1741. $build['handlers'] = array(
  1742. '#type' => 'fieldset',
  1743. '#title' => t('Handlers'),
  1744. '#description' => t('In some cases, such as when a field handler for a contributed module is implemented in both migrate_extras and the module itself, you may need to disable a particular handler. In this case, you may uncheck the undesired handler below.'),
  1745. '#collapsible' => TRUE,
  1746. '#collapsed' => TRUE,
  1747. );
  1748. $build['handlers']['destination'] = array(
  1749. '#type' => 'fieldset',
  1750. '#title' => t('Destination handlers'),
  1751. '#collapsible' => TRUE,
  1752. );
  1753. $header = array(
  1754. 'module' => array('data' => t('Module')),
  1755. 'class' => array('data' => t('Class')),
  1756. 'types' => array('data' => t('Destination types handled')),
  1757. );
  1758. $disabled = unserialize(variable_get('migrate_disabled_handlers',
  1759. serialize(array())));
  1760. $class_list = _migrate_class_list('MigrateDestinationHandler');
  1761. $rows = array();
  1762. $default_values = array();
  1763. foreach ($class_list as $class_name => $handler) {
  1764. $row = array();
  1765. $module = db_select('registry', 'r')
  1766. ->fields('r', array('module'))
  1767. ->condition('name', $class_name)
  1768. ->condition('type', 'class')
  1769. ->execute()
  1770. ->fetchField();
  1771. $row['module'] = check_plain($module);
  1772. $row['class'] = check_plain($class_name);
  1773. $row['types'] = filter_xss_admin(implode(', ', $handler->getTypesHandled()));
  1774. $default_values[$class_name] = !in_array($class_name, $disabled);
  1775. $rows[$class_name] = $row;
  1776. }
  1777. $build['handlers']['destination']['destination_handlers'] = array(
  1778. '#type' => 'tableselect',
  1779. '#header' => $header,
  1780. '#options' => $rows,
  1781. '#default_value' => $default_values,
  1782. '#empty' => t('No destination handlers found'),
  1783. );
  1784. $build['handlers']['field'] = array(
  1785. '#type' => 'fieldset',
  1786. '#title' => t('Field handlers'),
  1787. '#collapsible' => TRUE,
  1788. );
  1789. $header = array(
  1790. 'module' => array('data' => t('Module')),
  1791. 'class' => array('data' => t('Class')),
  1792. 'types' => array('data' => t('Field types handled')),
  1793. );
  1794. $class_list = _migrate_class_list('MigrateFieldHandler');
  1795. $rows = array();
  1796. $default_values = array();
  1797. foreach ($class_list as $class_name => $handler) {
  1798. $row = array();
  1799. $module = db_select('registry', 'r')
  1800. ->fields('r', array('module'))
  1801. ->condition('name', $class_name)
  1802. ->condition('type', 'class')
  1803. ->execute()
  1804. ->fetchField();
  1805. $row['module'] = check_plain($module);
  1806. $row['class'] = check_plain($class_name);
  1807. $row['types'] = filter_xss_admin(implode(', ', $handler->getTypesHandled()));
  1808. $default_values[$class_name] = !in_array($class_name, $disabled);
  1809. $rows[$class_name] = $row;
  1810. }
  1811. $build['handlers']['field']['field_handlers'] = array(
  1812. '#type' => 'tableselect',
  1813. '#header' => $header,
  1814. '#options' => $rows,
  1815. '#default_value' => $default_values,
  1816. '#empty' => t('No field handlers found'),
  1817. );
  1818. $build['handlers']['submit'] = array(
  1819. '#type' => 'submit',
  1820. '#value' => t('Save handler statuses'),
  1821. '#submit' => array('migrate_ui_configure_submit'),
  1822. );
  1823. return $build;
  1824. }
  1825. /**
  1826. * Submit callback for the configuration form registration fieldset.
  1827. */
  1828. function migrate_ui_configure_register_submit($form, &$form_state) {
  1829. migrate_static_registration();
  1830. drupal_set_message(t('All statically defined migrations have been (re)registered.'));
  1831. $form_state['redirect'] = 'admin/content/migrate';
  1832. }
  1833. /**
  1834. * Submit callback for the configuration form deregistration fieldset.
  1835. */
  1836. function migrate_ui_configure_deregister_submit($form, &$form_state) {
  1837. $result = db_select('migrate_status', 'ms')
  1838. ->fields('ms', array('class_name', 'machine_name'))
  1839. ->execute();
  1840. foreach ($result as $row) {
  1841. if (!class_exists($row->class_name)) {
  1842. migrate_ui_deregister_migration($row->machine_name);
  1843. }
  1844. }
  1845. $form_state['redirect'] = 'admin/content/migrate';
  1846. }
  1847. /**
  1848. * Submit callback for the configuration form settings fieldset.
  1849. */
  1850. function migrate_ui_configure_settings_submit($form, &$form_state) {
  1851. variable_set('migrate_deprecation_warnings',
  1852. $form_state['values']['deprecation_warnings']);
  1853. drupal_set_message(t('Migration settings saved.'));
  1854. }
  1855. /**
  1856. * Submit callback for the drush configuration form handler fieldset.
  1857. */
  1858. function migrate_ui_configure_drush_submit($form, &$form_state) {
  1859. $values = $form_state['values'];
  1860. variable_set('migrate_import_method', $values['migrate_import_method']);
  1861. variable_set('migrate_drush_mail', $values['migrate_drush_mail']);
  1862. variable_set('migrate_drush_mail_subject', $values['migrate_drush_mail_subject']);
  1863. variable_set('migrate_drush_mail_body', $values['migrate_drush_mail_body']);
  1864. }
  1865. /**
  1866. * Submit callback for the handler configuration form handler fieldset.
  1867. */
  1868. function migrate_ui_configure_submit($form, &$form_state) {
  1869. $disabled = array();
  1870. foreach ($form_state['values']['destination_handlers'] as $class => $value) {
  1871. if (!$value) {
  1872. $disabled[] = $class;
  1873. }
  1874. }
  1875. foreach ($form_state['values']['field_handlers'] as $class => $value) {
  1876. if (!$value) {
  1877. $disabled[] = $class;
  1878. }
  1879. }
  1880. variable_set('migrate_disabled_handlers', serialize($disabled));
  1881. if (!empty($disabled)) {
  1882. drupal_set_message(t('The following handler classes are disabled: @classes',
  1883. array('@classes' => implode(', ', $disabled))));
  1884. }
  1885. else {
  1886. drupal_set_message(t('No handler classes are currently disabled.'));
  1887. }
  1888. }