migrate_ui.pages.inc 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841
  1. <?php
  2. /**
  3. * @file
  4. */
  5. /**
  6. * Menu callback
  7. */
  8. function migrate_ui_dashboard() {
  9. drupal_set_title(t('Migrate'));
  10. return drupal_get_form('migrate_ui_dashboard_form');
  11. }
  12. /**
  13. * Form for reviewing migrations.
  14. */
  15. function migrate_ui_dashboard_form($form, &$form_state) {
  16. $build = array();
  17. $build['overview'] = array(
  18. '#prefix' => '<div>',
  19. '#markup' => migrate_overview(),
  20. '#suffix' => '</div>',
  21. );
  22. $header = array(
  23. 'status' => array('data' => t('Status')),
  24. 'machinename' => array('data' => t('Migration')),
  25. 'importrows' => array('data' => t('Total rows')),
  26. 'imported' => array('data' => t('Imported')),
  27. 'unimported' => array('data' => t('Unimported')),
  28. 'messages' => array('data' => t('Messages')),
  29. 'lastthroughput' => array('data' => t('Throughput')),
  30. 'lastimported' => array('data' => t('Last imported')),
  31. );
  32. $migrations = migrate_migrations();
  33. $rows = array();
  34. foreach ($migrations as $migration) {
  35. $row = array();
  36. $has_counts = TRUE;
  37. if (method_exists($migration, 'sourceCount')) {
  38. $total = $migration->sourceCount();
  39. if ($total < 0) {
  40. $has_counts = FALSE;
  41. $total = t('N/A');
  42. }
  43. }
  44. else {
  45. $has_counts = FALSE;
  46. $total = t('N/A');
  47. }
  48. if (method_exists($migration, 'importedCount')) {
  49. $imported = $migration->importedCount();
  50. $processed = $migration->processedCount();
  51. }
  52. else {
  53. $has_counts = FALSE;
  54. $imported = t('N/A');
  55. }
  56. if ($has_counts) {
  57. $unimported = $total - $processed;
  58. }
  59. else {
  60. $unimported = t('N/A');
  61. }
  62. $status = $migration->getStatus();
  63. switch ($status) {
  64. case MigrationBase::STATUS_IDLE:
  65. $status = t('Idle');
  66. break;
  67. case MigrationBase::STATUS_IMPORTING:
  68. $status = t('Importing');
  69. break;
  70. case MigrationBase::STATUS_ROLLING_BACK:
  71. $status = t('Rolling back');
  72. break;
  73. case MigrationBase::STATUS_STOPPING:
  74. $status = t('Stopping');
  75. break;
  76. case MigrationBase::STATUS_DISABLED:
  77. $status = t('Disabled');
  78. break;
  79. default:
  80. $status = t('Unknown');
  81. break;
  82. }
  83. $row['status'] = $status;
  84. $machine_name = $migration->getMachineName();
  85. $row['machinename'] =
  86. l($machine_name, 'admin/content/migrate/' . $machine_name);
  87. $row['importrows'] = $total;
  88. $row['imported'] = $imported;
  89. $row['unimported'] = $unimported;
  90. if (is_subclass_of($migration, 'Migration')) {
  91. $num_messages = $migration->messageCount();
  92. $row['messages'] = $num_messages ? l($num_messages, 'admin/content/migrate/messages/' . $machine_name) : 0;
  93. }
  94. else {
  95. $row['messages'] = t('N/A');
  96. }
  97. if (method_exists($migration, 'getLastThroughput')) {
  98. $rate = $migration->getLastThroughput();
  99. if ($rate == '') {
  100. $row['lastthroughput'] = t('Unknown');
  101. }
  102. elseif ($status == MigrationBase::STATUS_IDLE) {
  103. $row['lastthroughput'] = t('!rate/min', array('!rate' => $rate));
  104. }
  105. else {
  106. if ($rate > 0) {
  107. $row['lastthroughput'] = t('!rate/min, !time remaining', array('!rate' => $rate, '!time' => format_interval((60*$unimported) / $rate)));
  108. }
  109. else {
  110. $row['lastthroughput'] = t('!rate/min, unknown time remaining', array('!rate' => $rate));
  111. }
  112. }
  113. }
  114. else {
  115. $row['lastthroughput'] = t('N/A');
  116. }
  117. $row['lastimported'] = $migration->getLastImported();
  118. $rows[$machine_name] = $row;
  119. }
  120. $build['dashboard'] = array(
  121. '#type' => 'tableselect',
  122. '#header' => $header,
  123. '#options' => $rows,
  124. '#empty' => t('No migrations'),
  125. );
  126. // Build the 'Update options' form.
  127. $build['operations'] = array(
  128. '#type' => 'fieldset',
  129. '#title' => t('Operations'),
  130. );
  131. $options = array(
  132. 'import' => t('Import'),
  133. 'rollback' => t('Rollback'),
  134. 'rollback_and_import' => t('Rollback and import'),
  135. 'stop' => t('Stop'),
  136. 'reset' => t('Reset'),
  137. );
  138. $build['operations']['operation'] = array(
  139. '#type' => 'select',
  140. '#title' => t('Operation'),
  141. '#title_display' => 'invisible',
  142. '#options' => $options,
  143. );
  144. $build['operations']['submit'] = array(
  145. '#type' => 'submit',
  146. '#value' => t('Execute'),
  147. '#validate' => array('migrate_ui_dashboard_validate'),
  148. '#submit' => array('migrate_ui_dashboard_submit'),
  149. );
  150. $build['operations']['description'] = array(
  151. '#prefix' => '<p>',
  152. '#markup' => t(
  153. 'Choose an operation to run on all migrations selected above:
  154. <ul>
  155. <li>Import - Imports all previously unimported records from the source, plus
  156. any records marked for update, into destination Drupal objects.</li>
  157. <li>Rollback - Deletes all Drupal objects created by the migration.</li>
  158. <li>Rollback and import - Performs the Rollback operation, immediately
  159. followed by the Import operation.</li>
  160. <li>Stop - Cleanly interrupts any import or rollback processes that may
  161. currently be running.</li>
  162. <li>Reset - Sometimes a migration process may fail to stop cleanly, and be
  163. left stuck in an Importing or Rolling Back status. Choose Reset to clear
  164. the status and permit other operations to proceed.</li>
  165. </ul>'
  166. ),
  167. '#postfix' => '</p>',
  168. );
  169. $build['operations']['options'] = array(
  170. '#type' => 'fieldset',
  171. '#title' => t('Options'),
  172. '#collapsible' => TRUE,
  173. '#collapsed' => TRUE,
  174. );
  175. $build['operations']['options']['update'] = array(
  176. '#type' => 'checkbox',
  177. '#title' => t('Update'),
  178. '#description' => t('Check this box to update all previously-migrated content
  179. in addition to importing new content. Leave unchecked to only import
  180. new content'),
  181. );
  182. $build['operations']['options']['force'] = array(
  183. '#type' => 'checkbox',
  184. '#title' => t('Ignore dependencies'),
  185. '#description' => t('Check this box to ignore dependencies when running imports
  186. - all migrations will run whether or not their dependent migrations have
  187. completed.'),
  188. );
  189. $build['operations']['options']['limit'] = array(
  190. '#tree' => TRUE,
  191. '#type' => 'fieldset',
  192. '#attributes' => array('class' => array('container-inline')),
  193. 'value' => array(
  194. '#type' => 'textfield',
  195. '#title' => t('Limit to:'),
  196. '#size' => 10,
  197. ),
  198. 'unit' => array(
  199. '#type' => 'select',
  200. '#options' => array(
  201. 'items' => t('items'),
  202. 'seconds' => t('seconds'),
  203. ),
  204. '#description' => t('Set a limit of how many items to process for
  205. each migration, or how long each should run.'),
  206. ),
  207. );
  208. return $build;
  209. }
  210. /**
  211. * Validate callback for the dashboard form.
  212. */
  213. function migrate_ui_dashboard_validate($form, &$form_state) {
  214. // Error if there are no items to select.
  215. if (!is_array($form_state['values']['dashboard']) || !count(array_filter($form_state['values']['dashboard']))) {
  216. form_set_error('', t('No items selected.'));
  217. }
  218. }
  219. /**
  220. * Submit callback for the dashboard form.
  221. */
  222. function migrate_ui_dashboard_submit($form, &$form_state) {
  223. $operation = $form_state['values']['operation'];
  224. $limit = $form_state['values']['limit'];
  225. $update = $form_state['values']['update'];
  226. $force = $form_state['values']['force'];
  227. $machine_names = array_filter($form_state['values']['dashboard']);
  228. $operations = array();
  229. // Rollback in reverse order.
  230. if (in_array($operation, array('rollback', 'rollback_and_import'))) {
  231. $machine_names = array_reverse($machine_names);
  232. foreach ($machine_names as $machine_name) {
  233. $operations[] = array('migrate_ui_batch', array('rollback', $machine_name, $limit));
  234. }
  235. // Reset order of machines names in preparation for final operation.
  236. $machine_names = array_reverse($machine_names);
  237. $operation = $operation == 'rollback_and_import' ? 'import' : NULL;
  238. }
  239. // Perform non-rollback operation, if one exists.
  240. if ($operation) {
  241. foreach ($machine_names as $machine_name) {
  242. $migration = Migration::getInstance($machine_name);
  243. // Update (if necessary) once, before starting
  244. if ($update && method_exists($migration, 'prepareUpdate')) {
  245. $migration->prepareUpdate();
  246. }
  247. $operations[] = array('migrate_ui_batch', array($operation, $machine_name, $limit, $force));
  248. }
  249. }
  250. $batch = array(
  251. 'operations' => $operations,
  252. 'title' => t('Migration processing'),
  253. 'file' => drupal_get_path('module', 'migrate_ui') . '/migrate_ui.pages.inc',
  254. 'init_message' => t('Starting migration process'),
  255. 'progress_message' => t(''),
  256. 'error_message' => t('An error occurred. Some or all of the migrate processing has failed.'),
  257. 'finished' => 'migrate_ui_batch_finish',
  258. );
  259. batch_set($batch);
  260. }
  261. /**
  262. * Process all enabled migration processes in a browser, using the Batch API
  263. * to break it into manageable chunks.
  264. *
  265. * @param $operation
  266. * Operation to perform - 'import', 'rollback', 'stop', or 'reset'.
  267. * @param $machine_name
  268. * Machine name of migration to process.
  269. * @param $limit
  270. * An array indicating the number of items to import or rollback, or the
  271. * number of seconds to process. Should include 'unit' (either 'items' or
  272. * 'seconds') and 'value'.
  273. * @param $context
  274. * Batch API context structure
  275. */
  276. function migrate_ui_batch($operation, $machine_name, $limit, $force = FALSE, &$context) {
  277. // If we got a stop message, skip everything else
  278. if (isset($context['results']['stopped'])) {
  279. $context['finished'] = 1;
  280. return;
  281. }
  282. $migration = Migration::getInstance($machine_name);
  283. // Messages generated by migration processes will be captured in this global
  284. global $_migrate_messages;
  285. $_migrate_messages = array();
  286. Migration::setDisplayFunction('migrate_ui_capture_message');
  287. // Perform the requested operation
  288. switch ($operation) {
  289. case 'import':
  290. $result = $migration->processImport(array('limit' => $limit, 'force' => $force));
  291. break;
  292. case 'rollback':
  293. $result = $migration->processRollback(array('limit' => $limit, 'force' => $force));
  294. break;
  295. case 'stop':
  296. $migration->stopProcess();
  297. $result = Migration::RESULT_COMPLETED;
  298. break;
  299. case 'reset':
  300. $migration->resetStatus();
  301. $result = Migration::RESULT_COMPLETED;
  302. break;
  303. }
  304. switch ($result) {
  305. case Migration::RESULT_INCOMPLETE:
  306. // Default to half-done, in case we can't get a more precise fix
  307. $context['finished'] = .5;
  308. if (method_exists($migration, 'sourceCount')) {
  309. $total = $migration->sourceCount();
  310. if ($total > 0 && method_exists($migration, 'importedCount')) {
  311. $processed = $migration->processedCount();
  312. switch ($operation) {
  313. case 'import':
  314. $to_update = $migration->updateCount();
  315. $context['finished'] = ($processed-$to_update)/$total;
  316. break;
  317. case 'rollback':
  318. $context['finished'] = ($total-$processed)/$total;
  319. break;
  320. }
  321. }
  322. }
  323. break;
  324. case MigrationBase::RESULT_SKIPPED:
  325. $_migrate_messages[] = t("Skipped !name due to unfulfilled dependencies: !depends",
  326. array(
  327. '!name' => $machine_name,
  328. '!depends' => implode(", ", $migration->incompleteDependencies()),
  329. ));
  330. $context['finished'] = 1;
  331. break;
  332. case MigrationBase::RESULT_STOPPED:
  333. $context['finished'] = 1;
  334. // Skip any further operations
  335. $context['results']['stopped'] = TRUE;
  336. break;
  337. default:
  338. $context['finished'] = 1;
  339. break;
  340. }
  341. // Add any messages generated in this batch to the cumulative list
  342. foreach ($_migrate_messages as $message) {
  343. $context['results'][] = $message;
  344. }
  345. // While in progress, show the cumulative list of messages
  346. $full_message = '';
  347. foreach ($context['results'] as $message) {
  348. $full_message .= $message . '<br />';
  349. }
  350. $context['message'] = $full_message;
  351. }
  352. /**
  353. * Batch API finished callback - report results
  354. *
  355. * @param $success
  356. * Ignored
  357. * @param $results
  358. * List of results from batch processing
  359. * @param $operations
  360. * Ignored
  361. */
  362. function migrate_ui_batch_finish($success, $results, $operations) {
  363. unset($results['stopped']);
  364. foreach ($results as $result) {
  365. drupal_set_message($result);
  366. }
  367. }
  368. function migrate_ui_capture_message($message, $level) {
  369. if ($level != 'debug') {
  370. global $_migrate_messages;
  371. $_migrate_messages[] = $message;
  372. }
  373. }
  374. /**
  375. * Menu callback for messages page
  376. */
  377. function migrate_ui_messages($migration) {
  378. $build = $rows = array();
  379. $header = array(
  380. array('data' => t('Source ID'), 'field' => 'sourceid1', 'sort' => 'asc'),
  381. array('data' => t('Level'), 'field' => 'level'),
  382. array('data' => t('Message'), 'field' => 'message'),
  383. );
  384. if (is_string($migration)) {
  385. $migration = migration_load($migration);
  386. }
  387. // TODO: need a general MigrateMap API
  388. $messages = $migration->getMap()->getConnection()
  389. ->select($migration->getMap()->getMessageTable(), 'msg')
  390. ->extend('PagerDefault')
  391. ->extend('TableSort')
  392. ->orderByHeader($header)
  393. ->limit(500)
  394. ->fields('msg')
  395. ->execute();
  396. foreach ($messages as $message) {
  397. $classes[] = $message->level <= MigrationBase::MESSAGE_WARNING ? 'migrate-error' : '';
  398. $rows[] = array(
  399. array('data' => $message->sourceid1, 'class' => $classes), // TODO: deal with compound keys
  400. array('data' => $migration->getMessageLevelName($message->level), 'class' => $classes),
  401. array('data' => $message->message, 'class' => $classes),
  402. );
  403. unset($classes);
  404. }
  405. $build['messages'] = array(
  406. '#theme' => 'table',
  407. '#header' => $header,
  408. '#rows' => $rows,
  409. '#empty' => t('No messages'),
  410. '#attached' => array(
  411. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  412. ),
  413. );
  414. $build['migrate_ui_pager'] = array('#theme' => 'pager');
  415. return $build;
  416. }
  417. /**
  418. * Menu callback function for migration view page.
  419. */
  420. function migrate_migration_info($form, $form_state, $migration) {
  421. if (is_string($migration)) {
  422. $migration = migration_load($migration);
  423. }
  424. $has_mappings = method_exists($migration, 'getFieldMappings');
  425. $form = array();
  426. if ($has_mappings) {
  427. $field_mappings = $migration->getFieldMappings();
  428. // Identify what destination and source fields are mapped
  429. foreach ($field_mappings as $mapping) {
  430. $source_field = $mapping->getSourceField();
  431. $destination_field = $mapping->getDestinationField();
  432. $source_fields[$source_field] = $source_field;
  433. $destination_fields[$destination_field] = $destination_field;
  434. }
  435. $form['detail'] = array(
  436. '#type' => 'vertical_tabs',
  437. '#attached' => array(
  438. 'js' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.js'),
  439. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  440. ),
  441. );
  442. }
  443. else {
  444. $form['detail'] = array(
  445. '#type' => 'fieldset',
  446. );
  447. }
  448. $form['overview'] = array(
  449. '#type' => 'fieldset',
  450. '#title' => t('Overview'),
  451. '#group' => 'detail',
  452. );
  453. $team = array();
  454. foreach ($migration->getTeam() as $member) {
  455. $email_address = $member->getEmailAddress();
  456. $team[$member->getGroup()][] =
  457. $member->getName() . ' <' . l($email_address, 'mailto:' . $email_address) . '>';
  458. }
  459. foreach ($team as $group => $list) {
  460. $form['overview'][$group] = array(
  461. '#type' => 'item',
  462. '#title' => $group,
  463. '#markup' => implode(', ', $list),
  464. );
  465. }
  466. $dependencies = $migration->getHardDependencies();
  467. if (count($dependencies) > 0) {
  468. $form['overview']['dependencies'] = array(
  469. '#title' => t('Dependencies') ,
  470. '#markup' => implode(', ', $dependencies),
  471. '#type' => 'item',
  472. );
  473. }
  474. $soft_dependencies = $migration->getSoftDependencies();
  475. if (count($soft_dependencies) > 0) {
  476. $form['overview']['soft_dependencies'] = array(
  477. '#title' => t('Soft Dependencies'),
  478. '#markup' => implode(', ', $soft_dependencies),
  479. '#type' => 'item',
  480. );
  481. }
  482. if ($has_mappings) {
  483. switch ($migration->getSystemOfRecord()) {
  484. case Migration::SOURCE:
  485. $system_of_record = t('Source data');
  486. break;
  487. case Migration::DESTINATION:
  488. $system_of_record = t('Destination data');
  489. break;
  490. default:
  491. $system_of_record = t('Unknown');
  492. break;
  493. }
  494. $form['overview']['system_of_record'] = array(
  495. '#type' => 'item',
  496. '#title' => t('System of record:'),
  497. '#markup' => $system_of_record,
  498. );
  499. }
  500. $form['overview']['description'] = array(
  501. '#title' => t('Description:'),
  502. '#markup' => $migration->getDescription(),
  503. '#type' => 'item',
  504. );
  505. if ($has_mappings) {
  506. // Destination field information
  507. $form['destination'] = array(
  508. '#type' => 'fieldset',
  509. '#title' => t('Destination'),
  510. '#group' => 'detail',
  511. '#description' =>
  512. t('<p>These are the fields available in the destination of this
  513. migration. The machine names listed here are those available to be used
  514. as the first parameter to $this->addFieldMapping() in your Migration
  515. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  516. );
  517. $destination = $migration->getDestination();
  518. $form['destination']['type'] = array(
  519. '#type' => 'item',
  520. '#title' => t('Type'),
  521. '#markup' => (string)$destination,
  522. );
  523. $dest_key = $destination->getKeySchema();
  524. $header = array(t('Machine name'), t('Description'));
  525. $rows = array();
  526. foreach ($destination->fields($migration) as $machine_name => $description) {
  527. $classes = array();
  528. if (isset($dest_key[$machine_name])) {
  529. // Identify primary key
  530. $machine_name .= ' ' . t('(PK)');
  531. }
  532. else {
  533. // Add class for mapped/unmapped. Used in summary.
  534. $classes[] = !isset($destination_fields[$machine_name]) ? 'migrate-error' : '';
  535. }
  536. $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes));
  537. }
  538. $classes = array();
  539. $form['destination']['fields'] = array(
  540. '#theme' => 'table',
  541. '#header' => $header,
  542. '#rows' => $rows,
  543. '#empty' => t('No fields'),
  544. );
  545. // TODO: Get source_fields from arguments
  546. $form['source'] = array(
  547. '#type' => 'fieldset',
  548. '#title' => t('Source'),
  549. '#group' => 'detail',
  550. '#description' =>
  551. t('<p>These are the fields available from the source of this
  552. migration. The machine names listed here are those available to be used
  553. as the second parameter to $this->addFieldMapping() in your Migration
  554. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  555. );
  556. $source = $migration->getSource();
  557. $form['source']['query'] = array(
  558. '#type' => 'item',
  559. '#title' => t('Query'),
  560. '#markup' => '<pre>' . $source . '</pre>',
  561. );
  562. $source_key = $migration->getMap()->getSourceKey();
  563. $header = array(t('Machine name'), t('Description'));
  564. $rows = array();
  565. foreach ($source->fields() as $machine_name => $description) {
  566. if (isset($source_key[$machine_name])) {
  567. // Identify primary key
  568. $machine_name .= ' ' . t('(PK)');
  569. }
  570. else {
  571. // Add class for mapped/unmapped. Used in summary.
  572. $classes = !isset($source_fields[$machine_name]) ? 'migrate-error' : '';
  573. }
  574. $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes));
  575. }
  576. $classes = array();
  577. $form['source']['fields'] = array(
  578. '#theme' => 'table',
  579. '#header' => $header,
  580. '#rows' => $rows,
  581. '#empty' => t('No fields'),
  582. );
  583. $header = array(t('Destination'), t('Source'), t('Default'), t('Description'), t('Priority'));
  584. // First group the mappings
  585. $descriptions = array();
  586. $source_fields = $source->fields();
  587. $destination_fields = $destination->fields($migration);
  588. foreach ($field_mappings as $mapping) {
  589. // Validate source and destination fields actually exist
  590. $source_field = $mapping->getSourceField();
  591. $destination_field = $mapping->getDestinationField();
  592. if (!is_null($source_field) && !isset($source_fields[$source_field])) {
  593. drupal_set_message(t('"!source" was used as source field in the
  594. "!destination" mapping but is not in list of source fields', array(
  595. '!source' => $source_field,
  596. '!destination' => $destination_field
  597. )),
  598. 'warning');
  599. }
  600. if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) {
  601. drupal_set_message(t('"!destination" was used as destination field in
  602. "!source" mapping but is not in list of destination fields', array(
  603. '!source' => $source_field,
  604. '!destination' => $destination_field)),
  605. 'warning');
  606. }
  607. $descriptions[$mapping->getIssueGroup()][] = $mapping;
  608. }
  609. // Put out each group header
  610. foreach ($descriptions as $group => $mappings) {
  611. $form[$group] = array(
  612. '#type' => 'fieldset',
  613. '#title' => t('Mapping: !group', array('!group' => $group)),
  614. '#group' => 'detail',
  615. '#attributes' => array('class' => array('migrate-mapping')),
  616. );
  617. $rows = array();
  618. foreach ($mappings as $mapping) {
  619. $default = $mapping->getDefaultValue();
  620. if (is_array($default)) {
  621. $default = implode(',', $default);
  622. }
  623. $issue_priority = $mapping->getIssuePriority();
  624. if (!is_null($issue_priority)) {
  625. $classes[] = 'migrate-priority-' . $issue_priority;
  626. $priority = MigrateFieldMapping::$priorities[$issue_priority];
  627. $issue_pattern = $migration->getIssuePattern();
  628. $issue_number = $mapping->getIssueNumber();
  629. if (!is_null($issue_pattern) && !is_null($issue_number)) {
  630. $priority .= ' (' . l(t('#') . $issue_number, str_replace(':id:', $issue_number,
  631. $issue_pattern)) . ')';
  632. }
  633. if ($issue_priority != MigrateFieldMapping::ISSUE_PRIORITY_OK) {
  634. $classes[] = 'migrate-error';
  635. }
  636. }
  637. else {
  638. $priority = t('OK');
  639. $classes[] = 'migrate-priority-' . 1;
  640. }
  641. $row = array(
  642. array('data' => $mapping->getDestinationField(), 'class' => $classes),
  643. array('data' => $mapping->getSourceField(), 'class' => $classes),
  644. array('data' => $default, 'class' => $classes),
  645. array('data' => $mapping->getDescription(), 'class' => $classes),
  646. array('data' => $priority, 'class' => $classes),
  647. );
  648. $rows[] = $row;
  649. $classes = array();
  650. }
  651. $form[$group]['table'] = array(
  652. '#theme' => 'table',
  653. '#header' => $header,
  654. '#rows' => $rows,
  655. );
  656. }
  657. }
  658. return $form;
  659. }
  660. /**
  661. * Menu callback
  662. */
  663. function migrate_ui_configure() {
  664. drupal_set_title(t('Migrate configuration'));
  665. return drupal_get_form('migrate_ui_configure_form');
  666. }
  667. /**
  668. * Form for reviewing migrations.
  669. */
  670. function migrate_ui_configure_form($form, &$form_state) {
  671. $build = array();
  672. $build['description'] = array(
  673. '#prefix' => '<div>',
  674. '#markup' => t('In some cases, such as when a handler for a contributed module is
  675. implemented in both migrate_extras and the module itself, you may need to disable
  676. a particular handler. In this case, you may uncheck the undesired handler below.'),
  677. '#suffix' => '</div>',
  678. );
  679. $build['destination'] = array(
  680. '#type' => 'fieldset',
  681. '#title' => t('Destination handlers'),
  682. '#collapsible' => TRUE,
  683. );
  684. $header = array(
  685. 'module' => array('data' => t('Module')),
  686. 'class' => array('data' => t('Class')),
  687. 'types' => array('data' => t('Destination types handled')),
  688. );
  689. $disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
  690. $class_list = _migrate_class_list('MigrateDestinationHandler');
  691. $rows = array();
  692. $default_values = array();
  693. foreach ($class_list as $class_name => $handler) {
  694. $row = array();
  695. $module = db_select('registry', 'r')
  696. ->fields('r', array('module'))
  697. ->condition('name', $class_name)
  698. ->condition('type', 'class')
  699. ->execute()
  700. ->fetchField();
  701. $row['module'] = $module;
  702. $row['class'] = $class_name;
  703. $row['types'] = implode(', ', $handler->getTypesHandled());
  704. $default_values[$class_name] = !in_array($class_name, $disabled);
  705. $rows[$class_name] = $row;
  706. }
  707. $build['destination']['destination_handlers'] = array(
  708. '#type' => 'tableselect',
  709. '#header' => $header,
  710. '#options' => $rows,
  711. '#default_value' => $default_values,
  712. '#empty' => t('No destination handlers found'),
  713. );
  714. $build['field'] = array(
  715. '#type' => 'fieldset',
  716. '#title' => t('Field handlers'),
  717. '#collapsible' => TRUE,
  718. );
  719. $header = array(
  720. 'module' => array('data' => t('Module')),
  721. 'class' => array('data' => t('Class')),
  722. 'types' => array('data' => t('Field types handled')),
  723. );
  724. $class_list = _migrate_class_list('MigrateFieldHandler');
  725. $rows = array();
  726. $default_values = array();
  727. foreach ($class_list as $class_name => $handler) {
  728. $row = array();
  729. $module = db_select('registry', 'r')
  730. ->fields('r', array('module'))
  731. ->condition('name', $class_name)
  732. ->condition('type', 'class')
  733. ->execute()
  734. ->fetchField();
  735. $row['module'] = $module;
  736. $row['class'] = $class_name;
  737. $row['types'] = implode(', ', $handler->getTypesHandled());
  738. $default_values[$class_name] = !in_array($class_name, $disabled);
  739. $rows[$class_name] = $row;
  740. }
  741. $build['field']['field_handlers'] = array(
  742. '#type' => 'tableselect',
  743. '#header' => $header,
  744. '#options' => $rows,
  745. '#default_value' => $default_values,
  746. '#empty' => t('No field handlers found'),
  747. );
  748. $build['submit'] = array(
  749. '#type' => 'submit',
  750. '#value' => t('Save handler statuses'),
  751. '#submit' => array('migrate_ui_configure_submit'),
  752. );
  753. return $build;
  754. }
  755. /**
  756. * Submit callback for the dashboard form.
  757. */
  758. function migrate_ui_configure_submit($form, &$form_state) {
  759. $disabled = array();
  760. foreach ($form_state['values']['destination_handlers'] as $class => $value) {
  761. if (!$value) {
  762. $disabled[] = $class;
  763. }
  764. }
  765. foreach ($form_state['values']['field_handlers'] as $class => $value) {
  766. if (!$value) {
  767. $disabled[] = $class;
  768. }
  769. }
  770. variable_set('migrate_disabled_handlers', serialize($disabled));
  771. if (!empty($disabled)) {
  772. drupal_set_message(t('The following handler classes are disabled: @classes',
  773. array('@classes' => implode(', ', $disabled))));
  774. }
  775. else {
  776. drupal_set_message(t('No handler classes are currently disabled.'));
  777. }
  778. }