wine.inc 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236
  1. <?php
  2. /**
  3. * @file
  4. * Advanced migration examples. These serve two purposes:
  5. *
  6. * 1. To demonstrate some of the more advanced usages of the Migrate module.
  7. * Search for "TIP:" below for features not found in the basic example.
  8. * 2. To provide thorough test cases for the simpletest suite.
  9. *
  10. */
  11. /**
  12. * Abstract intermediate class holding common settings.
  13. */
  14. abstract class AdvancedExampleMigration extends Migration {
  15. public $basicFormat;
  16. public function __construct() {
  17. // TIP: Migrations can be organized into groups. In this case, all the migrations
  18. // derived from AdvancedExampleMigration will be part of the 'wine' group.
  19. // This enables us to easily run just the wine example migrations:
  20. // drush migrate-import --group=wine
  21. // The second argument to MigrateGroup::getInstance is an array of groups
  22. // which should come before this when viewing migration statuses, or running
  23. // migration operations using --all. Since the beer migrations in this module
  24. // did not specify a group, it is in the 'default' group, so this constructor
  25. // indicates that the wine migrations come after the beer migrations.
  26. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  27. $this->team = array(
  28. new MigrateTeamMember('Jack Kramer', 'jkramer@example.com', t('Taster')),
  29. new MigrateTeamMember('Linda Madison', 'lmadison@example.com', t('Winemaker')),
  30. );
  31. $this->issuePattern = 'http://drupal.org/node/:id:';
  32. // A format of our own, for testing migration of formats
  33. $this->basicFormat = filter_format_load('migrate_example');
  34. // We can do shared field mappings in the common class
  35. if (module_exists('path')) {
  36. $this->addFieldMapping('path')
  37. ->issueGroup(t('DNM'));
  38. if (module_exists('pathauto')) {
  39. $this->addFieldMapping('pathauto')
  40. ->issueGroup(t('DNM'));
  41. }
  42. }
  43. }
  44. }
  45. /**
  46. * TIP: While usually you'll create true migrations - processes that copy data
  47. * from some source into Drupal - you can also define processing steps for either
  48. * the import or rollback stages that take other actions. In this case, we want
  49. * to disable auto_nodetitle while the migration steps run. We'll re-enable it
  50. * over in WineFinishMigration.
  51. */
  52. class WinePrepMigration extends MigrationBase {
  53. // Track whether the auto_nodetitle was originally enabled so we know whether
  54. // to re-enable it. This is public so WineFinishMigration can reference it.
  55. public static $wasEnabled = FALSE;
  56. public function __construct() {
  57. // Because we're derived directly from migrationBase rather than AdvancedExampleMigration,
  58. // we must specify the group again here.
  59. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  60. $this->description = t('If auto_nodetitle is present, disable it for the duration');
  61. }
  62. // Define isComplete(), returning a boolean, to indicate whether dependent
  63. // migrations may proceed
  64. public function isComplete() {
  65. // If Auto Node Title is disabled, other migrations are free to go
  66. if (module_exists('auto_nodetitle')) {
  67. return FALSE;
  68. }
  69. else {
  70. return TRUE;
  71. }
  72. }
  73. // Implement any action you want to occur during an import process in an
  74. // import() method (alternatively, if you have an action which you want to
  75. // run during rollbacks, define a rollback() method).
  76. public function import() {
  77. if (module_exists('auto_nodetitle')) {
  78. self::$wasEnabled = TRUE;
  79. module_disable(array('auto_nodetitle'));
  80. self::displayMessage(t('Disabled auto_nodetitle module'), 'success');
  81. }
  82. else {
  83. self::$wasEnabled = FALSE;
  84. self::displayMessage(t('Auto_nodetitle is already disabled'), 'success');
  85. }
  86. // Must return one of the MigrationBase RESULT constants
  87. return MigrationBase::RESULT_COMPLETED;
  88. }
  89. }
  90. // The term migrations are very similar - implement the commonalities here
  91. abstract class WineTermMigration extends AdvancedExampleMigration {
  92. public function __construct($type, $vocabulary_name, $description) {
  93. parent::__construct();
  94. $this->description = $description;
  95. $this->dependencies = array('WinePrep');
  96. $this->map = new MigrateSQLMap($this->machineName,
  97. array(
  98. 'categoryid' => array('type' => 'int',
  99. 'unsigned' => TRUE,
  100. 'not null' => TRUE,
  101. )
  102. ),
  103. MigrateDestinationTerm::getKeySchema()
  104. );
  105. $query = db_select('migrate_example_wine_categories', 'wc')
  106. ->fields('wc', array('categoryid', 'name', 'details', 'category_parent', 'ordering'))
  107. ->condition('type', $type)
  108. // This sort assures that parents are saved before children.
  109. ->orderBy('category_parent', 'ASC');
  110. $this->source = new MigrateSourceSQL($query);
  111. $this->destination = new MigrateDestinationTerm($vocabulary_name);
  112. // Mapped fields
  113. $this->addFieldMapping('name', 'name');
  114. $this->addFieldMapping('description', 'details');
  115. $this->addFieldMapping('parent', 'category_parent')
  116. ->sourceMigration($this->getMachineName());
  117. $this->addFieldMapping('weight', 'ordering');
  118. $this->addFieldMapping('format')
  119. ->defaultValue($this->basicFormat->format);
  120. // Unmapped source fields
  121. // Unmapped destination fields
  122. $this->addFieldMapping('parent_name')
  123. ->issueGroup(t('DNM'));
  124. }
  125. }
  126. class WineVarietyMigration extends WineTermMigration {
  127. public function __construct() {
  128. parent::__construct('variety', 'migrate_example_wine_varieties',
  129. t('Migrate varieties from the source database to taxonomy terms'));
  130. }
  131. }
  132. class WineRegionMigration extends WineTermMigration {
  133. public function __construct() {
  134. parent::__construct('region', 'migrate_example_wine_regions',
  135. t('Migrate regions from the source database to taxonomy terms'));
  136. }
  137. }
  138. class WineBestWithMigration extends WineTermMigration {
  139. public function __construct() {
  140. parent::__construct('best_with', 'migrate_example_wine_best_with',
  141. t('Migrate "Best With" from the source database to taxonomy terms'));
  142. }
  143. }
  144. /**
  145. * TIP: Files can be migrated directly by themselves, by using the MigrateDestinationFile
  146. * class. This will copy the files themselves from the source, and set up the
  147. * Drupal file tables appropriately.
  148. */
  149. class WineFileCopyMigration extends AdvancedExampleMigration {
  150. public function __construct() {
  151. parent::__construct();
  152. $this->description = t('Profile images');
  153. $this->dependencies = array('WinePrep');
  154. $this->map = new MigrateSQLMap($this->machineName,
  155. array('imageid' => array(
  156. 'type' => 'int',
  157. 'unsigned' => TRUE,
  158. 'not null' => TRUE,
  159. 'description' => 'Image ID.'
  160. )
  161. ),
  162. MigrateDestinationFile::getKeySchema()
  163. );
  164. $query = db_select('migrate_example_wine_files', 'wf')
  165. ->fields('wf', array('imageid', 'url'))
  166. ->isNull('wineid');
  167. $this->source = new MigrateSourceSQL($query);
  168. $this->destination = new MigrateDestinationFile();
  169. // In the simplest case, just map the incoming URL to 'value'.
  170. $this->addFieldMapping('value', 'url');
  171. $this->addUnmigratedDestinations(array('fid', 'uid', 'timestamp',
  172. 'destination_dir', 'destination_file', 'source_dir', 'preserve_files',
  173. 'file_replace'));
  174. $this->removeFieldMapping('pathauto');
  175. }
  176. }
  177. /**
  178. * Migration class to test importing from a BLOB column into a file entity.
  179. * Typically, one would use this OR import into a File Field.
  180. * @see MigrateExampleOracleNode()
  181. */
  182. class WineFileBlobMigration extends AdvancedExampleMigration {
  183. public function __construct() {
  184. parent::__construct();
  185. $this->description = t('Example migration from BLOB column into files.');
  186. $this->dependencies = array('WinePrep');
  187. $this->map = new MigrateSQLMap($this->machineName,
  188. array('imageid' => array(
  189. 'type' => 'int',
  190. 'unsigned' => TRUE,
  191. 'not null' => TRUE,
  192. 'description' => 'Image ID.'
  193. )
  194. ),
  195. MigrateDestinationFile::getKeySchema()
  196. );
  197. $query = db_select('migrate_example_wine_blobs', 'wf')
  198. ->fields('wf', array('imageid', 'imageblob'));
  199. $this->source = new MigrateSourceSQL($query);
  200. // Note that the WineFileCopyMigration example let the second argument,
  201. // the file_class, to default to MigrateFileUri, indicating that the
  202. // 'value' we're passing was a URI. In this case, we're passing a blob, so
  203. // tell the destination to expect it.
  204. $this->destination = new MigrateDestinationFile('file', 'MigrateFileBlob');
  205. // Basic fields
  206. $this->addFieldMapping('uid')
  207. ->defaultValue(1);
  208. // The destination filename must be specified for blobs
  209. $this->addFieldMapping('destination_file')
  210. ->defaultValue('druplicon.png');
  211. $this->addFieldMapping('value', 'imageblob')
  212. ->description('An image blob in the DB');
  213. // Unmapped destination fields
  214. $this->addUnmigratedDestinations(array('fid', 'timestamp',
  215. 'destination_dir', 'file_replace', 'preserve_files'));
  216. // Our base class mapped this since most migrations use it, but not this one,
  217. // so remove it
  218. $this->removeFieldMapping('pathauto');
  219. }
  220. }
  221. class WineRoleMigration extends XMLMigration {
  222. public function __construct() {
  223. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  224. $this->description = t('XML feed (multi items) of roles (positions)');
  225. // TIP: Regular dependencies, besides enforcing (in the absence of --force)
  226. // the run order of migrations, affect the sorting of migrations on display.
  227. // You can use soft dependencies to affect just the display order when the
  228. // migrations aren't technically required to run in a certain order. In this
  229. // case, we want the role migration to appear after the file migration.
  230. $this->softDependencies = array('WineFileCopy');
  231. // There isn't a consistent way to automatically identify appropriate "fields"
  232. // from an XML feed, so we pass an explicit list of source fields
  233. $fields = array(
  234. 'name' => t('Position name'),
  235. );
  236. // The source ID here is the one retrieved from each data item in the XML file, and
  237. // used to identify specific items
  238. $this->map = new MigrateSQLMap($this->machineName,
  239. array(
  240. 'sourceid' => array(
  241. 'type' => 'int',
  242. 'unsigned' => TRUE,
  243. 'not null' => TRUE,
  244. )
  245. ),
  246. MigrateDestinationRole::getKeySchema()
  247. );
  248. // IMPORTANT: Do not try this at home! We have included importable files
  249. // with the migrate_example module so it can be very simply installed and
  250. // run, but you should never include any data you want to keep private
  251. // (especially user data like email addresses, phone numbers, etc.) in the
  252. // module directory. Your source data should be outside of the webroot, and
  253. // should not be anywhere where it may get committed into a revision control
  254. // system.
  255. // This can also be an URL instead of a file path.
  256. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  257. $items_url = $xml_folder . 'positions.xml';
  258. $item_xpath = '/positions/position'; // relative to document
  259. $item_ID_xpath = 'sourceid'; // relative to item_xpath and gets assembled
  260. // into full path /producers/producer/sourceid
  261. $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
  262. $this->source = new MigrateSourceMultiItems($items_class, $fields);
  263. $this->destination = new MigrateDestinationRole();
  264. $this->addFieldMapping('name', 'name')
  265. ->xpath('name');
  266. $this->addUnmigratedDestinations(array('weight'));
  267. }
  268. }
  269. class WineUserMigration extends AdvancedExampleMigration {
  270. public function __construct() {
  271. parent::__construct();
  272. $this->description = t('Wine Drinkers of the world');
  273. $this->dependencies = array('WinePrep', 'WineFileCopy', 'WineRole');
  274. $this->map = new MigrateSQLMap($this->machineName,
  275. array('accountid' => array(
  276. 'type' => 'int',
  277. 'unsigned' => TRUE,
  278. 'not null' => TRUE,
  279. 'description' => 'Account ID.'
  280. )
  281. ),
  282. MigrateDestinationUser::getKeySchema()
  283. );
  284. $query = db_select('migrate_example_wine_account', 'wa')
  285. ->fields('wa', array('accountid', 'status', 'posted', 'name',
  286. 'password', 'mail', 'last_access', 'last_login',
  287. 'original_mail', 'sig', 'sex', 'imageid', 'positions'));
  288. $this->source = new MigrateSourceSQL($query);
  289. $this->destination = new MigrateDestinationUser();
  290. // Mapped fields
  291. $this->addSimpleMappings(array('name', 'status', 'mail'));
  292. $this->addFieldMapping('created', 'posted')
  293. ->description('See prepare method');
  294. $this->addFieldMapping('access', 'last_access')
  295. ->description('See prepare method');
  296. $this->addFieldMapping('login', 'last_login')
  297. ->description('See prepare method');
  298. $this->addFieldMapping('pass', 'password');
  299. $this->addFieldMapping('roles', 'positions')
  300. ->separator(',')
  301. ->sourceMigration('WineRole');
  302. $this->addFieldMapping('signature', 'sig');
  303. $this->addFieldMapping('signature_format')
  304. ->defaultValue($this->basicFormat->format);
  305. $this->addFieldMapping('init', 'original_mail');
  306. $this->addFieldMapping('field_migrate_example_gender', 'sex')
  307. ->description(t('Map from M/F to 0/1 in prepare method'));
  308. $this->addFieldMapping('picture', 'imageid')
  309. ->sourceMigration('WineFileCopy');
  310. // Unmapped source fields
  311. // Unmapped destination fields
  312. $this->addUnmigratedDestinations(array('theme', 'timezone', 'language',
  313. 'is_new', 'field_migrate_example_favbeers', 'role_names'));
  314. }
  315. public function prepare(stdClass $account, stdClass $row) {
  316. // Source dates are in ISO format.
  317. // Because the mappings above have been applied, $account->created contains
  318. // the date/time string now - we could also pass $row->posted here.
  319. $account->created = strtotime($account->created);
  320. $account->access = strtotime($account->access);
  321. $account->login = strtotime($account->login);
  322. // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
  323. // TIP: Note that the Migration prepare method is called after all other
  324. // prepare handlers. Most notably, the field handlers have had their way
  325. // and created field arrays, so we have to save in the same format.
  326. switch ($row->sex) {
  327. case 'm':
  328. case 'M':
  329. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
  330. break;
  331. case 'f':
  332. case 'F':
  333. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
  334. break;
  335. default:
  336. unset($account->field_migrate_example_gender);
  337. break;
  338. }
  339. }
  340. }
  341. class WineProducerMigration extends AdvancedExampleMigration {
  342. public function __construct() {
  343. parent::__construct();
  344. $this->description = t('Wine producers of the world');
  345. $this->dependencies = array('WineRegion', 'WineUser');
  346. $this->map = new MigrateSQLMap($this->machineName,
  347. array(
  348. 'producerid' => array(
  349. 'type' => 'int',
  350. 'unsigned' => TRUE,
  351. 'not null' => TRUE,
  352. 'alias' => 'p',
  353. )
  354. ),
  355. MigrateDestinationNode::getKeySchema()
  356. );
  357. $query = db_select('migrate_example_wine_producer', 'p')
  358. ->fields('p', array('producerid', 'name', 'body', 'excerpt', 'accountid'));
  359. // Region term is singletons, handled straighforwardly
  360. $query->leftJoin('migrate_example_wine_category_producer', 'reg',
  361. "p.producerid = reg.producerid");
  362. $query->addField('reg', 'categoryid', 'region');
  363. $this->source = new MigrateSourceSQL($query);
  364. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  365. // Mapped fields
  366. $this->addFieldMapping('title', 'name')
  367. ->description(t('Mapping producer name in source to node title'));
  368. $this->addFieldMapping('uid', 'accountid')
  369. ->sourceMigration('WineUser')
  370. ->defaultValue(1);
  371. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  372. ->sourceMigration('WineRegion');
  373. $this->addFieldMapping('migrate_example_wine_regions:source_type')
  374. ->defaultValue('tid');
  375. $this->addFieldMapping('body', 'body');
  376. $this->addFieldMapping('body:summary', 'excerpt');
  377. $this->addFieldMapping('sticky')
  378. ->defaultValue(0);
  379. // No unmapped source fields
  380. // Unmapped destination fields
  381. $this->addUnmigratedDestinations(array('is_new', 'created', 'changed',
  382. 'status', 'promote', 'revision', 'language', 'revision_uid', 'log', 'tnid',
  383. 'body:format', 'body:language', 'migrate_example_wine_regions:create_term',
  384. 'comment'));
  385. if (module_exists('statistics')) {
  386. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  387. }
  388. }
  389. }
  390. /**
  391. * TIP: An example of importing from an XML feed. See the files in the xml
  392. * directory - index.xml contains a list of IDs to import, and <id>.xml
  393. * is the data for a given producer.
  394. *
  395. * Note that, if basing a migration on an XML source, you need to derive it
  396. * from XMLMigration instead of Migration.
  397. */
  398. class WineProducerXMLMigration extends XMLMigration {
  399. public function __construct() {
  400. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  401. $this->description = t('XML feed of wine producers of the world');
  402. $this->dependencies = array('WineRegion', 'WineUser');
  403. // There isn't a consistent way to automatically identify appropriate "fields"
  404. // from an XML feed, so we pass an explicit list of source fields
  405. $fields = array(
  406. 'name' => t('Producer name'),
  407. 'description' => t('Description of producer'),
  408. 'authorid' => t('Numeric ID of the author'),
  409. 'region' => t('Name of region'),
  410. );
  411. // The source ID here is the one retrieved from the XML listing file, and
  412. // used to identify the specific item's file
  413. $this->map = new MigrateSQLMap($this->machineName,
  414. array(
  415. 'sourceid' => array(
  416. 'type' => 'varchar',
  417. 'length' => 4,
  418. 'not null' => TRUE,
  419. )
  420. ),
  421. MigrateDestinationNode::getKeySchema()
  422. );
  423. // IMPORTANT: Do not try this at home! We have included importable files
  424. // with the migrate_example module so it can be very simply installed and
  425. // run, but you should never include any data you want to keep private
  426. // (especially user data like email addresses, phone numbers, etc.) in the
  427. // module directory. Your source data should be outside of the webroot, and
  428. // should not be anywhere where it may get committed into a revision control
  429. // system.
  430. // This can also be an URL instead of a file path.
  431. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  432. $list_url = $xml_folder . 'index.xml';
  433. // Each ID retrieved from the list URL will be plugged into :id in the
  434. // item URL to fetch the specific objects.
  435. $item_url = $xml_folder . ':id.xml';
  436. // We use the MigrateSourceList class for any source where we obtain the list
  437. // of IDs to process separately from the data for each item. The listing
  438. // and item are represented by separate classes, so for example we could
  439. // replace the XML listing with a file directory listing, or the XML item
  440. // with a JSON item.
  441. $this->source = new MigrateSourceList(new MigrateListXML($list_url),
  442. new MigrateItemXML($item_url), $fields);
  443. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  444. // TIP: Note that for XML sources, in addition to the source field passed to
  445. // addFieldMapping (the name under which it will be saved in the data row
  446. // passed through the migration process) we specify the Xpath used to retrieve
  447. // the value from the XML.
  448. $this->addFieldMapping('title', 'name')
  449. ->xpath('/producer/name');
  450. $this->addFieldMapping('uid', 'authorid')
  451. ->xpath('/producer/authorid')
  452. ->sourceMigration('WineUser')
  453. ->defaultValue(1);
  454. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  455. ->xpath('/producer/region');
  456. $this->addFieldMapping('body', 'description')
  457. ->xpath('/producer/description');
  458. $this->addUnmigratedDestinations(array('revision_uid', 'created', 'changed',
  459. 'status', 'promote', 'sticky', 'revision', 'log', 'language', 'tnid',
  460. 'is_new', 'body:summary', 'body:format', 'body:language',
  461. 'migrate_example_wine_regions:source_type', 'migrate_example_wine_regions:create_term',
  462. 'comment'));
  463. if (module_exists('path')) {
  464. $this->addFieldMapping('path')
  465. ->issueGroup(t('DNM'));
  466. if (module_exists('pathauto')) {
  467. $this->addFieldMapping('pathauto')
  468. ->issueGroup(t('DNM'));
  469. }
  470. }
  471. if (module_exists('statistics')) {
  472. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  473. }
  474. }
  475. }
  476. /**
  477. * TIP: An example of importing from an XML feed where both the id and the
  478. * data to import are in the same file. The id is a part of the data. See
  479. * the file in the xml directory - producers.xml which contains all IDs and
  480. * producer data for this example.
  481. *
  482. * Note that, if basing a migration on an XML source, you need to derive it
  483. * from XMLMigration instead of Migration.
  484. */
  485. class WineProducerMultiXMLMigration extends XMLMigration {
  486. public function __construct() {
  487. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  488. $this->description = t('XML feed (multi items) of wine producers of the world');
  489. $this->dependencies = array('WineRegion', 'WineUser');
  490. // There isn't a consistent way to automatically identify appropriate "fields"
  491. // from an XML feed, so we pass an explicit list of source fields
  492. $fields = array(
  493. 'name' => t('Producer name'),
  494. 'description' => t('Description of producer'),
  495. 'authorid' => t('Numeric ID of the author'),
  496. 'region' => t('Name of region'),
  497. );
  498. // The source ID here is the one retrieved from each data item in the XML file, and
  499. // used to identify specific items
  500. $this->map = new MigrateSQLMap($this->machineName,
  501. array(
  502. 'sourceid' => array(
  503. 'type' => 'varchar',
  504. 'length' => 4,
  505. 'not null' => TRUE,
  506. )
  507. ),
  508. MigrateDestinationNode::getKeySchema()
  509. );
  510. // IMPORTANT: Do not try this at home! We have included importable files
  511. // with the migrate_example module so it can be very simply installed and
  512. // run, but you should never include any data you want to keep private
  513. // (especially user data like email addresses, phone numbers, etc.) in the
  514. // module directory. Your source data should be outside of the webroot, and
  515. // should not be anywhere where it may get committed into a revision control
  516. // system.
  517. // This can also be an URL instead of a file path.
  518. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  519. $items_url = $xml_folder . 'producers.xml';
  520. // We use the MigrateSourceMultiItems class for any source where we obtain the list
  521. // of IDs to process and the data for each item from the same file. Typically the data
  522. // for an item is not contained in a single line within the source file. Examples include
  523. // multiple items defined in a single xml file or a single json file where in both cases
  524. // the id is part of the item.
  525. $item_xpath = '/producers/producer'; // relative to document
  526. $item_ID_xpath = 'sourceid'; // relative to item_xpath and gets assembled
  527. // into full path /producers/producer/sourceid
  528. $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
  529. $this->source = new MigrateSourceMultiItems($items_class, $fields);
  530. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  531. // TIP: Note that for XML sources, in addition to the source field passed to
  532. // addFieldMapping (the name under which it will be saved in the data row
  533. // passed through the migration process) we specify the Xpath used to retrieve
  534. // the value from the XML.
  535. // TIP: Note that all xpaths for fields begin at the last element of the item
  536. // xpath since each item xml chunk is processed individually.
  537. // (ex. xpath=name is equivalent to a full xpath of /producers/producer/name)
  538. $this->addFieldMapping('title', 'name')
  539. ->xpath('name');
  540. $this->addFieldMapping('uid', 'authorid')
  541. ->xpath('authorid')
  542. ->sourceMigration('WineUser')
  543. ->defaultValue(1);
  544. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  545. ->xpath('region');
  546. $this->addFieldMapping('body', 'description')
  547. ->xpath('description');
  548. $this->addUnmigratedDestinations(array('revision_uid', 'created', 'changed',
  549. 'status', 'promote', 'sticky', 'revision', 'log', 'language', 'tnid',
  550. 'is_new', 'body:summary', 'body:format', 'body:language',
  551. 'migrate_example_wine_regions:source_type', 'migrate_example_wine_regions:create_term',
  552. 'comment'));
  553. if (module_exists('path')) {
  554. $this->addFieldMapping('path')
  555. ->issueGroup(t('DNM'));
  556. if (module_exists('pathauto')) {
  557. $this->addFieldMapping('pathauto')
  558. ->issueGroup(t('DNM'));
  559. }
  560. }
  561. if (module_exists('statistics')) {
  562. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  563. }
  564. }
  565. }
  566. /**
  567. * TIP: An alternative approach using MigrateSourceSQL. This uses a different
  568. * XML library, which advances element-by-element through the XML file rather
  569. * than reading in the whole file. This source will work better with large XML
  570. * files, but is slower for small files and has a more restrictive query lanaguage
  571. * for selecting the elements to process.
  572. */
  573. class WineProducerXMLPullMigration extends XMLMigration {
  574. public function __construct() {
  575. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  576. $this->description = t('XML feed (pull) of wine producers of the world');
  577. $this->dependencies = array('WineRegion', 'WineUser');
  578. $fields = array(
  579. 'name' => t('Producer name'),
  580. 'description' => t('Description of producer'),
  581. 'authorid' => t('Numeric ID of the author'),
  582. 'region' => t('Name of region'),
  583. );
  584. $this->map = new MigrateSQLMap($this->machineName,
  585. array(
  586. 'sourceid' => array(
  587. 'type' => 'varchar',
  588. 'length' => 4,
  589. 'not null' => TRUE,
  590. )
  591. ),
  592. MigrateDestinationNode::getKeySchema()
  593. );
  594. // IMPORTANT: Do not try this at home! We have included importable files
  595. // with the migrate_example module so it can be very simply installed and
  596. // run, but you should never include any data you want to keep private
  597. // (especially user data like email addresses, phone numbers, etc.) in the
  598. // module directory. Your source data should be outside of the webroot, and
  599. // should not be anywhere where it may get committed into a revision control
  600. // system.
  601. // This can also be an URL instead of a file path.
  602. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  603. $items_url = $xml_folder . 'producers2.xml';
  604. // As with MigrateSourceMultiItems, this applies where there is not a separate
  605. // list of IDs to process - the source XML file is entirely self-contained.
  606. // For the ID path, and xpath for each component, we can use the full xpath
  607. // syntax as usual. However, the syntax to select the elements that correspond
  608. // to objects to import is more limited. It must be a fully-qualified path
  609. // to the element (i.e., /producers/producer rather than just //producer).
  610. $item_xpath = '/producers/producer'; // relative to document
  611. $item_ID_xpath = 'sourceid'; // relative to item_xpath
  612. $this->source = new MigrateSourceXML($items_url, $item_xpath, $item_ID_xpath,
  613. $fields);
  614. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  615. $this->addFieldMapping('title', 'name')
  616. ->xpath('name');
  617. $this->addFieldMapping('uid', 'authorid')
  618. ->xpath('authorid')
  619. ->sourceMigration('WineUser')
  620. ->defaultValue(1);
  621. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  622. ->xpath('region');
  623. $this->addFieldMapping('body', 'description')
  624. ->xpath('description');
  625. $this->addUnmigratedDestinations(array('revision_uid', 'created', 'changed',
  626. 'status', 'promote', 'sticky', 'revision', 'log', 'language', 'tnid',
  627. 'is_new', 'body:summary', 'body:format', 'body:language',
  628. 'migrate_example_wine_regions:source_type', 'migrate_example_wine_regions:create_term',
  629. 'comment'));
  630. if (module_exists('path')) {
  631. $this->addFieldMapping('path')
  632. ->issueGroup(t('DNM'));
  633. if (module_exists('pathauto')) {
  634. $this->addFieldMapping('pathauto')
  635. ->issueGroup(t('DNM'));
  636. }
  637. }
  638. if (module_exists('statistics')) {
  639. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  640. }
  641. }
  642. }
  643. // TODO: Add node_reference field pointing to producer
  644. class WineWineMigration extends AdvancedExampleMigration {
  645. public function __construct() {
  646. parent::__construct();
  647. $this->description = t('Wines of the world');
  648. $this->dependencies = array('WineVariety', 'WineRegion',
  649. 'WineBestWith', 'WineUser', 'WineProducer');
  650. $this->map = new MigrateSQLMap($this->machineName,
  651. array(
  652. 'wineid' => array(
  653. 'type' => 'int',
  654. 'unsigned' => TRUE,
  655. 'not null' => TRUE,
  656. 'description' => 'Wine ID',
  657. 'alias' => 'w',
  658. )
  659. ),
  660. MigrateDestinationNode::getKeySchema()
  661. );
  662. $query = db_select('migrate_example_wine', 'w')
  663. ->fields('w', array('wineid', 'name', 'body', 'excerpt', 'accountid',
  664. 'posted', 'last_changed', 'variety', 'region', 'rating'));
  665. $query->leftJoin('migrate_example_wine_category_wine', 'cwbw',
  666. "w.wineid = cwbw.wineid");
  667. $query->leftJoin('migrate_example_wine_categories', 'bw',
  668. "cwbw.categoryid = bw.categoryid AND bw.type = 'best_with'");
  669. // Gives a single comma-separated list of related terms
  670. $query->groupBy('w.wineid');
  671. $query->addExpression('GROUP_CONCAT(bw.categoryid)', 'best_with');
  672. $count_query = db_select('migrate_example_wine', 'w');
  673. $count_query->addExpression('COUNT(wineid)', 'cnt');
  674. // TIP: By passing an array of source fields to the MigrateSourceSQL constructor,
  675. // we can modify the descriptions of source fields (which just default, for
  676. // SQL migrations, to table_alias.column_name), as well as add additional fields
  677. // (which may be populated in prepareRow()).
  678. $source_fields = array(
  679. 'wineid' => t('Wine ID in the old system'),
  680. 'name' => t('The name of the wine'),
  681. 'best_vintages' => t('What years were best for this wine?'),
  682. 'url' => t('Image URLs attached to this wine; populated in prepareRow()'),
  683. 'image_alt' => t('Image alt text attached to this wine; populated in prepareRow()'),
  684. 'image_title' => t('Image titles attached to this wine; populated in prepareRow()'),
  685. );
  686. // TIP: By default, each time a migration is run, any previously unimported source items
  687. // are imported (along with any previously-imported items marked for update). If the
  688. // source data contains a timestamp that is set to the creation time of each new item,
  689. // as well as set to the update time for any existing items that are updated, then
  690. // you can have those updated items automatically reimported by setting the field as
  691. // your highwater field.
  692. $this->highwaterField = array(
  693. 'name' => 'last_changed', // Column to be used as highwater mark
  694. 'alias' => 'w', // Table alias containing that column
  695. 'type' => 'int', // By default, highwater marks are assumed to be lexicographically
  696. // sortable (e.g., '2011-05-19 17:53:12'). To properly
  697. // deal with integer highwater marks (such as UNIX
  698. // timestamps), indicate so here.
  699. );
  700. // Note that it is important to process rows in the order of the highwater mark
  701. $query->orderBy('last_changed');
  702. $this->source = new MigrateSourceSQL($query, $source_fields, $count_query);
  703. $this->destination = new MigrateDestinationNode('migrate_example_wine');
  704. // Mapped fields
  705. $this->addFieldMapping('title', 'name')
  706. ->description(t('Mapping wine name in source to node title'));
  707. $this->addFieldMapping('uid', 'accountid')
  708. ->sourceMigration('WineUser')
  709. ->defaultValue(1);
  710. // TIP: By default, term relationship are assumed to be passed by name.
  711. // In this case, the source values are IDs, so we specify the relevant
  712. // migration (so the tid can be looked up in the map), and tell the term
  713. // field handler that it is receiving tids instead of names
  714. $this->addFieldMapping('migrate_example_wine_varieties', 'variety')
  715. ->sourceMigration('WineVariety');
  716. $this->addFieldMapping('migrate_example_wine_varieties:source_type')
  717. ->defaultValue('tid');
  718. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  719. ->sourceMigration('WineRegion');
  720. $this->addFieldMapping('migrate_example_wine_regions:source_type')
  721. ->defaultValue('tid');
  722. $this->addFieldMapping('migrate_example_wine_best_with', 'best_with')
  723. ->separator(',')
  724. ->sourceMigration('WineBestWith');
  725. $this->addFieldMapping('migrate_example_wine_best_with:source_type')
  726. ->defaultValue('tid');
  727. $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
  728. $this->addFieldMapping('field_migrate_example_top_vintag', 'best_vintages');
  729. // TIP: You can apply one or more functions to a source value using ->callbacks().
  730. // The function must take a single argument and return a value which is a
  731. // transformation of the argument. As this example shows, you can have multiple
  732. // callbacks, and they can either be straight functions or class methods. In
  733. // this case, our custom method prepends 'review: ' to the body, and then we
  734. // call a standard Drupal function to uppercase the whole body.
  735. $this->addFieldMapping('body', 'body')
  736. ->callbacks(array($this, 'addTitlePrefix'), 'drupal_strtoupper');
  737. $this->addFieldMapping('body:summary', 'excerpt');
  738. // We will get the image data from a related table in prepareRow()
  739. $this->addFieldMapping('field_migrate_example_image', 'url');
  740. // Indicate that we want each file to maintain its name, replacing any
  741. // previous file of the same name (as opposed to being renamed to avoid
  742. // collisions, which is the default).
  743. $this->addFieldMapping('field_migrate_example_image:file_replace')
  744. ->defaultValue(FILE_EXISTS_REPLACE);
  745. $this->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
  746. $this->addFieldMapping('field_migrate_example_image:title', 'image_title');
  747. $this->addFieldMapping('sticky')
  748. ->defaultValue(0);
  749. // These are already UNIX timestamps, so just pass through
  750. $this->addFieldMapping('created', 'posted');
  751. $this->addFieldMapping('changed', 'last_changed');
  752. // No unmapped source fields
  753. // Unmapped destination fields
  754. $this->addUnmigratedDestinations(array('revision_uid', 'status', 'promote',
  755. 'revision', 'log', 'language', 'tnid', 'is_new', 'body:format',
  756. 'body:language', 'migrate_example_wine_regions:create_term', 'comment',
  757. 'migrate_example_wine_varieties:create_term', 'migrate_example_wine_best_with:create_term',
  758. 'field_migrate_example_image:language', 'field_migrate_example_image:preserve_files',
  759. 'field_migrate_example_image:source_dir', 'field_migrate_example_image:destination_dir',
  760. 'field_migrate_example_image:destination_file', 'field_migrate_example_image:file_class',
  761. ));
  762. if (module_exists('statistics')) {
  763. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  764. }
  765. }
  766. protected function addTitlePrefix($source_title) {
  767. return t('review: !title', array('!title' => $source_title));
  768. }
  769. // TIP: Implement a prepareRow() method to manipulate the source row between
  770. // retrieval from the database and the automatic applicaton of mappings
  771. public function prepareRow($current_row) {
  772. // We used the MySQL GROUP_CONCAT function above to handle a multi-value source
  773. // field - more portably, we query the related table with multiple values here,
  774. // so the values can run through the mapping process
  775. $source_id = $current_row->wineid;
  776. $result = db_select('migrate_example_wine_vintages', 'v')
  777. ->fields('v', array('vintage'))
  778. ->condition('wineid', $source_id)
  779. ->execute();
  780. foreach ($result as $row) {
  781. $current_row->best_vintages[] = $row->vintage;
  782. }
  783. // We can have multiple files per node, so we pull them here along with
  784. // their related data (alt/title).
  785. $result = db_select('migrate_example_wine_files', 'f')
  786. ->fields('f', array('url', 'image_alt', 'image_title'))
  787. ->condition('wineid', $source_id)
  788. ->execute();
  789. foreach ($result as $row) {
  790. $current_row->url[] = $row->url;
  791. $current_row->image_alt[] = $row->image_alt;
  792. $current_row->image_title[] = $row->image_title;
  793. }
  794. // We could also have used this function to decide to skip a row, in cases
  795. // where that couldn't easily be done through the original query. Simply
  796. // return FALSE in such cases.
  797. return TRUE;
  798. }
  799. }
  800. class WineCommentMigration extends AdvancedExampleMigration {
  801. public function __construct() {
  802. parent::__construct();
  803. $this->description = 'Comments about wines';
  804. $this->dependencies = array('WineUser', 'WineWine');
  805. $this->map = new MigrateSQLMap($this->machineName,
  806. array('commentid' => array(
  807. 'type' => 'int',
  808. 'unsigned' => TRUE,
  809. 'not null' => TRUE,
  810. )
  811. ),
  812. MigrateDestinationComment::getKeySchema()
  813. );
  814. $query = db_select('migrate_example_wine_comment', 'wc')
  815. ->fields('wc', array('commentid', 'comment_parent', 'name', 'mail',
  816. 'accountid', 'body', 'wineid', 'subject', 'commenthost', 'userpage',
  817. 'posted', 'lastchanged'))
  818. ->orderBy('comment_parent');
  819. $this->source = new MigrateSourceSQL($query);
  820. $this->destination = new MigrateDestinationComment('comment_node_migrate_example_wine');
  821. // Mapped fields
  822. $this->addSimpleMappings(array('name', 'subject', 'mail'));
  823. $this->addFieldMapping('status')
  824. ->defaultValue(COMMENT_PUBLISHED);
  825. $this->addFieldMapping('nid', 'wineid')
  826. ->sourceMigration('WineWine');
  827. $this->addFieldMapping('uid', 'accountid')
  828. ->sourceMigration('WineUser')
  829. ->defaultValue(0);
  830. $this->addFieldMapping('pid', 'comment_parent')
  831. ->sourceMigration('WineComment')
  832. ->description('Parent comment');
  833. $this->addFieldMapping('comment_body', 'body');
  834. $this->addFieldMapping('hostname', 'commenthost');
  835. $this->addFieldMapping('created', 'posted');
  836. $this->addFieldMapping('changed', 'lastchanged');
  837. $this->addFieldMapping('homepage', 'userpage');
  838. // No unmapped source fields
  839. // Unmapped destination fields
  840. $this->addUnmigratedDestinations(array('thread', 'language',
  841. 'comment_body:format', 'comment_body:language'));
  842. $this->removeFieldMapping('pathauto');
  843. }
  844. }
  845. // TIP: An easy way to simply migrate into a Drupal table (i.e., one defined
  846. // through the Schema API) is to use the MigrateDestinationTable destination.
  847. // Just pass the table name to getKeySchema and the MigrateDestinationTable constructor.
  848. class WineTableMigration extends AdvancedExampleMigration {
  849. public function __construct() {
  850. parent::__construct();
  851. $this->description = 'Miscellaneous table data';
  852. $this->softDependencies = array('WineComment');
  853. $table_name = 'migrate_example_wine_table_dest';
  854. $this->map = new MigrateSQLMap($this->machineName,
  855. array('fooid' => array(
  856. 'type' => 'int',
  857. 'unsigned' => TRUE,
  858. 'not null' => TRUE,
  859. )
  860. ),
  861. MigrateDestinationTable::getKeySchema($table_name)
  862. );
  863. $query = db_select('migrate_example_wine_table_source', 't')
  864. ->fields('t', array('fooid', 'field1', 'field2'));
  865. $this->source = new MigrateSourceSQL($query);
  866. $this->destination = new MigrateDestinationTable($table_name);
  867. // Mapped fields
  868. $this->addFieldMapping('drupal_text', 'field1');
  869. $this->addFieldMapping('drupal_int', 'field2');
  870. $this->addUnmigratedDestinations(array('recordid'));
  871. $this->removeFieldMapping('path');
  872. $this->removeFieldMapping('pathauto');
  873. }
  874. }
  875. /**
  876. * This migration works with WinePrepMigration to make ensure auto_nodetitle
  877. * is re-enabled if we disabled it.
  878. */
  879. class WineFinishMigration extends MigrationBase {
  880. public function __construct() {
  881. parent::__construct(MigrateGroup::getInstance('wine', array('default')));
  882. $this->description = t('If auto_nodetitle is present and was previously enabled,
  883. re-enable it');
  884. $this->dependencies = array('WineComment');
  885. }
  886. public function isComplete() {
  887. if (module_exists('auto_nodetitle')) {
  888. return TRUE;
  889. }
  890. else {
  891. return FALSE;
  892. }
  893. }
  894. public function import() {
  895. if (!module_exists('auto_nodetitle')) {
  896. if (WinePrepMigration::$wasEnabled) {
  897. module_enable(array('auto_nodetitle'));
  898. self::displayMessage(t('Re-enabled auto_nodetitle module'), 'success');
  899. }
  900. else {
  901. self::displayMessage(t('auto_nodetitle was not originally enabled'), 'success');
  902. }
  903. }
  904. else {
  905. self::displayMessage(t('Auto_nodetitle module already enabled'), 'success');
  906. }
  907. return Migration::RESULT_COMPLETED;
  908. }
  909. }
  910. /**
  911. * TIP: This demonstrates a migration designed not to import new content, but
  912. * to update existing content (in this case, revised wine ratings)
  913. */
  914. class WineUpdatesMigration extends AdvancedExampleMigration {
  915. public function __construct() {
  916. parent::__construct();
  917. $this->description = t('Update wine ratings');
  918. $this->dependencies = array('WineWine');
  919. $this->softDependencies = array('WineFinish');
  920. $this->map = new MigrateSQLMap($this->machineName,
  921. array(
  922. 'wineid' => array(
  923. 'type' => 'int',
  924. 'unsigned' => TRUE,
  925. 'not null' => TRUE,
  926. 'description' => 'Wine ID',
  927. 'alias' => 'w',
  928. )
  929. ),
  930. MigrateDestinationNode::getKeySchema()
  931. );
  932. $query = db_select('migrate_example_wine_updates', 'w')
  933. ->fields('w', array('wineid', 'rating'));
  934. $this->source = new MigrateSourceSQL($query);
  935. $this->destination = new MigrateDestinationNode('migrate_example_wine');
  936. // Indicate we're updating existing data. The default, Migration::SOURCE, would
  937. // cause existing nodes to be completely replaced by the source data. In this
  938. // case, the existing node will be loaded and only the rating altered.
  939. $this->systemOfRecord = Migration::DESTINATION;
  940. // Mapped fields
  941. // The destination handler needs the nid to change - since the incoming data
  942. // has a source id, not a nid, we need to apply the original wine migration
  943. // mapping to populate the nid.
  944. $this->addFieldMapping('nid', 'wineid')
  945. ->sourceMigration('WineWine');
  946. $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
  947. // No unmapped source fields
  948. // Unmapped destination fields
  949. $this->addFieldMapping('uid');
  950. $this->addFieldMapping('migrate_example_wine_varieties');
  951. $this->addFieldMapping('migrate_example_wine_regions');
  952. $this->addFieldMapping('migrate_example_wine_best_with');
  953. $this->addFieldMapping('body');
  954. $this->addFieldMapping('field_migrate_example_image');
  955. $this->addFieldMapping('sticky');
  956. $this->addFieldMapping('created');
  957. $this->addFieldMapping('changed');
  958. $this->addUnmigratedDestinations(array('title', 'revision_uid', 'status', 'promote',
  959. 'revision', 'log', 'language', 'tnid', 'is_new', 'body:format', 'body:summary',
  960. 'body:language', 'migrate_example_wine_regions:source_type',
  961. 'migrate_example_wine_regions:create_term', 'comment',
  962. 'migrate_example_wine_varieties:source_type', 'migrate_example_wine_varieties:create_term',
  963. 'migrate_example_wine_best_with:source_type', 'migrate_example_wine_best_with:create_term',
  964. 'field_migrate_example_image:language', 'field_migrate_example_image:destination_dir',
  965. 'field_migrate_example_image:alt', 'field_migrate_example_image:title',
  966. 'field_migrate_example_image:file_class', 'field_migrate_example_image:file_replace',
  967. 'field_migrate_example_image:preserve_files', 'field_migrate_example_image:destination_file',
  968. 'field_migrate_example_image:source_dir', 'field_migrate_example_top_vintag'));
  969. if (module_exists('statistics')) {
  970. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  971. }
  972. }
  973. }
  974. class WineCommentUpdatesMigration extends AdvancedExampleMigration {
  975. public function __construct() {
  976. parent::__construct();
  977. $this->description = 'Update wine comments';
  978. $this->dependencies = array('WineComment');
  979. $this->softDependencies = array('WineUpdates');
  980. $this->map = new MigrateSQLMap($this->machineName,
  981. array('commentid' => array(
  982. 'type' => 'int',
  983. 'unsigned' => TRUE,
  984. 'not null' => TRUE,
  985. )
  986. ),
  987. MigrateDestinationComment::getKeySchema()
  988. );
  989. $query = db_select('migrate_example_wine_comment_updates', 'wc')
  990. ->fields('wc', array('commentid', 'subject'));
  991. $this->source = new MigrateSourceSQL($query);
  992. $this->destination = new MigrateDestinationComment('comment_node_migrate_example_wine');
  993. $this->systemOfRecord = Migration::DESTINATION;
  994. // Mapped fields
  995. $this->addFieldMapping('cid', 'commentid')
  996. ->sourceMigration('WineComment');
  997. $this->addFieldMapping('subject', 'subject');
  998. // No unmapped source fields
  999. // Unmapped destination fields
  1000. $this->addFieldMapping('name');
  1001. $this->addFieldMapping('mail');
  1002. $this->addFieldMapping('status');
  1003. $this->addFieldMapping('nid');
  1004. $this->addFieldMapping('uid');
  1005. $this->addFieldMapping('pid');
  1006. $this->addFieldMapping('comment_body');
  1007. $this->addFieldMapping('hostname');
  1008. $this->addFieldMapping('created');
  1009. $this->addFieldMapping('changed');
  1010. $this->addFieldMapping('homepage');
  1011. $this->addUnmigratedDestinations(array('thread', 'language',
  1012. 'comment_body:format', 'comment_body:language'));
  1013. $this->removeFieldMapping('pathauto');
  1014. }
  1015. }
  1016. class WineVarietyUpdatesMigration extends AdvancedExampleMigration {
  1017. public function __construct() {
  1018. parent::__construct();
  1019. $this->description = t('Migrate varieties from the source database to taxonomy terms');
  1020. $this->dependencies = array('WineVariety');
  1021. $this->softDependencies = array('WineUpdates');
  1022. $this->map = new MigrateSQLMap($this->machineName,
  1023. array(
  1024. 'categoryid' => array('type' => 'int',
  1025. 'unsigned' => TRUE,
  1026. 'not null' => TRUE,
  1027. )
  1028. ),
  1029. MigrateDestinationTerm::getKeySchema()
  1030. );
  1031. $query = db_select('migrate_example_wine_variety_updates', 'wc')
  1032. ->fields('wc', array('categoryid', 'details'));
  1033. $this->source = new MigrateSourceSQL($query);
  1034. $this->destination = new MigrateDestinationTerm('migrate_example_wine_varieties');
  1035. $this->systemOfRecord = Migration::DESTINATION;
  1036. // Mapped fields
  1037. $this->addFieldMapping('tid', 'categoryid')
  1038. ->sourceMigration('WineVariety');
  1039. $this->addFieldMapping('description', 'details');
  1040. // Unmapped source fields
  1041. // Unmapped destination fields
  1042. $this->addFieldMapping('name');
  1043. $this->addFieldMapping('parent');
  1044. $this->addFieldMapping('weight');
  1045. $this->addFieldMapping('format');
  1046. $this->addFieldMapping('parent_name')
  1047. ->issueGroup(t('DNM'));
  1048. }
  1049. }
  1050. class WineUserUpdatesMigration extends AdvancedExampleMigration {
  1051. public function __construct() {
  1052. parent::__construct();
  1053. $this->description = t('Account updates');
  1054. $this->dependencies = array('WineUser');
  1055. $this->softDependencies = array('WineUpdates');
  1056. $this->map = new MigrateSQLMap($this->machineName,
  1057. array('accountid' => array(
  1058. 'type' => 'int',
  1059. 'unsigned' => TRUE,
  1060. 'not null' => TRUE,
  1061. 'description' => 'Account ID.'
  1062. )
  1063. ),
  1064. MigrateDestinationUser::getKeySchema()
  1065. );
  1066. $query = db_select('migrate_example_wine_account_updates', 'wa')
  1067. ->fields('wa', array('accountid', 'sex'));
  1068. $this->source = new MigrateSourceSQL($query);
  1069. $this->destination = new MigrateDestinationUser();
  1070. $this->systemOfRecord = Migration::DESTINATION;
  1071. // Mapped fields
  1072. $this->addFieldMapping('uid', 'accountid')
  1073. ->sourceMigration('WineUser');
  1074. $this->addFieldMapping('field_migrate_example_gender', 'sex')
  1075. ->description(t('Map from M/F to 0/1 in prepare method'));
  1076. // Unmapped source fields
  1077. // Unmapped destination fields
  1078. $this->addFieldMapping('name');
  1079. $this->addFieldMapping('status');
  1080. $this->addFieldMapping('created');
  1081. $this->addFieldMapping('access');
  1082. $this->addFieldMapping('login');
  1083. $this->addFieldMapping('mail');
  1084. $this->addFieldMapping('pass');
  1085. $this->addFieldMapping('roles');
  1086. $this->addFieldMapping('signature');
  1087. $this->addFieldMapping('signature_format');
  1088. $this->addFieldMapping('init');
  1089. $this->addUnmigratedDestinations(array('theme', 'timezone', 'language',
  1090. 'picture', 'is_new', 'field_migrate_example_favbeers'));
  1091. }
  1092. public function prepare(stdClass $account, stdClass $row) {
  1093. // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
  1094. // TIP: Note that the Migration prepare method is called after all other
  1095. // prepare handlers. Most notably, the field handlers have had their way
  1096. // and created field arrays, so we have to save in the same format.
  1097. switch ($row->sex) {
  1098. case 'm':
  1099. case 'M':
  1100. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
  1101. break;
  1102. case 'f':
  1103. case 'F':
  1104. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
  1105. break;
  1106. default:
  1107. $account->field_migrate_example_gender = NULL;
  1108. break;
  1109. }
  1110. }
  1111. }