migrate_ui.pages.inc 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923
  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, $force));
  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. switch ($operation) {
  244. case 'import':
  245. // Update (if necessary) once, before starting
  246. if ($update && method_exists($migration, 'prepareUpdate')) {
  247. $migration->prepareUpdate();
  248. }
  249. $operations[] = array('migrate_ui_batch', array($operation, $machine_name, $limit, $force));
  250. break;
  251. case 'stop':
  252. $migration->stopProcess();
  253. break;
  254. case 'reset':
  255. $migration->resetStatus();
  256. break;
  257. }
  258. }
  259. }
  260. // Only rollback and import operations will need to go through Batch API.
  261. if (count($operations) > 0) {
  262. $batch = array(
  263. 'operations' => $operations,
  264. 'title' => t('Migration processing'),
  265. 'file' => drupal_get_path('module', 'migrate_ui') . '/migrate_ui.pages.inc',
  266. 'init_message' => t('Starting migration process'),
  267. 'progress_message' => t(''),
  268. 'error_message' => t('An error occurred. Some or all of the migrate processing has failed.'),
  269. 'finished' => 'migrate_ui_batch_finish',
  270. );
  271. batch_set($batch);
  272. }
  273. }
  274. /**
  275. * Process all enabled migration processes in a browser, using the Batch API
  276. * to break it into manageable chunks.
  277. *
  278. * @param $operation
  279. * Operation to perform - 'import', 'rollback', 'stop', or 'reset'.
  280. * @param $machine_name
  281. * Machine name of migration to process.
  282. * @param $limit
  283. * An array indicating the number of items to import or rollback, or the
  284. * number of seconds to process. Should include 'unit' (either 'items' or
  285. * 'seconds') and 'value'.
  286. * @param $context
  287. * Batch API context structure
  288. */
  289. function migrate_ui_batch($operation, $machine_name, $limit, $force = FALSE, &$context) {
  290. // If we got a stop message, skip everything else
  291. if (isset($context['results']['stopped'])) {
  292. $context['finished'] = 1;
  293. return;
  294. }
  295. $migration = Migration::getInstance($machine_name);
  296. // Messages generated by migration processes will be captured in this global
  297. global $_migrate_messages;
  298. $_migrate_messages = array();
  299. Migration::setDisplayFunction('migrate_ui_capture_message');
  300. // Perform the requested operation
  301. switch ($operation) {
  302. case 'import':
  303. $result = $migration->processImport(array('limit' => $limit, 'force' => $force));
  304. break;
  305. case 'rollback':
  306. $result = $migration->processRollback(array('limit' => $limit, 'force' => $force));
  307. break;
  308. }
  309. switch ($result) {
  310. case Migration::RESULT_INCOMPLETE:
  311. // Default to half-done, in case we can't get a more precise fix
  312. $context['finished'] = .5;
  313. if (method_exists($migration, 'sourceCount')) {
  314. $total = $migration->sourceCount();
  315. if ($total > 0 && method_exists($migration, 'importedCount')) {
  316. $processed = $migration->processedCount();
  317. switch ($operation) {
  318. case 'import':
  319. $to_update = $migration->updateCount();
  320. $context['finished'] = ($processed-$to_update)/$total;
  321. break;
  322. case 'rollback':
  323. $context['finished'] = ($total - $migration->importedCount())/$total;
  324. break;
  325. }
  326. }
  327. }
  328. break;
  329. case MigrationBase::RESULT_SKIPPED:
  330. $_migrate_messages[] = t("Skipped !name due to unfulfilled dependencies: !depends",
  331. array(
  332. '!name' => $machine_name,
  333. '!depends' => implode(", ", $migration->incompleteDependencies()),
  334. ));
  335. $context['finished'] = 1;
  336. break;
  337. case MigrationBase::RESULT_STOPPED:
  338. $context['finished'] = 1;
  339. // Skip any further operations
  340. $context['results']['stopped'] = TRUE;
  341. break;
  342. default:
  343. $context['finished'] = 1;
  344. break;
  345. }
  346. // Add any messages generated in this batch to the cumulative list
  347. foreach ($_migrate_messages as $message) {
  348. $context['results'][] = $message;
  349. }
  350. // While in progress, show the cumulative list of messages
  351. $full_message = '';
  352. foreach ($context['results'] as $message) {
  353. $full_message .= $message . '<br />';
  354. }
  355. $context['message'] = $full_message;
  356. }
  357. /**
  358. * Batch API finished callback - report results
  359. *
  360. * @param $success
  361. * Ignored
  362. * @param $results
  363. * List of results from batch processing
  364. * @param $operations
  365. * Ignored
  366. */
  367. function migrate_ui_batch_finish($success, $results, $operations) {
  368. unset($results['stopped']);
  369. foreach ($results as $result) {
  370. drupal_set_message($result);
  371. }
  372. }
  373. function migrate_ui_capture_message($message, $level) {
  374. if ($level != 'debug') {
  375. global $_migrate_messages;
  376. $_migrate_messages[] = $message;
  377. }
  378. }
  379. /**
  380. * Menu callback for messages page
  381. */
  382. function migrate_ui_messages($migration) {
  383. $build = $rows = array();
  384. $header = array(
  385. array('data' => t('Source ID'), 'field' => 'sourceid1', 'sort' => 'asc'),
  386. array('data' => t('Level'), 'field' => 'level'),
  387. array('data' => t('Message'), 'field' => 'message'),
  388. );
  389. if (is_string($migration)) {
  390. $migration = migration_load($migration);
  391. }
  392. // TODO: need a general MigrateMap API
  393. $messages = $migration->getMap()->getConnection()
  394. ->select($migration->getMap()->getMessageTable(), 'msg')
  395. ->extend('PagerDefault')
  396. ->extend('TableSort')
  397. ->orderByHeader($header)
  398. ->limit(500)
  399. ->fields('msg')
  400. ->execute();
  401. foreach ($messages as $message) {
  402. $classes[] = $message->level <= MigrationBase::MESSAGE_WARNING ? 'migrate-error' : '';
  403. $rows[] = array(
  404. array('data' => $message->sourceid1, 'class' => $classes), // TODO: deal with compound keys
  405. array('data' => $migration->getMessageLevelName($message->level), 'class' => $classes),
  406. array('data' => $message->message, 'class' => $classes),
  407. );
  408. unset($classes);
  409. }
  410. $build['messages'] = array(
  411. '#theme' => 'table',
  412. '#header' => $header,
  413. '#rows' => $rows,
  414. '#empty' => t('No messages'),
  415. '#attached' => array(
  416. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  417. ),
  418. );
  419. $build['migrate_ui_pager'] = array('#theme' => 'pager');
  420. return $build;
  421. }
  422. /**
  423. * Menu callback function for migration view page.
  424. */
  425. function migrate_migration_info($form, $form_state, $migration) {
  426. if (is_string($migration)) {
  427. $migration = migration_load($migration);
  428. }
  429. $has_mappings = method_exists($migration, 'getFieldMappings');
  430. $form = array();
  431. if ($has_mappings) {
  432. $field_mappings = $migration->getFieldMappings();
  433. // Identify what destination and source fields are mapped
  434. foreach ($field_mappings as $mapping) {
  435. $source_field = $mapping->getSourceField();
  436. $destination_field = $mapping->getDestinationField();
  437. $source_fields[$source_field] = $source_field;
  438. $destination_fields[$destination_field] = $destination_field;
  439. }
  440. $form['detail'] = array(
  441. '#type' => 'vertical_tabs',
  442. '#attached' => array(
  443. 'js' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.js'),
  444. 'css' => array(drupal_get_path('module', 'migrate_ui') . '/migrate_ui.css'),
  445. ),
  446. );
  447. }
  448. else {
  449. $form['detail'] = array(
  450. '#type' => 'fieldset',
  451. );
  452. }
  453. $form['overview'] = array(
  454. '#type' => 'fieldset',
  455. '#title' => t('Overview'),
  456. '#group' => 'detail',
  457. );
  458. $team = array();
  459. foreach ($migration->getTeam() as $member) {
  460. $email_address = $member->getEmailAddress();
  461. $team[$member->getGroup()][] =
  462. $member->getName() . ' <' . l($email_address, 'mailto:' . $email_address) . '>';
  463. }
  464. foreach ($team as $group => $list) {
  465. $form['overview'][$group] = array(
  466. '#type' => 'item',
  467. '#title' => $group,
  468. '#markup' => implode(', ', $list),
  469. );
  470. }
  471. $dependencies = $migration->getHardDependencies();
  472. if (count($dependencies) > 0) {
  473. $form['overview']['dependencies'] = array(
  474. '#title' => t('Dependencies') ,
  475. '#markup' => implode(', ', $dependencies),
  476. '#type' => 'item',
  477. );
  478. }
  479. $soft_dependencies = $migration->getSoftDependencies();
  480. if (count($soft_dependencies) > 0) {
  481. $form['overview']['soft_dependencies'] = array(
  482. '#title' => t('Soft Dependencies'),
  483. '#markup' => implode(', ', $soft_dependencies),
  484. '#type' => 'item',
  485. );
  486. }
  487. if ($has_mappings) {
  488. switch ($migration->getSystemOfRecord()) {
  489. case Migration::SOURCE:
  490. $system_of_record = t('Source data');
  491. break;
  492. case Migration::DESTINATION:
  493. $system_of_record = t('Destination data');
  494. break;
  495. default:
  496. $system_of_record = t('Unknown');
  497. break;
  498. }
  499. $form['overview']['system_of_record'] = array(
  500. '#type' => 'item',
  501. '#title' => t('System of record:'),
  502. '#markup' => $system_of_record,
  503. );
  504. }
  505. $form['overview']['description'] = array(
  506. '#title' => t('Description:'),
  507. '#markup' => $migration->getDescription(),
  508. '#type' => 'item',
  509. );
  510. if ($has_mappings) {
  511. // Destination field information
  512. $form['destination'] = array(
  513. '#type' => 'fieldset',
  514. '#title' => t('Destination'),
  515. '#group' => 'detail',
  516. '#description' =>
  517. t('<p>These are the fields available in the destination of this
  518. migration. The machine names listed here are those available to be used
  519. as the first parameter to $this->addFieldMapping() in your Migration
  520. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  521. );
  522. $destination = $migration->getDestination();
  523. $form['destination']['type'] = array(
  524. '#type' => 'item',
  525. '#title' => t('Type'),
  526. '#markup' => (string)$destination,
  527. );
  528. $dest_key = $destination->getKeySchema();
  529. $header = array(t('Machine name'), t('Description'));
  530. $rows = array();
  531. foreach ($destination->fields($migration) as $machine_name => $description) {
  532. $classes = array();
  533. if (isset($dest_key[$machine_name])) {
  534. // Identify primary key
  535. $machine_name .= ' ' . t('(PK)');
  536. }
  537. else {
  538. // Add class for mapped/unmapped. Used in summary.
  539. $classes[] = !isset($destination_fields[$machine_name]) ? 'migrate-error' : '';
  540. }
  541. $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes));
  542. }
  543. $classes = array();
  544. $form['destination']['fields'] = array(
  545. '#theme' => 'table',
  546. '#header' => $header,
  547. '#rows' => $rows,
  548. '#empty' => t('No fields'),
  549. );
  550. // TODO: Get source_fields from arguments
  551. $form['source'] = array(
  552. '#type' => 'fieldset',
  553. '#title' => t('Source'),
  554. '#group' => 'detail',
  555. '#description' =>
  556. t('<p>These are the fields available from the source of this
  557. migration. The machine names listed here are those available to be used
  558. as the second parameter to $this->addFieldMapping() in your Migration
  559. class constructor. <span class="error">Unmapped fields are red</span>.</p>'),
  560. );
  561. $source = $migration->getSource();
  562. $form['source']['query'] = array(
  563. '#type' => 'item',
  564. '#title' => t('Query'),
  565. '#markup' => '<pre>' . $source . '</pre>',
  566. );
  567. $source_key = $migration->getMap()->getSourceKey();
  568. $header = array(t('Machine name'), t('Description'));
  569. $rows = array();
  570. foreach ($source->fields() as $machine_name => $description) {
  571. if (isset($source_key[$machine_name])) {
  572. // Identify primary key
  573. $machine_name .= ' ' . t('(PK)');
  574. }
  575. else {
  576. // Add class for mapped/unmapped. Used in summary.
  577. $classes = !isset($source_fields[$machine_name]) ? 'migrate-error' : '';
  578. }
  579. $rows[] = array(array('data' => $machine_name, 'class' => $classes), array('data' => $description, 'class' => $classes));
  580. }
  581. $classes = array();
  582. $form['source']['fields'] = array(
  583. '#theme' => 'table',
  584. '#header' => $header,
  585. '#rows' => $rows,
  586. '#empty' => t('No fields'),
  587. );
  588. $header = array(t('Destination'), t('Source'), t('Default'), t('Description'), t('Priority'));
  589. // First group the mappings
  590. $descriptions = array();
  591. $source_fields = $source->fields();
  592. $destination_fields = $destination->fields($migration);
  593. foreach ($field_mappings as $mapping) {
  594. // Validate source and destination fields actually exist
  595. $source_field = $mapping->getSourceField();
  596. $destination_field = $mapping->getDestinationField();
  597. if (!is_null($source_field) && !isset($source_fields[$source_field])) {
  598. drupal_set_message(t('"!source" was used as source field in the
  599. "!destination" mapping but is not in list of source fields', array(
  600. '!source' => $source_field,
  601. '!destination' => $destination_field
  602. )),
  603. 'warning');
  604. }
  605. if (!is_null($destination_field) && !isset($destination_fields[$destination_field])) {
  606. drupal_set_message(t('"!destination" was used as destination field in
  607. "!source" mapping but is not in list of destination fields', array(
  608. '!source' => $source_field,
  609. '!destination' => $destination_field)),
  610. 'warning');
  611. }
  612. $descriptions[$mapping->getIssueGroup()][] = $mapping;
  613. }
  614. // Put out each group header
  615. foreach ($descriptions as $group => $mappings) {
  616. $form[$group] = array(
  617. '#type' => 'fieldset',
  618. '#title' => t('Mapping: !group', array('!group' => $group)),
  619. '#group' => 'detail',
  620. '#attributes' => array('class' => array('migrate-mapping')),
  621. );
  622. $rows = array();
  623. foreach ($mappings as $mapping) {
  624. $default = $mapping->getDefaultValue();
  625. if (is_array($default)) {
  626. $default = implode(',', $default);
  627. }
  628. $issue_priority = $mapping->getIssuePriority();
  629. if (!is_null($issue_priority)) {
  630. $classes[] = 'migrate-priority-' . $issue_priority;
  631. $priority = MigrateFieldMapping::$priorities[$issue_priority];
  632. $issue_pattern = $migration->getIssuePattern();
  633. $issue_number = $mapping->getIssueNumber();
  634. if (!is_null($issue_pattern) && !is_null($issue_number)) {
  635. $priority .= ' (' . l(t('#') . $issue_number, str_replace(':id:', $issue_number,
  636. $issue_pattern)) . ')';
  637. }
  638. if ($issue_priority != MigrateFieldMapping::ISSUE_PRIORITY_OK) {
  639. $classes[] = 'migrate-error';
  640. }
  641. }
  642. else {
  643. $priority = t('OK');
  644. $classes[] = 'migrate-priority-' . 1;
  645. }
  646. $row = array(
  647. array('data' => $mapping->getDestinationField(), 'class' => $classes),
  648. array('data' => $mapping->getSourceField(), 'class' => $classes),
  649. array('data' => $default, 'class' => $classes),
  650. array('data' => $mapping->getDescription(), 'class' => $classes),
  651. array('data' => $priority, 'class' => $classes),
  652. );
  653. $rows[] = $row;
  654. $classes = array();
  655. }
  656. $form[$group]['table'] = array(
  657. '#theme' => 'table',
  658. '#header' => $header,
  659. '#rows' => $rows,
  660. );
  661. }
  662. }
  663. return $form;
  664. }
  665. /**
  666. * Menu callback
  667. */
  668. function migrate_ui_registration() {
  669. drupal_set_title(t('Migration class registration'));
  670. return drupal_get_form('migrate_ui_registration_form');
  671. }
  672. /**
  673. * Form for reviewing migrations.
  674. */
  675. function migrate_ui_registration_form($form, &$form_state) {
  676. $build = array();
  677. if (variable_get('migrate_disable_autoregistration', FALSE)) {
  678. $description = t('You currently have automatic class registration turned off.
  679. This means that any Migration classes, destination handler classes, or
  680. field handlers must be explicitly registered, either through hook_migrate_api()
  681. or by calling MigrationBase::registerMigration(). You may enable automatic
  682. class registration by clicking this button - however, it\'s important to
  683. note that in some environments registration may fail with errors like
  684. "<em>Class \'views_handler_field\' not found</em>".');
  685. $button_label = t('Enable automatic registration');
  686. }
  687. else {
  688. $description = t('You currently have automatic class registration turned on.
  689. This means that any Migration classes, destination handler classes, or
  690. field handlers not explicitly registered, either through hook_migrate_api()
  691. or by calling MigrationBase::registerMigration(), can be registered by
  692. clicking the <strong>Register</strong> button below. It\'s important to
  693. note that in some environments registration may fail with errors like
  694. "<em>Class \'views_handler_field\' not found</em>" - in those cases, you
  695. can click the <strong>Disable automatic registration</strong> button below,
  696. but you must be sure that your classes are explicitly registered.');
  697. $button_label = t('Disable automatic registration');
  698. }
  699. $build['registration'] = array(
  700. '#type' => 'fieldset',
  701. '#title' => t('Migration registration'),
  702. '#description' => $description,
  703. );
  704. $build['registration']['auto_register'] = array(
  705. '#type' => 'submit',
  706. '#value' => $button_label,
  707. '#submit' => array('migrate_ui_configure_register_auto_register'),
  708. );
  709. if (!variable_get('migrate_disable_autoregistration', FALSE)) {
  710. $build['registration']['submit'] = array(
  711. '#type' => 'submit',
  712. '#value' => t('Register'),
  713. '#submit' => array('migrate_ui_configure_register_submit'),
  714. );
  715. }
  716. return $build;
  717. }
  718. /**
  719. * Submit callback for the configuration form registration fieldset.
  720. */
  721. function migrate_ui_configure_register_submit($form, &$form_state) {
  722. migrate_autoregister();
  723. $form_state['redirect'] = 'admin/content/migrate';
  724. }
  725. /**
  726. * Submit callback for the configuration form auto-registration changes.
  727. */
  728. function migrate_ui_configure_register_auto_register($form, &$form_state) {
  729. variable_set('migrate_disable_autoregistration',
  730. !variable_get('migrate_disable_autoregistration', FALSE));
  731. }
  732. /**
  733. * Menu callback
  734. */
  735. function migrate_ui_handlers() {
  736. drupal_set_title(t('Migrate handler configuration'));
  737. return drupal_get_form('migrate_ui_handlers_form');
  738. }
  739. /**
  740. * Form for reviewing migrations.
  741. */
  742. function migrate_ui_handlers_form($form, &$form_state) {
  743. $build = array();
  744. $build['handlers'] = array(
  745. '#type' => 'fieldset',
  746. '#title' => t('Handler configuration'),
  747. '#description' => t('In some cases, such as when a field handler for a contributed module is
  748. implemented in both migrate_extras and the module itself, you may need to disable
  749. a particular handler. In this case, you may uncheck the undesired handler below.'),
  750. );
  751. $build['handlers']['destination'] = array(
  752. '#type' => 'fieldset',
  753. '#title' => t('Destination handlers'),
  754. '#collapsible' => TRUE,
  755. );
  756. $header = array(
  757. 'module' => array('data' => t('Module')),
  758. 'class' => array('data' => t('Class')),
  759. 'types' => array('data' => t('Destination types handled')),
  760. );
  761. $disabled = unserialize(variable_get('migrate_disabled_handlers', serialize(array())));
  762. $class_list = _migrate_class_list('MigrateDestinationHandler');
  763. $rows = array();
  764. $default_values = array();
  765. foreach ($class_list as $class_name => $handler) {
  766. $row = array();
  767. $module = db_select('registry', 'r')
  768. ->fields('r', array('module'))
  769. ->condition('name', $class_name)
  770. ->condition('type', 'class')
  771. ->execute()
  772. ->fetchField();
  773. $row['module'] = $module;
  774. $row['class'] = $class_name;
  775. $row['types'] = implode(', ', $handler->getTypesHandled());
  776. $default_values[$class_name] = !in_array($class_name, $disabled);
  777. $rows[$class_name] = $row;
  778. }
  779. $build['handlers']['destination']['destination_handlers'] = array(
  780. '#type' => 'tableselect',
  781. '#header' => $header,
  782. '#options' => $rows,
  783. '#default_value' => $default_values,
  784. '#empty' => t('No destination handlers found'),
  785. );
  786. $build['handlers']['field'] = array(
  787. '#type' => 'fieldset',
  788. '#title' => t('Field handlers'),
  789. '#collapsible' => TRUE,
  790. );
  791. $header = array(
  792. 'module' => array('data' => t('Module')),
  793. 'class' => array('data' => t('Class')),
  794. 'types' => array('data' => t('Field types handled')),
  795. );
  796. $class_list = _migrate_class_list('MigrateFieldHandler');
  797. $rows = array();
  798. $default_values = array();
  799. foreach ($class_list as $class_name => $handler) {
  800. $row = array();
  801. $module = db_select('registry', 'r')
  802. ->fields('r', array('module'))
  803. ->condition('name', $class_name)
  804. ->condition('type', 'class')
  805. ->execute()
  806. ->fetchField();
  807. $row['module'] = $module;
  808. $row['class'] = $class_name;
  809. $row['types'] = implode(', ', $handler->getTypesHandled());
  810. $default_values[$class_name] = !in_array($class_name, $disabled);
  811. $rows[$class_name] = $row;
  812. }
  813. $build['handlers']['field']['field_handlers'] = array(
  814. '#type' => 'tableselect',
  815. '#header' => $header,
  816. '#options' => $rows,
  817. '#default_value' => $default_values,
  818. '#empty' => t('No field handlers found'),
  819. );
  820. $build['handlers']['submit'] = array(
  821. '#type' => 'submit',
  822. '#value' => t('Save handler statuses'),
  823. '#submit' => array('migrate_ui_handlers_submit'),
  824. );
  825. return $build;
  826. }
  827. /**
  828. * Submit callback for the configuration form handler fieldset.
  829. */
  830. function migrate_ui_handlers_submit($form, &$form_state) {
  831. $disabled = array();
  832. foreach ($form_state['values']['destination_handlers'] as $class => $value) {
  833. if (!$value) {
  834. $disabled[] = $class;
  835. }
  836. }
  837. foreach ($form_state['values']['field_handlers'] as $class => $value) {
  838. if (!$value) {
  839. $disabled[] = $class;
  840. }
  841. }
  842. variable_set('migrate_disabled_handlers', serialize($disabled));
  843. if (!empty($disabled)) {
  844. drupal_set_message(t('The following handler classes are disabled: @classes',
  845. array('@classes' => implode(', ', $disabled))));
  846. }
  847. else {
  848. drupal_set_message(t('No handler classes are currently disabled.'));
  849. }
  850. }