wine.inc 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755
  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. /**
  16. * Text format object for our migrate_example format.
  17. * @var
  18. */
  19. public $basicFormat;
  20. public function __construct($arguments) {
  21. parent::__construct($arguments);
  22. $this->team = array(
  23. new MigrateTeamMember('Jack Kramer', 'jkramer@example.com', t('Taster')),
  24. new MigrateTeamMember('Linda Madison', 'lmadison@example.com',
  25. t('Winemaker')),
  26. );
  27. $this->issuePattern = 'http://drupal.org/node/:id:';
  28. // A format of our own, for testing migration of formats
  29. $this->basicFormat = filter_format_load('migrate_example');
  30. // We can do shared field mappings in the common class
  31. if (module_exists('path')) {
  32. $this->addFieldMapping('path')
  33. ->issueGroup(t('DNM'));
  34. if (module_exists('pathauto')) {
  35. $this->addFieldMapping('pathauto')
  36. ->issueGroup(t('DNM'));
  37. }
  38. }
  39. }
  40. }
  41. /**
  42. * TIP: While usually you'll create true migrations - processes that copy data
  43. * from some source into Drupal - you can also define processing steps for
  44. * either the import or rollback stages that take other actions. In this case,
  45. * we want to disable auto_nodetitle while the migration steps run. We'll
  46. * re-enable it over in WineFinishMigration.
  47. */
  48. class WinePrepMigration extends MigrationBase {
  49. // Track whether the auto_nodetitle was originally enabled so we know whether
  50. // to re-enable it. This is public so WineFinishMigration can reference it.
  51. public static $wasEnabled = FALSE;
  52. public function __construct($arguments) {
  53. parent::__construct($arguments);
  54. $this->description = t('If auto_nodetitle is present, disable it for the duration');
  55. }
  56. // Define isComplete(), returning a boolean, to indicate whether dependent
  57. // migrations may proceed
  58. public function isComplete() {
  59. // If Auto Node Title is disabled, other migrations are free to go
  60. if (module_exists('auto_nodetitle')) {
  61. return FALSE;
  62. }
  63. else {
  64. return TRUE;
  65. }
  66. }
  67. // Implement any action you want to occur during an import process in an
  68. // import() method (alternatively, if you have an action which you want to
  69. // run during rollbacks, define a rollback() method).
  70. public function import() {
  71. if (module_exists('auto_nodetitle')) {
  72. self::$wasEnabled = TRUE;
  73. module_disable(array('auto_nodetitle'));
  74. self::displayMessage(t('Disabled auto_nodetitle module'), 'success');
  75. }
  76. else {
  77. self::$wasEnabled = FALSE;
  78. self::displayMessage(t('Auto_nodetitle is already disabled'), 'success');
  79. }
  80. // import() must return one of the MigrationBase RESULT constants.
  81. return MigrationBase::RESULT_COMPLETED;
  82. }
  83. }
  84. // The term migrations are very similar - implement the commonalities here (yes,
  85. // two layers of abstraction).
  86. abstract class WineTermMigration extends AdvancedExampleMigration {
  87. // The type, vocabulary machine name, and description are the only
  88. // differences among the incoming vocabularies, so pass them through the
  89. // constructor - you'll see below that the individual term migrations classes
  90. // thus become very simple.
  91. public function __construct($arguments, $type, $vocabulary_name,
  92. $description) {
  93. parent::__construct($arguments);
  94. $this->description = $description;
  95. // Best practice as of Migrate 2.6 is to specify dependencies in
  96. // hook_migrate_api. However, in this case, since we have a common class
  97. // for a few different migrations, we'll specify this dependency here
  98. // rather than repeatedly in hook_migrate_api().
  99. $this->dependencies = array('WinePrep');
  100. $query = db_select('migrate_example_wine_categories', 'wc')
  101. ->fields('wc', array('categoryid', 'name', 'details',
  102. 'category_parent', 'ordering'))
  103. ->condition('type', $type)
  104. // This sort assures that parents are saved before children.
  105. ->orderBy('category_parent', 'ASC');
  106. $this->source = new MigrateSourceSQL($query);
  107. $this->destination = new MigrateDestinationTerm($vocabulary_name);
  108. $this->map = new MigrateSQLMap($this->machineName,
  109. array(
  110. 'categoryid' => array('type' => 'int',
  111. 'unsigned' => TRUE,
  112. 'not null' => TRUE,
  113. )
  114. ),
  115. MigrateDestinationTerm::getKeySchema()
  116. );
  117. // Mapped fields
  118. $this->addFieldMapping('name', 'name');
  119. $this->addFieldMapping('description', 'details');
  120. $this->addFieldMapping('parent', 'category_parent')
  121. ->sourceMigration($this->getMachineName());
  122. $this->addFieldMapping('weight', 'ordering');
  123. $this->addFieldMapping('format')
  124. ->defaultValue($this->basicFormat->format);
  125. // Unmapped source fields
  126. // Unmapped destination fields
  127. $this->addFieldMapping('parent_name')
  128. ->issueGroup(t('DNM'));
  129. }
  130. }
  131. class WineVarietyMigration extends WineTermMigration {
  132. public function __construct($arguments) {
  133. parent::__construct($arguments, 'variety', 'migrate_example_wine_varieties',
  134. t('Migrate varieties from the source database to taxonomy terms'));
  135. }
  136. }
  137. class WineRegionMigration extends WineTermMigration {
  138. public function __construct($arguments) {
  139. parent::__construct($arguments, 'region', 'migrate_example_wine_regions',
  140. t('Migrate regions from the source database to taxonomy terms'));
  141. }
  142. }
  143. class WineBestWithMigration extends WineTermMigration {
  144. public function __construct($arguments) {
  145. parent::__construct($arguments, 'best_with', 'migrate_example_wine_best_with',
  146. t('Migrate "Best With" from the source database to taxonomy terms'));
  147. }
  148. }
  149. /**
  150. * TIP: Files can be migrated directly by themselves, by using the
  151. * MigrateDestinationFile class. This will copy the files themselves from the
  152. * source, and set up the Drupal file tables appropriately. Referencing them
  153. * in, say, a node field later is then simple.
  154. */
  155. class WineFileCopyMigration extends AdvancedExampleMigration {
  156. public function __construct($arguments) {
  157. parent::__construct($arguments);
  158. $this->description = t('Profile images');
  159. $query = db_select('migrate_example_wine_files', 'wf')
  160. ->fields('wf', array('imageid', 'url'))
  161. ->isNull('wineid');
  162. $this->source = new MigrateSourceSQL($query);
  163. $this->destination = new MigrateDestinationFile();
  164. $this->map = new MigrateSQLMap($this->machineName,
  165. array('imageid' => array(
  166. 'type' => 'int',
  167. 'unsigned' => TRUE,
  168. 'not null' => TRUE,
  169. 'description' => 'Image ID.'
  170. )
  171. ),
  172. MigrateDestinationFile::getKeySchema()
  173. );
  174. // In the simplest case, just map the incoming URL to 'value'.
  175. $this->addFieldMapping('value', 'url');
  176. $this->addUnmigratedDestinations(array(
  177. 'destination_dir',
  178. 'destination_file',
  179. 'fid',
  180. 'file_replace',
  181. 'preserve_files',
  182. 'source_dir',
  183. 'timestamp',
  184. 'uid',
  185. 'urlencode',
  186. ));
  187. $this->removeFieldMapping('pathauto');
  188. }
  189. }
  190. /**
  191. * Migration class to test importing from a BLOB column into a file entity.
  192. *
  193. * @see MigrateExampleOracleNode()
  194. */
  195. class WineFileBlobMigration extends AdvancedExampleMigration {
  196. public function __construct($arguments) {
  197. parent::__construct($arguments);
  198. $this->description = t('Example migration from BLOB column into files.');
  199. $query = db_select('migrate_example_wine_blobs', 'wf')
  200. ->fields('wf', array('imageid', 'imageblob'));
  201. $this->source = new MigrateSourceSQL($query);
  202. // Note that the WineFileCopyMigration example let the second argument,
  203. // the file_class, default to MigrateFileUri, indicating that the
  204. // 'value' we're passing was a URI. In this case, we're passing a blob, so
  205. // tell the destination to expect it.
  206. $this->destination = new MigrateDestinationFile('file', 'MigrateFileBlob');
  207. $this->map = new MigrateSQLMap($this->machineName,
  208. array('imageid' => array(
  209. 'type' => 'int',
  210. 'unsigned' => TRUE,
  211. 'not null' => TRUE,
  212. 'description' => 'Image ID.'
  213. )
  214. ),
  215. MigrateDestinationFile::getKeySchema()
  216. );
  217. // Basic fields
  218. $this->addFieldMapping('value', 'imageblob')
  219. ->description('An image blob in the DB');
  220. // The destination filename must be specified for blobs
  221. $this->addFieldMapping('destination_file')
  222. ->defaultValue('druplicon.png');
  223. $this->addFieldMapping('uid')
  224. ->defaultValue(1);
  225. // Unmapped destination fields
  226. $this->addUnmigratedDestinations(array(
  227. 'destination_dir',
  228. 'fid',
  229. 'file_replace',
  230. 'preserve_files',
  231. 'timestamp',
  232. ));
  233. $this->removeFieldMapping('pathauto');
  234. }
  235. }
  236. class WineRoleMigration extends XMLMigration {
  237. public function __construct($arguments) {
  238. parent::__construct($arguments);
  239. $this->description = t('XML feed (multi items) of roles (positions)');
  240. // There isn't a consistent way to automatically identify appropriate
  241. // "fields" from an XML feed, so we pass an explicit list of source fields
  242. $fields = array(
  243. 'name' => t('Position name'),
  244. );
  245. // IMPORTANT: Do not try this at home! We have included importable files
  246. // with the migrate_example module so it can be very simply installed and
  247. // run, but you should never include any data you want to keep private
  248. // (especially user data like email addresses, phone numbers, etc.) in the
  249. // module directory. Your source data should be outside of the webroot, and
  250. // should not be anywhere where it may get committed into a revision control
  251. // system.
  252. // This can also be an URL instead of a local file path.
  253. $xml_folder = DRUPAL_ROOT . '/' .
  254. drupal_get_path('module', 'migrate_example') . '/xml/';
  255. $items_url = $xml_folder . 'positions.xml';
  256. // This is the xpath identifying the items to be migrated, relative to the
  257. // document.
  258. $item_xpath = '/positions/position';
  259. // This is the xpath relative to the individual items - thus the full xpath
  260. // of an ID will be /positions/position/sourceid.
  261. $item_ID_xpath = 'sourceid';
  262. $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
  263. $this->source = new MigrateSourceMultiItems($items_class, $fields);
  264. $this->destination = new MigrateDestinationRole();
  265. // The source ID here is the one retrieved from each data item in the XML
  266. // file, and used to identify specific items
  267. $this->map = new MigrateSQLMap($this->machineName,
  268. array(
  269. 'sourceid' => array(
  270. 'type' => 'int',
  271. 'unsigned' => TRUE,
  272. 'not null' => TRUE,
  273. )
  274. ),
  275. MigrateDestinationRole::getKeySchema()
  276. );
  277. $this->addFieldMapping('name', 'name')
  278. ->xpath('name');
  279. $this->addUnmigratedDestinations(array('weight'));
  280. }
  281. }
  282. class WineUserMigration extends AdvancedExampleMigration {
  283. public function __construct($arguments) {
  284. parent::__construct($arguments);
  285. $this->description = t('Wine Drinkers of the world');
  286. $query = db_select('migrate_example_wine_account', 'wa')
  287. ->fields('wa', array('accountid', 'status', 'posted', 'name',
  288. 'password', 'mail', 'last_access', 'last_login',
  289. 'original_mail', 'sig', 'sex', 'imageid', 'positions'));
  290. $this->source = new MigrateSourceSQL($query);
  291. $this->destination = new MigrateDestinationUser();
  292. $this->map = new MigrateSQLMap($this->machineName,
  293. array('accountid' => array(
  294. 'type' => 'int',
  295. 'unsigned' => TRUE,
  296. 'not null' => TRUE,
  297. 'description' => 'Account ID.'
  298. )
  299. ),
  300. MigrateDestinationUser::getKeySchema()
  301. );
  302. // Mapped fields
  303. $this->addSimpleMappings(array('name', 'status', 'mail'));
  304. // Note that these date/time values are coming in as ISO strings, but
  305. // Drupal uses UNIX timestamps. The user destination automatically
  306. // translates them as necessary.
  307. $this->addFieldMapping('created', 'posted');
  308. $this->addFieldMapping('access', 'last_access');
  309. $this->addFieldMapping('login', 'last_login');
  310. $this->addFieldMapping('pass', 'password');
  311. $this->addFieldMapping('roles', 'positions')
  312. ->separator(',')
  313. ->sourceMigration('WineRole');
  314. $this->addFieldMapping('signature', 'sig');
  315. $this->addFieldMapping('signature_format')
  316. ->defaultValue($this->basicFormat->format);
  317. $this->addFieldMapping('init', 'original_mail');
  318. $this->addFieldMapping('field_migrate_example_gender', 'sex')
  319. ->description(t('Map from M/F to 0/1 in prepare method'));
  320. $this->addFieldMapping('picture', 'imageid')
  321. ->sourceMigration('WineFileCopy');
  322. // Unmapped source fields
  323. // Unmapped destination fields
  324. $this->addUnmigratedDestinations(array(
  325. 'data',
  326. 'is_new',
  327. 'language',
  328. 'role_names',
  329. 'theme',
  330. 'timezone',
  331. ));
  332. }
  333. public function prepare(stdClass $account, stdClass $row) {
  334. // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
  335. // TIP: Note that the Migration prepare method is called after all other
  336. // prepare handlers. Most notably, the field handlers have had their way
  337. // and created field arrays, so we have to save in the same format. In this
  338. // case, best practice would be to use a callback instead (see below), we're
  339. // just demonstrating here what the $account object looks like at this
  340. // stage.
  341. switch ($row->sex) {
  342. case 'm':
  343. case 'M':
  344. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
  345. break;
  346. case 'f':
  347. case 'F':
  348. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
  349. break;
  350. default:
  351. unset($account->field_migrate_example_gender);
  352. break;
  353. }
  354. }
  355. }
  356. class WineProducerMigration extends AdvancedExampleMigration {
  357. public function __construct($arguments) {
  358. parent::__construct($arguments);
  359. $this->description = t('Wine producers of the world');
  360. $this->map = new MigrateSQLMap($this->machineName,
  361. array(
  362. 'producerid' => array(
  363. 'type' => 'int',
  364. 'unsigned' => TRUE,
  365. 'not null' => TRUE,
  366. 'alias' => 'p',
  367. )
  368. ),
  369. MigrateDestinationNode::getKeySchema()
  370. );
  371. $query = db_select('migrate_example_wine_producer', 'p')
  372. ->fields('p', array('producerid', 'name', 'body', 'excerpt',
  373. 'accountid'));
  374. // Region term is singletons, handled straighforwardly
  375. $query->leftJoin('migrate_example_wine_category_producer', 'reg',
  376. 'p.producerid = reg.producerid');
  377. $query->addField('reg', 'categoryid', 'region');
  378. $this->source = new MigrateSourceSQL($query);
  379. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  380. // Mapped fields
  381. $this->addFieldMapping('title', 'name')
  382. ->description(t('Mapping producer name in source to node title'));
  383. $this->addFieldMapping('uid', 'accountid')
  384. ->sourceMigration('WineUser')
  385. ->defaultValue(1);
  386. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  387. ->sourceMigration('WineRegion');
  388. $this->addFieldMapping('migrate_example_wine_regions:source_type')
  389. ->defaultValue('tid');
  390. $this->addFieldMapping('body', 'body');
  391. $this->addFieldMapping('body:summary', 'excerpt');
  392. $this->addFieldMapping('sticky')
  393. ->defaultValue(0);
  394. // No unmapped source fields
  395. // Unmapped destination fields
  396. $this->addUnmigratedDestinations(array(
  397. 'body:format',
  398. 'changed',
  399. 'comment',
  400. 'created',
  401. 'is_new',
  402. 'language',
  403. 'log',
  404. 'promote',
  405. 'revision',
  406. 'revision_uid',
  407. 'status',
  408. 'tnid',
  409. 'translate',
  410. 'migrate_example_wine_regions:create_term',
  411. 'migrate_example_wine_regions:ignore_case',
  412. ));
  413. if (module_exists('statistics')) {
  414. $this->addUnmigratedDestinations(
  415. array('totalcount', 'daycount', 'timestamp'));
  416. }
  417. }
  418. }
  419. /**
  420. * TIP: An example of importing from an XML feed. See the files in the xml
  421. * directory - index.xml contains a list of IDs to import, and <id>.xml
  422. * is the data for a given producer.
  423. *
  424. * Note that, if basing a migration on an XML source, you need to derive it
  425. * from XMLMigration instead of Migration.
  426. */
  427. class WineProducerXMLMigration extends XMLMigration {
  428. public function __construct($arguments) {
  429. parent::__construct($arguments);
  430. $this->description = t('XML feed of wine producers of the world');
  431. // There isn't a consistent way to automatically identify appropriate
  432. // "fields" from an XML feed, so we pass an explicit list of source fields.
  433. $fields = array(
  434. 'name' => t('Producer name'),
  435. 'description' => t('Description of producer'),
  436. 'authorid' => t('Numeric ID of the author'),
  437. 'region' => t('Name of region'),
  438. );
  439. // IMPORTANT: Do not try this at home! We have included importable files
  440. // with the migrate_example module so it can be very simply installed and
  441. // run, but you should never include any data you want to keep private
  442. // (especially user data like email addresses, phone numbers, etc.) in the
  443. // module directory. Your source data should be outside of the webroot, and
  444. // should not be anywhere where it may get committed into a revision control
  445. // system.
  446. // This can also be an URL instead of a file path.
  447. $xml_folder = DRUPAL_ROOT . '/' .
  448. drupal_get_path('module', 'migrate_example') . '/xml/';
  449. $list_url = $xml_folder . 'index.xml';
  450. // Each ID retrieved from the list URL will be plugged into :id in the
  451. // item URL to fetch the specific objects.
  452. $item_url = $xml_folder . ':id.xml';
  453. // We use the MigrateSourceList class for any source where we obtain the
  454. // list of IDs to process separately from the data for each item. The
  455. // listing and item are represented by separate classes, so for example we
  456. // could replace the XML listing with a file directory listing, or the XML
  457. // item with a JSON item.
  458. $this->source = new MigrateSourceList(new MigrateListXML($list_url),
  459. new MigrateItemXML($item_url), $fields);
  460. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  461. // The source ID here is the one retrieved from the XML listing file, and
  462. // used to identify the specific item's file
  463. $this->map = new MigrateSQLMap($this->machineName,
  464. array(
  465. 'sourceid' => array(
  466. 'type' => 'varchar',
  467. 'length' => 4,
  468. 'not null' => TRUE,
  469. )
  470. ),
  471. MigrateDestinationNode::getKeySchema()
  472. );
  473. // TIP: Note that for XML sources, in addition to the source field passed to
  474. // addFieldMapping (the name under which it will be saved in the data row
  475. // passed through the migration process) we specify the Xpath used to
  476. // retrieve the value from the XML.
  477. $this->addFieldMapping('title', 'name')
  478. ->xpath('/producer/name');
  479. $this->addFieldMapping('uid', 'authorid')
  480. ->xpath('/producer/authorid')
  481. ->sourceMigration('WineUser')
  482. ->defaultValue(1);
  483. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  484. ->xpath('/producer/region');
  485. $this->addFieldMapping('body', 'description')
  486. ->xpath('/producer/description');
  487. $this->addUnmigratedDestinations(array(
  488. 'body:summary', 'body:format',
  489. 'changed',
  490. 'comment',
  491. 'created',
  492. 'is_new',
  493. 'language',
  494. 'log',
  495. 'migrate_example_wine_regions:create_term',
  496. 'migrate_example_wine_regions:ignore_case',
  497. 'migrate_example_wine_regions:source_type',
  498. 'promote',
  499. 'revision',
  500. 'revision_uid',
  501. 'status',
  502. 'sticky',
  503. 'tnid',
  504. 'translate',
  505. ));
  506. $destination_fields = $this->destination->fields();
  507. if (isset($destination_fields['path'])) {
  508. $this->addFieldMapping('path')
  509. ->issueGroup(t('DNM'));
  510. if (isset($destination_fields['pathauto'])) {
  511. $this->addFieldMapping('pathauto')
  512. ->issueGroup(t('DNM'));
  513. }
  514. }
  515. if (module_exists('statistics')) {
  516. $this->addUnmigratedDestinations(
  517. array('totalcount', 'daycount', 'timestamp'));
  518. }
  519. }
  520. }
  521. /**
  522. * TIP: An example of importing from an XML feed with namespaces.
  523. * See the files in the xml directory - index2.xml contains a list of IDs
  524. * to import, and <id>.xml is the data for a given producer.
  525. *
  526. * Note that, if basing a migration on an XML source, you need to derive it
  527. * from XMLMigration instead of Migration.
  528. */
  529. class WineProducerNamespaceXMLMigration extends XMLMigration {
  530. public function __construct($arguments) {
  531. parent::__construct($arguments);
  532. $this->description = t('Namespaced XML feed of wine producers of the world');
  533. // There isn't a consistent way to automatically identify appropriate
  534. // "fields" from an XML feed, so we pass an explicit list of source fields.
  535. $fields = array(
  536. 'pr:name' => t('Producer name'),
  537. 'pr:description' => t('Description of producer'),
  538. 'pr:authorid' => t('Numeric ID of the author'),
  539. 'pr:region' => t('Name of region'),
  540. );
  541. // IMPORTANT: Do not try this at home! We have included importable files
  542. // with the migrate_example module so it can be very simply installed and
  543. // run, but you should never include any data you want to keep private
  544. // (especially user data like email addresses, phone numbers, etc.) in the
  545. // module directory. Your source data should be outside of the webroot, and
  546. // should not be anywhere where it may get committed into a revision control
  547. // system.
  548. // This can also be an URL instead of a file path.
  549. $xml_folder = DRUPAL_ROOT . '/' .
  550. drupal_get_path('module', 'migrate_example') . '/xml/';
  551. $list_url = $xml_folder . 'index2.xml';
  552. // Each ID retrieved from the list URL will be plugged into :id in the
  553. // item URL to fetch the specific objects.
  554. $item_url = $xml_folder . ':id.xml';
  555. // We use the MigrateSourceList class for any source where we obtain the
  556. // list of IDs to process separately from the data for each item. The
  557. // listing and item are represented by separate classes, so for example we
  558. // could replace the XML listing with a file directory listing, or the XML
  559. // item with a JSON item.
  560. $list = new MigrateListXML($list_url, array('wn' => 'http://www.wine.org/wine'));
  561. $item = new MigrateItemXML($item_url, array('pr' => 'http://www.wine.org/wine-producers'));
  562. $this->source = new MigrateSourceList($list, $item, $fields);
  563. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  564. // The source ID here is the one retrieved from the XML listing file, and
  565. // used to identify the specific item's file
  566. $this->map = new MigrateSQLMap($this->machineName,
  567. array(
  568. 'sourceid' => array(
  569. 'type' => 'varchar',
  570. 'length' => 4,
  571. 'not null' => TRUE,
  572. )
  573. ),
  574. MigrateDestinationNode::getKeySchema()
  575. );
  576. // TIP: Note that for XML sources, in addition to the source field passed to
  577. // addFieldMapping (the name under which it will be saved in the data row
  578. // passed through the migration process) we specify the Xpath used to
  579. // retrieve the value from the XML.
  580. $this->addFieldMapping('title', 'pr:name')
  581. ->xpath('/pr:producer/pr:name');
  582. $this->addFieldMapping('uid', 'pr:authorid')
  583. ->xpath('/pr:producer/pr:authorid')
  584. ->sourceMigration('WineUser')
  585. ->defaultValue(1);
  586. $this->addFieldMapping('migrate_example_wine_regions', 'pr:region')
  587. ->xpath('/pr:producer/pr:region');
  588. $this->addFieldMapping('body', 'pr:description')
  589. ->xpath('/pr:producer/pr:description');
  590. $this->addUnmigratedDestinations(array(
  591. 'body:summary', 'body:format',
  592. 'changed',
  593. 'comment',
  594. 'created',
  595. 'is_new',
  596. 'language',
  597. 'log',
  598. 'migrate_example_wine_regions:create_term',
  599. 'migrate_example_wine_regions:ignore_case',
  600. 'migrate_example_wine_regions:source_type',
  601. 'promote',
  602. 'revision',
  603. 'revision_uid',
  604. 'status',
  605. 'sticky',
  606. 'tnid',
  607. 'translate',
  608. ));
  609. $destination_fields = $this->destination->fields();
  610. if (isset($destination_fields['path'])) {
  611. $this->addFieldMapping('path')
  612. ->issueGroup(t('DNM'));
  613. if (isset($destination_fields['pathauto'])) {
  614. $this->addFieldMapping('pathauto')
  615. ->issueGroup(t('DNM'));
  616. }
  617. }
  618. if (module_exists('statistics')) {
  619. $this->addUnmigratedDestinations(
  620. array('totalcount', 'daycount', 'timestamp'));
  621. }
  622. }
  623. }
  624. /**
  625. * TIP: An example of importing from an XML feed where both the id and the
  626. * data to import are in the same file. The id is a part of the data. See
  627. * the file in the xml directory - producers.xml which contains all IDs and
  628. * producer data for this example.
  629. *
  630. * Note that, if basing a migration on an XML source, you need to derive it
  631. * from XMLMigration instead of Migration.
  632. */
  633. class WineProducerMultiXMLMigration extends XMLMigration {
  634. public function __construct($arguments) {
  635. parent::__construct($arguments);
  636. $this->description =
  637. t('XML feed (multi items) of wine producers of the world');
  638. // There isn't a consistent way to automatically identify appropriate
  639. // "fields" from an XML feed, so we pass an explicit list of source fields.
  640. $fields = array(
  641. 'name' => t('Producer name'),
  642. 'description' => t('Description of producer'),
  643. 'authorid' => t('Numeric ID of the author'),
  644. 'region' => t('Name of region'),
  645. );
  646. // IMPORTANT: Do not try this at home! We have included importable files
  647. // with the migrate_example module so it can be very simply installed and
  648. // run, but you should never include any data you want to keep private
  649. // (especially user data like email addresses, phone numbers, etc.) in the
  650. // module directory. Your source data should be outside of the webroot, and
  651. // should not be anywhere where it may get committed into a revision control
  652. // system.
  653. // This can also be an URL instead of a file path.
  654. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  655. $items_url = $xml_folder . 'producers.xml';
  656. // We use the MigrateSourceMultiItems class for any source where we obtain
  657. // the list of IDs to process and the data for each item from the same
  658. // file. Examples include multiple items defined in a single xml file or a
  659. // single json file where in both cases the id is part of the item.
  660. // This is the xpath identifying the items to be migrated, relative to the
  661. // document.
  662. $item_xpath = '/producers/producer';
  663. // This is the xpath relative to the individual items - thus the full xpath
  664. // of an ID will be /producers/producer/sourceid.
  665. $item_ID_xpath = 'sourceid';
  666. $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
  667. $this->source = new MigrateSourceMultiItems($items_class, $fields);
  668. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  669. // The source ID here is the one retrieved from each data item in the XML
  670. // file, and used to identify specific items
  671. $this->map = new MigrateSQLMap($this->machineName,
  672. array(
  673. 'sourceid' => array(
  674. 'type' => 'varchar',
  675. 'length' => 4,
  676. 'not null' => TRUE,
  677. )
  678. ),
  679. MigrateDestinationNode::getKeySchema()
  680. );
  681. // TIP: Note that for XML sources, in addition to the source field passed to
  682. // addFieldMapping (the name under which it will be saved in the data row
  683. // passed through the migration process) we specify the Xpath used to
  684. // retrieve the value from the XML.
  685. // TIP: Note that all xpaths for fields begin at the last element of the
  686. // item xpath since each item xml chunk is processed individually.
  687. // (ex. xpath=name is equivalent to a full xpath of
  688. // /producers/producer/name).
  689. $this->addFieldMapping('title', 'name')
  690. ->xpath('name');
  691. $this->addFieldMapping('uid', 'authorid')
  692. ->xpath('authorid')
  693. ->sourceMigration('WineUser')
  694. ->defaultValue(1);
  695. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  696. ->xpath('region');
  697. $this->addFieldMapping('body', 'description')
  698. ->xpath('description');
  699. $this->addUnmigratedDestinations(array(
  700. 'body:summary', 'body:format',
  701. 'changed',
  702. 'comment',
  703. 'created',
  704. 'is_new',
  705. 'language',
  706. 'log',
  707. 'migrate_example_wine_regions:create_term',
  708. 'migrate_example_wine_regions:ignore_case',
  709. 'migrate_example_wine_regions:source_type',
  710. 'promote',
  711. 'revision',
  712. 'revision_uid',
  713. 'status',
  714. 'sticky',
  715. 'tnid',
  716. 'translate',
  717. ));
  718. $destination_fields = $this->destination->fields();
  719. if (isset($destination_fields['path'])) {
  720. $this->addFieldMapping('path')
  721. ->issueGroup(t('DNM'));
  722. if (isset($destination_fields['pathauto'])) {
  723. $this->addFieldMapping('pathauto')
  724. ->issueGroup(t('DNM'));
  725. }
  726. }
  727. if (module_exists('statistics')) {
  728. $this->addUnmigratedDestinations(
  729. array('totalcount', 'daycount', 'timestamp'));
  730. }
  731. }
  732. }
  733. /**
  734. * TIP: An example of importing from an XML feed with namespaces, where both
  735. * the id and the data to import are in the same file. The id is a part of
  736. * the data. See the file in the xml directory - producers3.xml which contains
  737. * all IDs and producer data for this example.
  738. *
  739. * Note that, if basing a migration on an XML source, you need to derive it
  740. * from XMLMigration instead of Migration.
  741. */
  742. class WineProducerMultiNamespaceXMLMigration extends XMLMigration {
  743. public function __construct($arguments) {
  744. parent::__construct($arguments);
  745. $this->description =
  746. t('Namespaced XML feed (multi items) of wine producers of the world');
  747. // There isn't a consistent way to automatically identify appropriate
  748. // "fields" from an XML feed, so we pass an explicit list of source fields.
  749. $fields = array(
  750. 'pr:name' => t('Producer name'),
  751. 'pr:description' => t('Description of producer'),
  752. 'pr:authorid' => t('Numeric ID of the author'),
  753. 'pr:region' => t('Name of region'),
  754. );
  755. // IMPORTANT: Do not try this at home! We have included importable files
  756. // with the migrate_example module so it can be very simply installed and
  757. // run, but you should never include any data you want to keep private
  758. // (especially user data like email addresses, phone numbers, etc.) in the
  759. // module directory. Your source data should be outside of the webroot, and
  760. // should not be anywhere where it may get committed into a revision control
  761. // system.
  762. // This can also be an URL instead of a file path.
  763. $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
  764. $items_url = $xml_folder . 'producers3.xml';
  765. // We use the MigrateSourceMultiItems class for any source where we obtain
  766. // the list of IDs to process and the data for each item from the same
  767. // file. Examples include multiple items defined in a single xml file or a
  768. // single json file where in both cases the id is part of the item.
  769. // This is the xpath identifying the items to be migrated, relative to the
  770. // document.
  771. $item_xpath = '/pr:producers/pr:producer';
  772. // This is the xpath relative to the individual items - thus the full xpath
  773. // of an ID will be /producers/producer/sourceid.
  774. $item_ID_xpath = 'pr:sourceid';
  775. // All XML namespaces used in the XML file need to be defined here too.
  776. $namespaces = array('pr' => 'http://www.wine.org/wine-producers');
  777. $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath, $namespaces);
  778. $this->source = new MigrateSourceMultiItems($items_class, $fields);
  779. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  780. // The source ID here is the one retrieved from each data item in the XML
  781. // file, and used to identify specific items
  782. $this->map = new MigrateSQLMap($this->machineName,
  783. array(
  784. 'sourceid' => array(
  785. 'type' => 'varchar',
  786. 'length' => 4,
  787. 'not null' => TRUE,
  788. )
  789. ),
  790. MigrateDestinationNode::getKeySchema()
  791. );
  792. // TIP: Note that for XML sources, in addition to the source field passed to
  793. // addFieldMapping (the name under which it will be saved in the data row
  794. // passed through the migration process) we specify the Xpath used to
  795. // retrieve the value from the XML.
  796. // TIP: Note that all xpaths for fields begin at the last element of the
  797. // item xpath since each item xml chunk is processed individually.
  798. // (ex. xpath=name is equivalent to a full xpath of
  799. // /producers/producer/name).
  800. $this->addFieldMapping('title', 'pr:name')
  801. ->xpath('pr:name');
  802. $this->addFieldMapping('uid', 'pr:authorid')
  803. ->xpath('pr:authorid')
  804. ->sourceMigration('WineUser')
  805. ->defaultValue(1);
  806. $this->addFieldMapping('migrate_example_wine_regions', 'pr:region')
  807. ->xpath('pr:region');
  808. $this->addFieldMapping('body', 'pr:description')
  809. ->xpath('pr:description');
  810. $this->addUnmigratedDestinations(array(
  811. 'body:summary', 'body:format',
  812. 'changed',
  813. 'comment',
  814. 'created',
  815. 'is_new',
  816. 'language',
  817. 'log',
  818. 'migrate_example_wine_regions:create_term',
  819. 'migrate_example_wine_regions:ignore_case',
  820. 'migrate_example_wine_regions:source_type',
  821. 'promote',
  822. 'revision',
  823. 'revision_uid',
  824. 'status',
  825. 'sticky',
  826. 'tnid',
  827. 'translate',
  828. ));
  829. $destination_fields = $this->destination->fields();
  830. if (isset($destination_fields['path'])) {
  831. $this->addFieldMapping('path')
  832. ->issueGroup(t('DNM'));
  833. if (isset($destination_fields['pathauto'])) {
  834. $this->addFieldMapping('pathauto')
  835. ->issueGroup(t('DNM'));
  836. }
  837. }
  838. if (module_exists('statistics')) {
  839. $this->addUnmigratedDestinations(
  840. array('totalcount', 'daycount', 'timestamp'));
  841. }
  842. }
  843. }
  844. /**
  845. * TIP: An alternative approach using MigrateSourceSQL. This uses a different
  846. * XML library, which advances element-by-element through the XML file rather
  847. * than reading in the whole file. This source will work better with large XML
  848. * files, but is slower for small files and has a more restrictive query
  849. * language for selecting the elements to process.
  850. */
  851. class WineProducerXMLPullMigration extends XMLMigration {
  852. public function __construct($arguments) {
  853. parent::__construct($arguments);
  854. $this->description = t('XML feed (pull) of wine producers of the world');
  855. $fields = array(
  856. 'name' => t('Producer name'),
  857. 'description' => t('Description of producer'),
  858. 'authorid' => t('Numeric ID of the author'),
  859. 'region' => t('Name of region'),
  860. );
  861. // IMPORTANT: Do not try this at home! We have included importable files
  862. // with the migrate_example module so it can be very simply installed and
  863. // run, but you should never include any data you want to keep private
  864. // (especially user data like email addresses, phone numbers, etc.) in the
  865. // module directory. Your source data should be outside of the webroot, and
  866. // should not be anywhere where it may get committed into a revision control
  867. // system.
  868. // This can also be an URL instead of a local file path.
  869. $xml_folder = DRUPAL_ROOT . '/' .
  870. drupal_get_path('module', 'migrate_example') . '/xml/';
  871. $items_url = $xml_folder . 'producers2.xml';
  872. // As with MigrateSourceMultiItems, this applies where there is not a
  873. // separate list of IDs to process - the source XML file is entirely
  874. // self-contained. For the ID path, and xpath for each component, we can
  875. // use the full xpath syntax as usual. However, the syntax to select the
  876. // elements that correspond to objects to import is more limited. It must
  877. // be a fully-qualified path to the element (i.e.,
  878. // /producers/producer rather than just //producer).
  879. $item_xpath = '/producers/producer'; // relative to document
  880. $item_ID_xpath = 'sourceid'; // relative to item_xpath
  881. $this->source = new MigrateSourceXML($items_url, $item_xpath,
  882. $item_ID_xpath, $fields);
  883. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  884. $this->map = new MigrateSQLMap($this->machineName,
  885. array(
  886. 'sourceid' => array(
  887. 'type' => 'varchar',
  888. 'length' => 4,
  889. 'not null' => TRUE,
  890. )
  891. ),
  892. MigrateDestinationNode::getKeySchema()
  893. );
  894. $this->addFieldMapping('title', 'name')
  895. ->xpath('name');
  896. $this->addFieldMapping('uid', 'authorid')
  897. ->xpath('authorid')
  898. ->sourceMigration('WineUser')
  899. ->defaultValue(1);
  900. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  901. ->xpath('region');
  902. $this->addFieldMapping('body', 'description')
  903. ->xpath('description');
  904. $this->addUnmigratedDestinations(array(
  905. 'body:summary', 'body:format',
  906. 'changed',
  907. 'comment',
  908. 'created',
  909. 'is_new',
  910. 'language',
  911. 'log',
  912. 'migrate_example_wine_regions:create_term',
  913. 'migrate_example_wine_regions:ignore_case',
  914. 'migrate_example_wine_regions:source_type',
  915. 'promote',
  916. 'revision',
  917. 'revision_uid',
  918. 'status',
  919. 'sticky',
  920. 'tnid',
  921. 'translate',
  922. ));
  923. $destination_fields = $this->destination->fields();
  924. if (isset($destination_fields['path'])) {
  925. $this->addFieldMapping('path')
  926. ->issueGroup(t('DNM'));
  927. if (isset($destination_fields['pathauto'])) {
  928. $this->addFieldMapping('pathauto')
  929. ->issueGroup(t('DNM'));
  930. }
  931. }
  932. if (module_exists('statistics')) {
  933. $this->addUnmigratedDestinations(
  934. array('totalcount', 'daycount', 'timestamp'));
  935. }
  936. }
  937. }
  938. /**
  939. * TIP: An alternative approach using MigrateSourceSQL. This uses a different
  940. * XML library, which advances element-by-element through the XML file rather
  941. * than reading in the whole file. This source will work better with large XML
  942. * files, but is slower for small files and has a more restrictive query
  943. * language for selecting the elements to process.
  944. */
  945. class WineProducerNamespaceXMLPullMigration extends XMLMigration {
  946. public function __construct($arguments) {
  947. parent::__construct($arguments);
  948. $this->description = t('XML feed with namespaces (pull) of wine producers of the world');
  949. $fields = array(
  950. 'pr:name' => t('Producer name'),
  951. 'pr:description' => t('Description of producer'),
  952. 'pr:authorid' => t('Numeric ID of the author'),
  953. 'pr:region' => t('Name of region'),
  954. );
  955. // IMPORTANT: Do not try this at home! We have included importable files
  956. // with the migrate_example module so it can be very simply installed and
  957. // run, but you should never include any data you want to keep private
  958. // (especially user data like email addresses, phone numbers, etc.) in the
  959. // module directory. Your source data should be outside of the webroot, and
  960. // should not be anywhere where it may get committed into a revision control
  961. // system.
  962. // This can also be an URL instead of a local file path.
  963. $xml_folder = DRUPAL_ROOT . '/' .
  964. drupal_get_path('module', 'migrate_example') . '/xml/';
  965. $items_url = $xml_folder . 'producers4.xml';
  966. // As with MigrateSourceMultiItems, this applies where there is not a
  967. // separate list of IDs to process - the source XML file is entirely
  968. // self-contained. For the ID path, and xpath for each component, we can
  969. // use the full xpath syntax as usual. However, the syntax to select the
  970. // elements that correspond to objects to import is more limited. It must
  971. // be a fully-qualified path to the element (i.e.,
  972. // /producers/producer rather than just //producer).
  973. $item_xpath = '/pr:producers/pr:producer'; // relative to document
  974. $item_ID_xpath = 'pr:sourceid'; // relative to item_xpath
  975. $namespaces = array('pr' => 'http://www.wine.org/wine-producers');
  976. $this->source = new MigrateSourceXML($items_url, $item_xpath,
  977. $item_ID_xpath, $fields,
  978. array(), $namespaces);
  979. $this->destination = new MigrateDestinationNode('migrate_example_producer');
  980. $this->map = new MigrateSQLMap($this->machineName,
  981. array(
  982. 'sourceid' => array(
  983. 'type' => 'varchar',
  984. 'length' => 4,
  985. 'not null' => TRUE,
  986. )
  987. ),
  988. MigrateDestinationNode::getKeySchema()
  989. );
  990. $this->addFieldMapping('title', 'pr:name')
  991. ->xpath('pr:name');
  992. $this->addFieldMapping('uid', 'pr:authorid')
  993. ->xpath('pr:authorid')
  994. ->sourceMigration('WineUser')
  995. ->defaultValue(1);
  996. $this->addFieldMapping('migrate_example_wine_regions', 'pr:region')
  997. ->xpath('pr:region');
  998. $this->addFieldMapping('body', 'pr:description')
  999. ->xpath('pr:description');
  1000. $this->addUnmigratedDestinations(array(
  1001. 'body:summary', 'body:format',
  1002. 'changed',
  1003. 'comment',
  1004. 'created',
  1005. 'is_new',
  1006. 'language',
  1007. 'log',
  1008. 'migrate_example_wine_regions:create_term',
  1009. 'migrate_example_wine_regions:ignore_case',
  1010. 'migrate_example_wine_regions:source_type',
  1011. 'promote',
  1012. 'revision',
  1013. 'revision_uid',
  1014. 'status',
  1015. 'sticky',
  1016. 'tnid',
  1017. 'translate',
  1018. ));
  1019. $destination_fields = $this->destination->fields();
  1020. if (isset($destination_fields['path'])) {
  1021. $this->addFieldMapping('path')
  1022. ->issueGroup(t('DNM'));
  1023. if (isset($destination_fields['pathauto'])) {
  1024. $this->addFieldMapping('pathauto')
  1025. ->issueGroup(t('DNM'));
  1026. }
  1027. }
  1028. if (module_exists('statistics')) {
  1029. $this->addUnmigratedDestinations(
  1030. array('totalcount', 'daycount', 'timestamp'));
  1031. }
  1032. }
  1033. }
  1034. // TODO: Add node_reference field pointing to producer
  1035. class WineWineMigration extends AdvancedExampleMigration {
  1036. public function __construct($arguments) {
  1037. parent::__construct($arguments);
  1038. $this->description = t('Wines of the world');
  1039. $query = db_select('migrate_example_wine', 'w')
  1040. ->fields('w', array('wineid', 'name', 'body', 'excerpt',
  1041. 'accountid', 'posted', 'last_changed', 'variety', 'region',
  1042. 'rating'));
  1043. $query->leftJoin('migrate_example_wine_category_wine', 'cwbw',
  1044. "w.wineid = cwbw.wineid");
  1045. $query->leftJoin('migrate_example_wine_categories', 'bw',
  1046. "cwbw.categoryid = bw.categoryid AND bw.type = 'best_with'");
  1047. // Gives a single comma-separated list of related terms
  1048. $query->groupBy('w.wineid');
  1049. $query->addExpression('GROUP_CONCAT(bw.categoryid)', 'best_with');
  1050. $count_query = db_select('migrate_example_wine', 'w');
  1051. $count_query->addExpression('COUNT(wineid)', 'cnt');
  1052. // TIP: By passing an array of source fields to the MigrateSourceSQL
  1053. // constructor, we can modify the descriptions of source fields (which just
  1054. // default, for SQL migrations, to table_alias.column_name), as well as add
  1055. // additional fields (which may be populated in prepareRow()).
  1056. $source_fields = array(
  1057. 'wineid' => t('Wine ID in the old system'),
  1058. 'name' => t('The name of the wine'),
  1059. 'best_vintages' => t('What years were best for this wine?'),
  1060. 'url' => t('Image URLs attached to this wine; populated in prepareRow()'),
  1061. 'image_alt' =>
  1062. t('Image alt text attached to this wine; populated in prepareRow()'),
  1063. 'image_title' =>
  1064. t('Image titles attached to this wine; populated in prepareRow()'),
  1065. );
  1066. // TIP: By default, each time a migration is run, any previously unprocessed
  1067. // source items are imported (along with any previously-imported items
  1068. // marked for update). If the source data contains a timestamp that is set
  1069. // to the creation time of each new item, as well as set to the update time
  1070. // for any existing items that are updated, then you can have those updated
  1071. // items automatically reimported by setting the field as your highwater
  1072. // field.
  1073. $this->highwaterField = array(
  1074. 'name' => 'last_changed', // Column to be used as highwater mark
  1075. 'alias' => 'w', // Table alias containing that column
  1076. 'type' => 'int', // By default, highwater marks are assumed to
  1077. // be lexicographically sortable (e.g.,
  1078. // '2011-05-19 17:53:12'). To properly deal with
  1079. // integer highwater marks (such as UNIX
  1080. // timestamps), indicate so here.
  1081. );
  1082. // Note that it is important to process rows in the order of the highwater
  1083. // mark.
  1084. $query->orderBy('last_changed');
  1085. $this->source = new MigrateSourceSQL($query, $source_fields, $count_query);
  1086. $this->destination = new MigrateDestinationNode('migrate_example_wine');
  1087. // You can add a 'track_last_imported' option to the map, to record the
  1088. // timestamp of when each item was last imported in the map table.
  1089. $this->map = new MigrateSQLMap($this->machineName,
  1090. array(
  1091. 'wineid' => array(
  1092. 'type' => 'int',
  1093. 'unsigned' => TRUE,
  1094. 'not null' => TRUE,
  1095. 'description' => 'Wine ID',
  1096. 'alias' => 'w',
  1097. )
  1098. ),
  1099. MigrateDestinationNode::getKeySchema(),
  1100. 'default', // The SQL connection where the map/message tables are created.
  1101. array('track_last_imported' => TRUE)
  1102. );
  1103. // Mapped fields
  1104. $this->addFieldMapping('title', 'name')
  1105. ->description(t('Mapping wine name in source to node title'));
  1106. $this->addFieldMapping('uid', 'accountid')
  1107. ->sourceMigration('WineUser')
  1108. ->defaultValue(1);
  1109. // TIP: By default, term relationship are assumed to be passed by name.
  1110. // In this case, the source values are IDs, so we specify the relevant
  1111. // migration (so the tid can be looked up in the map), and tell the term
  1112. // field handler that it is receiving tids instead of names
  1113. $this->addFieldMapping('migrate_example_wine_varieties', 'variety')
  1114. ->sourceMigration('WineVariety');
  1115. $this->addFieldMapping('migrate_example_wine_varieties:source_type')
  1116. ->defaultValue('tid');
  1117. $this->addFieldMapping('migrate_example_wine_regions', 'region')
  1118. ->sourceMigration('WineRegion');
  1119. $this->addFieldMapping('migrate_example_wine_regions:source_type')
  1120. ->defaultValue('tid');
  1121. $this->addFieldMapping('migrate_example_wine_best_with', 'best_with')
  1122. ->separator(',')
  1123. ->sourceMigration('WineBestWith');
  1124. $this->addFieldMapping('migrate_example_wine_best_with:source_type')
  1125. ->defaultValue('tid');
  1126. $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
  1127. $this->addFieldMapping('field_migrate_example_top_vintag', 'best_vintages');
  1128. // TIP: You can apply one or more functions to a source value using
  1129. // ->callbacks(). The function must take a single argument and return a
  1130. // value which is a transformation of the argument. As this example shows,
  1131. // you can have multiple callbacks, and they can either be straight
  1132. // functions or class methods. In this case, our custom method prepends
  1133. // 'review: ' to the body, and then we call a standard Drupal function to
  1134. // uppercase the whole body.
  1135. $this->addFieldMapping('body', 'body')
  1136. ->callbacks(array($this, 'addTitlePrefix'), 'drupal_strtoupper');
  1137. $this->addFieldMapping('body:summary', 'excerpt');
  1138. // We will get the image data from a related table in prepareRow()
  1139. $this->addFieldMapping('field_migrate_example_image', 'url');
  1140. // Indicate that we want each file to maintain its name, replacing any
  1141. // previous file of the same name (as opposed to being renamed to avoid
  1142. // collisions, which is the default).
  1143. $this->addFieldMapping('field_migrate_example_image:file_replace')
  1144. ->defaultValue(FILE_EXISTS_REPLACE);
  1145. $this->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
  1146. $this->addFieldMapping('field_migrate_example_image:title', 'image_title');
  1147. $this->addFieldMapping('sticky')
  1148. ->defaultValue(0);
  1149. $this->addFieldMapping('created', 'posted');
  1150. $this->addFieldMapping('changed', 'last_changed');
  1151. // Unmapped source fields
  1152. $this->addUnmigratedSources(array('last_changed'));
  1153. // Unmapped destination fields
  1154. $this->addUnmigratedDestinations(array(
  1155. 'body:format',
  1156. 'comment',
  1157. 'field_migrate_example_image:destination_dir',
  1158. 'field_migrate_example_image:destination_file',
  1159. 'field_migrate_example_image:file_class',
  1160. 'field_migrate_example_image:preserve_files',
  1161. 'field_migrate_example_image:source_dir',
  1162. 'field_migrate_example_image:urlencode',
  1163. 'is_new',
  1164. 'language',
  1165. 'log',
  1166. 'migrate_example_wine_best_with:create_term',
  1167. 'migrate_example_wine_best_with:ignore_case',
  1168. 'migrate_example_wine_regions:create_term',
  1169. 'migrate_example_wine_regions:ignore_case',
  1170. 'migrate_example_wine_varieties:create_term',
  1171. 'migrate_example_wine_varieties:ignore_case',
  1172. 'promote',
  1173. 'revision',
  1174. 'revision_uid',
  1175. 'status',
  1176. 'tnid',
  1177. 'translate',
  1178. ));
  1179. if (module_exists('statistics')) {
  1180. $this->addUnmigratedDestinations(
  1181. array('totalcount', 'daycount', 'timestamp'));
  1182. }
  1183. }
  1184. protected function addTitlePrefix($source_title) {
  1185. return t('review: !title', array('!title' => $source_title));
  1186. }
  1187. // TIP: Implement a prepareRow() method to manipulate the source row between
  1188. // retrieval from the database and the automatic applicaton of mappings.
  1189. public function prepareRow($current_row) {
  1190. // Always start your prepareRow implementation with this clause. You need to
  1191. // be sure your parent classes have their chance at the row, and that if
  1192. // they return FALSE (indicating the row should be skipped) you pass that
  1193. // on.
  1194. if (parent::prepareRow($current_row) === FALSE) {
  1195. return FALSE;
  1196. }
  1197. // We used the MySQL GROUP_CONCAT function above to handle a multi-value
  1198. // source field - more portably, we query the related table with multiple
  1199. // values here, so the values can run through the mapping process.
  1200. $source_id = $current_row->wineid;
  1201. $result = db_select('migrate_example_wine_vintages', 'v')
  1202. ->fields('v', array('vintage'))
  1203. ->condition('wineid', $source_id)
  1204. ->execute();
  1205. foreach ($result as $row) {
  1206. $current_row->best_vintages[] = $row->vintage;
  1207. }
  1208. // We can have multiple files per node, so we pull them here along with
  1209. // their related data (alt/title).
  1210. /*
  1211. * This is disabled - see http://drupal.org/node/1679798. To demonstrate
  1212. * remote file migration, edit the migrate_example_wine_files table and enter
  1213. * the URLs of known remote image files, then enable this code.
  1214. $result = db_select('migrate_example_wine_files', 'f')
  1215. ->fields('f', array('url', 'image_alt', 'image_title'))
  1216. ->condition('wineid', $source_id)
  1217. ->execute();
  1218. foreach ($result as $row) {
  1219. $current_row->url[] = $row->url;
  1220. $current_row->image_alt[] = $row->image_alt;
  1221. $current_row->image_title[] = $row->image_title;
  1222. }
  1223. */
  1224. // We could also have used this function to decide to skip a row, in cases
  1225. // where that couldn't easily be done through the original query. Simply
  1226. // return FALSE in such cases.
  1227. return TRUE;
  1228. }
  1229. }
  1230. class WineCommentMigration extends AdvancedExampleMigration {
  1231. public function __construct($arguments) {
  1232. parent::__construct($arguments);
  1233. $this->description = 'Comments about wines';
  1234. $query = db_select('migrate_example_wine_comment', 'wc')
  1235. ->fields('wc', array('commentid', 'comment_parent', 'name', 'mail',
  1236. 'accountid', 'body', 'wineid', 'subject', 'commenthost',
  1237. 'userpage', 'posted', 'lastchanged'))
  1238. ->orderBy('comment_parent');
  1239. $this->source = new MigrateSourceSQL($query);
  1240. $this->destination =
  1241. new MigrateDestinationComment('comment_node_migrate_example_wine');
  1242. $this->map = new MigrateSQLMap($this->machineName,
  1243. array('commentid' => array(
  1244. 'type' => 'int',
  1245. 'unsigned' => TRUE,
  1246. 'not null' => TRUE,
  1247. )
  1248. ),
  1249. MigrateDestinationComment::getKeySchema()
  1250. );
  1251. // Mapped fields
  1252. $this->addSimpleMappings(array('name', 'subject', 'mail'));
  1253. $this->addFieldMapping('status')
  1254. ->defaultValue(COMMENT_PUBLISHED);
  1255. $this->addFieldMapping('nid', 'wineid')
  1256. ->sourceMigration('WineWine');
  1257. $this->addFieldMapping('uid', 'accountid')
  1258. ->sourceMigration('WineUser')
  1259. ->defaultValue(0);
  1260. $this->addFieldMapping('pid', 'comment_parent')
  1261. ->sourceMigration('WineComment')
  1262. ->description('Parent comment');
  1263. $this->addFieldMapping('comment_body', 'body');
  1264. $this->addFieldMapping('hostname', 'commenthost');
  1265. $this->addFieldMapping('created', 'posted');
  1266. $this->addFieldMapping('changed', 'lastchanged');
  1267. $this->addFieldMapping('homepage', 'userpage');
  1268. // No unmapped source fields
  1269. // Unmapped destination fields
  1270. $this->addUnmigratedDestinations(array(
  1271. 'comment_body:format',
  1272. 'language',
  1273. 'thread',
  1274. ));
  1275. $this->removeFieldMapping('pathauto');
  1276. }
  1277. }
  1278. // TIP: An easy way to simply migrate into a Drupal table (i.e., one defined
  1279. // through the Schema API) is to use the MigrateDestinationTable destination.
  1280. // Just pass the table name to getKeySchema and the MigrateDestinationTable
  1281. // constructor.
  1282. class WineTableMigration extends AdvancedExampleMigration {
  1283. public function __construct($arguments) {
  1284. parent::__construct($arguments);
  1285. $this->description = 'Miscellaneous table data';
  1286. $table_name = 'migrate_example_wine_table_dest';
  1287. $query = db_select('migrate_example_wine_table_source', 't')
  1288. ->fields('t', array('fooid', 'field1', 'field2'));
  1289. $this->source = new MigrateSourceSQL($query);
  1290. $this->destination = new MigrateDestinationTable($table_name);
  1291. $this->map = new MigrateSQLMap($this->machineName,
  1292. array('fooid' => array(
  1293. 'type' => 'int',
  1294. 'unsigned' => TRUE,
  1295. 'not null' => TRUE,
  1296. )
  1297. ),
  1298. MigrateDestinationTable::getKeySchema($table_name)
  1299. );
  1300. // Mapped fields
  1301. $this->addFieldMapping('drupal_text', 'field1');
  1302. $this->addFieldMapping('drupal_int', 'field2');
  1303. $this->addUnmigratedDestinations(array('recordid'));
  1304. $this->removeFieldMapping('path');
  1305. $this->removeFieldMapping('pathauto');
  1306. }
  1307. }
  1308. /**
  1309. * This migration works with WinePrepMigration to make sure auto_nodetitle is
  1310. * re-enabled if we disabled it.
  1311. */
  1312. class WineFinishMigration extends MigrationBase {
  1313. public function __construct($arguments) {
  1314. parent::__construct($arguments);
  1315. $this->description =
  1316. t('If auto_nodetitle is present and was previously enabled, re-enable it');
  1317. }
  1318. public function isComplete() {
  1319. // There is no incomplete state for this operation.
  1320. return TRUE;
  1321. }
  1322. public function import() {
  1323. if (!module_exists('auto_nodetitle')) {
  1324. if (WinePrepMigration::$wasEnabled) {
  1325. module_enable(array('auto_nodetitle'));
  1326. self::displayMessage(t('Re-enabled auto_nodetitle module'), 'success');
  1327. }
  1328. else {
  1329. self::displayMessage(t('auto_nodetitle was not originally enabled'),
  1330. 'success');
  1331. }
  1332. }
  1333. else {
  1334. self::displayMessage(t('Auto_nodetitle module already enabled'),
  1335. 'success');
  1336. }
  1337. return Migration::RESULT_COMPLETED;
  1338. }
  1339. }
  1340. /**
  1341. * TIP: This demonstrates a migration designed not to import new content, but
  1342. * to update existing content (in this case, revised wine ratings)
  1343. */
  1344. class WineUpdatesMigration extends AdvancedExampleMigration {
  1345. public function __construct($arguments) {
  1346. parent::__construct($arguments);
  1347. $this->description = t('Update wine ratings');
  1348. $query = db_select('migrate_example_wine_updates', 'w')
  1349. ->fields('w', array('wineid', 'rating'));
  1350. $this->source = new MigrateSourceSQL($query);
  1351. $this->destination = new MigrateDestinationNode('migrate_example_wine');
  1352. $this->map = new MigrateSQLMap($this->machineName,
  1353. array(
  1354. 'wineid' => array(
  1355. 'type' => 'int',
  1356. 'unsigned' => TRUE,
  1357. 'not null' => TRUE,
  1358. 'description' => 'Wine ID',
  1359. 'alias' => 'w',
  1360. )
  1361. ),
  1362. MigrateDestinationNode::getKeySchema()
  1363. );
  1364. // Indicate we're updating existing data. The default, Migration::SOURCE,
  1365. // would cause existing nodes to be completely replaced by the source data.
  1366. // In this case, the existing node will be loaded and only the rating
  1367. // altered.
  1368. $this->systemOfRecord = Migration::DESTINATION;
  1369. // Mapped fields
  1370. // The destination handler needs the nid to change - since the incoming data
  1371. // has a source id, not a nid, we need to apply the original wine migration
  1372. // mapping to populate the nid.
  1373. $this->addFieldMapping('nid', 'wineid')
  1374. ->sourceMigration('WineWine');
  1375. $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
  1376. // No unmapped source fields
  1377. // Unmapped destination fields - these will be left unchanged in this case
  1378. // (normally they would be overwritten with their default values).
  1379. $this->addFieldMapping('uid');
  1380. $this->addFieldMapping('migrate_example_wine_varieties');
  1381. $this->addFieldMapping('migrate_example_wine_regions');
  1382. $this->addFieldMapping('migrate_example_wine_best_with');
  1383. $this->addFieldMapping('body');
  1384. $this->addFieldMapping('field_migrate_example_image');
  1385. $this->addFieldMapping('sticky');
  1386. $this->addFieldMapping('created');
  1387. $this->addFieldMapping('changed');
  1388. $this->addUnmigratedDestinations(array(
  1389. 'body:format', 'body:summary',
  1390. 'comment',
  1391. 'field_migrate_example_image:alt',
  1392. 'field_migrate_example_image:destination_dir',
  1393. 'field_migrate_example_image:destination_file',
  1394. 'field_migrate_example_image:file_class',
  1395. 'field_migrate_example_image:file_replace',
  1396. 'field_migrate_example_image:preserve_files',
  1397. 'field_migrate_example_image:source_dir',
  1398. 'field_migrate_example_image:title',
  1399. 'field_migrate_example_image:urlencode',
  1400. 'field_migrate_example_top_vintag',
  1401. 'is_new',
  1402. 'language',
  1403. 'log',
  1404. 'migrate_example_wine_best_with:source_type',
  1405. 'migrate_example_wine_best_with:create_term',
  1406. 'migrate_example_wine_best_with:ignore_case',
  1407. 'migrate_example_wine_regions:source_type',
  1408. 'migrate_example_wine_regions:create_term',
  1409. 'migrate_example_wine_regions:ignore_case',
  1410. 'migrate_example_wine_varieties:source_type',
  1411. 'migrate_example_wine_varieties:create_term',
  1412. 'migrate_example_wine_varieties:ignore_case',
  1413. 'promote',
  1414. 'revision',
  1415. 'revision_uid',
  1416. 'status',
  1417. 'title',
  1418. 'tnid',
  1419. 'translate',
  1420. ));
  1421. if (module_exists('statistics')) {
  1422. $this->addUnmigratedDestinations(
  1423. array('totalcount', 'daycount', 'timestamp'));
  1424. }
  1425. }
  1426. }
  1427. class WineCommentUpdatesMigration extends AdvancedExampleMigration {
  1428. public function __construct($arguments) {
  1429. parent::__construct($arguments);
  1430. $this->description = 'Update wine comments';
  1431. $query = db_select('migrate_example_wine_comment_updates', 'wc')
  1432. ->fields('wc', array('commentid', 'subject'));
  1433. $this->source = new MigrateSourceSQL($query);
  1434. $this->destination =
  1435. new MigrateDestinationComment('comment_node_migrate_example_wine');
  1436. $this->map = new MigrateSQLMap($this->machineName,
  1437. array('commentid' => array(
  1438. 'type' => 'int',
  1439. 'unsigned' => TRUE,
  1440. 'not null' => TRUE,
  1441. )
  1442. ),
  1443. MigrateDestinationComment::getKeySchema()
  1444. );
  1445. $this->systemOfRecord = Migration::DESTINATION;
  1446. // Mapped fields
  1447. $this->addFieldMapping('cid', 'commentid')
  1448. ->sourceMigration('WineComment');
  1449. $this->addFieldMapping('subject', 'subject');
  1450. // No unmapped source fields
  1451. // Unmapped destination fields
  1452. $this->addUnmigratedDestinations(array(
  1453. 'changed',
  1454. 'comment_body', 'comment_body:format',
  1455. 'created',
  1456. 'homepage',
  1457. 'hostname',
  1458. 'language',
  1459. 'mail',
  1460. 'name',
  1461. 'nid',
  1462. 'pid',
  1463. 'status',
  1464. 'thread',
  1465. 'uid',
  1466. ));
  1467. $this->removeFieldMapping('pathauto');
  1468. }
  1469. }
  1470. class WineVarietyUpdatesMigration extends AdvancedExampleMigration {
  1471. public function __construct($arguments) {
  1472. parent::__construct($arguments);
  1473. $this->description =
  1474. t('Migrate varieties from the source database to taxonomy terms');
  1475. $query = db_select('migrate_example_wine_variety_updates', 'wc')
  1476. ->fields('wc', array('categoryid', 'details'));
  1477. $this->source = new MigrateSourceSQL($query);
  1478. $this->destination =
  1479. new MigrateDestinationTerm('migrate_example_wine_varieties');
  1480. $this->map = new MigrateSQLMap($this->machineName,
  1481. array(
  1482. 'categoryid' => array('type' => 'int',
  1483. 'unsigned' => TRUE,
  1484. 'not null' => TRUE,
  1485. )
  1486. ),
  1487. MigrateDestinationTerm::getKeySchema()
  1488. );
  1489. $this->systemOfRecord = Migration::DESTINATION;
  1490. // Mapped fields
  1491. $this->addFieldMapping('tid', 'categoryid')
  1492. ->sourceMigration('WineVariety');
  1493. $this->addFieldMapping('description', 'details');
  1494. // Unmapped source fields
  1495. // Unmapped destination fields
  1496. $this->addUnmigratedDestinations(array(
  1497. 'format',
  1498. 'name',
  1499. 'parent',
  1500. 'parent_name',
  1501. 'weight',
  1502. ));
  1503. }
  1504. }
  1505. class WineUserUpdatesMigration extends AdvancedExampleMigration {
  1506. public function __construct($arguments) {
  1507. parent::__construct($arguments);
  1508. $this->description = t('Account updates');
  1509. $query = db_select('migrate_example_wine_account_updates', 'wa')
  1510. ->fields('wa', array('accountid', 'sex'));
  1511. $this->source = new MigrateSourceSQL($query);
  1512. $this->destination = new MigrateDestinationUser();
  1513. $this->map = new MigrateSQLMap($this->machineName,
  1514. array('accountid' => array(
  1515. 'type' => 'int',
  1516. 'unsigned' => TRUE,
  1517. 'not null' => TRUE,
  1518. 'description' => 'Account ID.'
  1519. )
  1520. ),
  1521. MigrateDestinationUser::getKeySchema()
  1522. );
  1523. $this->systemOfRecord = Migration::DESTINATION;
  1524. // Mapped fields
  1525. $this->addFieldMapping('uid', 'accountid')
  1526. ->sourceMigration('WineUser');
  1527. $this->addFieldMapping('field_migrate_example_gender', 'sex')
  1528. ->description(t('Map from M/F to 0/1 in prepare method'));
  1529. // Unmapped source fields
  1530. // Unmapped destination fields
  1531. $this->addUnmigratedDestinations(array(
  1532. 'access',
  1533. 'created',
  1534. 'data',
  1535. 'init',
  1536. 'is_new',
  1537. 'language',
  1538. 'login',
  1539. 'mail',
  1540. 'name',
  1541. 'pass',
  1542. 'picture',
  1543. 'role_names',
  1544. 'roles',
  1545. 'signature',
  1546. 'signature_format',
  1547. 'status',
  1548. 'theme',
  1549. 'timezone',
  1550. ));
  1551. }
  1552. public function prepare(stdClass $account, stdClass $row) {
  1553. // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
  1554. // TIP: Note that the Migration prepare method is called after all other
  1555. // prepare handlers. Most notably, the field handlers have had their way
  1556. // and created field arrays, so we have to save in the same format.
  1557. switch ($row->sex) {
  1558. case 'm':
  1559. case 'M':
  1560. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
  1561. break;
  1562. case 'f':
  1563. case 'F':
  1564. $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
  1565. break;
  1566. default:
  1567. $account->field_migrate_example_gender = NULL;
  1568. break;
  1569. }
  1570. }
  1571. }