beer.inc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. <?php
  2. /**
  3. * @file
  4. * A basic example of using the Migrate module to import taxonomy, users, nodes,
  5. * and comments.
  6. *
  7. * The basic idea is
  8. * - The users in the source application are listed in the
  9. * migrate_example_beer_account table and are transformed into Drupal users.
  10. * - Drupal "beer" nodes describe beers; The information to create the nodes
  11. * comes from the migrate_example_beer_node table.
  12. * - Taxonomy terms for the beer nodes (ale, pilsner) come from the
  13. * migrate_example_beer_topic table and they are applied to nodes using the
  14. * source information in the migrate_example_beer_topic_node table.
  15. * - Comments to be attached to the beer nodes are described in the source
  16. * migrate_example_beer_comment table.
  17. *
  18. * We will use the Migrate API to import and transform this data and turn it into
  19. * a working Drupal system.
  20. */
  21. /**
  22. * To define a migration process from a set of source data to a particular
  23. * kind of Drupal object (for example, a specific node type), you define
  24. * a class derived from Migration. You must define a constructor to initialize
  25. * your migration object. By default, your class name will be the "machine name"
  26. * of the migration, by which you refer to it. Note that the machine name is
  27. * case-sensitive.
  28. *
  29. * In any serious migration project, you will find there are some options
  30. * which are common to the individual migrations you're implementing. You can
  31. * define an abstract intermediate class derived from Migration, then derive your
  32. * individual migrations from that, to share settings, utility functions, etc.
  33. */
  34. abstract class BasicExampleMigration extends Migration {
  35. public function __construct() {
  36. // Always call the parent constructor first for basic setup
  37. parent::__construct();
  38. // With migrate_ui enabled, migration pages will indicate people involved in
  39. // the particular migration, with their role and contact info. We default the
  40. // list in the shared class; it can be overridden for specific migrations.
  41. $this->team = array(
  42. new MigrateTeamMember('Liz Taster', 'ltaster@example.com', t('Product Owner')),
  43. new MigrateTeamMember('Larry Brewer', 'lbrewer@example.com', t('Implementor')),
  44. );
  45. // Individual mappings in a migration can be linked to a ticket or issue
  46. // in an external tracking system. Define the URL pattern here in the shared
  47. // class with ':id:' representing the position of the issue number, then add
  48. // ->issueNumber(1234) to a mapping.
  49. $this->issuePattern = 'http://drupal.org/node/:id:';
  50. }
  51. }
  52. /**
  53. * There are four essential components to set up in your constructor:
  54. * $this->source - An instance of a class derived from MigrateSource, this
  55. * will feed data to the migration.
  56. * $this->destination - An instance of a class derived from MigrateDestination,
  57. * this will receive data that originated from the source and has been mapped
  58. * by the Migration class, and create Drupal objects.
  59. * $this->map - An instance of a class derived from MigrateMap, this will keep
  60. * track of which source items have been imported and what destination objects
  61. * they map to.
  62. * Mappings - Use $this->addFieldMapping to tell the Migration class what source
  63. * fields correspond to what destination fields, and additional information
  64. * associated with the mappings.
  65. */
  66. class BeerTermMigration extends BasicExampleMigration {
  67. public function __construct() {
  68. parent::__construct();
  69. // Human-friendly description of your migration process. Be as detailed as you
  70. // like.
  71. $this->description = t('Migrate styles from the source database to taxonomy terms');
  72. // Create a map object for tracking the relationships between source rows
  73. // and their resulting Drupal objects. Usually, you'll use the MigrateSQLMap
  74. // class, which uses database tables for tracking. Pass the machine name
  75. // (BeerTerm) of this migration to use in generating map and message tables.
  76. // And, pass schema definitions for the primary keys of the source and
  77. // destination - we need to be explicit for our source, but the destination
  78. // class knows its schema already.
  79. $this->map = new MigrateSQLMap($this->machineName,
  80. array(
  81. 'style' => array('type' => 'varchar',
  82. 'length' => 255,
  83. 'not null' => TRUE,
  84. 'description' => 'Topic ID',
  85. )
  86. ),
  87. MigrateDestinationTerm::getKeySchema()
  88. );
  89. // In this example, we're using tables that have been added to the existing
  90. // Drupal database but which are not Drupal tables. You can examine the
  91. // various tables (starting here with migrate_example_beer_topic) using a
  92. // database browser like phpMyAdmin.
  93. // First, we set up a query for this data. Note that by ordering on
  94. // style_parent, we guarantee root terms are migrated first, so the
  95. // parent_name mapping below will find that the parent exists.
  96. $query = db_select('migrate_example_beer_topic', 'met')
  97. ->fields('met', array('style', 'details', 'style_parent', 'region', 'hoppiness'))
  98. // This sort assures that parents are saved before children.
  99. ->orderBy('style_parent', 'ASC');
  100. // Create a MigrateSource object, which manages retrieving the input data.
  101. $this->source = new MigrateSourceSQL($query);
  102. // Set up our destination - terms in the migrate_example_beer_styles vocabulary
  103. $this->destination = new MigrateDestinationTerm('migrate_example_beer_styles');
  104. // Assign mappings TO destination fields FROM source fields. To discover
  105. // the names used in these calls, use the drush commands
  106. // drush migrate-fields-destination BeerTerm
  107. // drush migrate-fields-source BeerTerm
  108. $this->addFieldMapping('name', 'style');
  109. $this->addFieldMapping('description', 'details');
  110. // Documenting your mappings makes it easier for the whole team to see
  111. // exactly what the status is when developing a migration process.
  112. $this->addFieldMapping('parent_name', 'style_parent')
  113. ->description(t('The incoming style_parent field is the name of the term parent'));
  114. // Mappings are assigned issue groups, by which they are grouped on the
  115. // migration info page when the migrate_ui module is enabled. The default
  116. // is 'Done', indicating active mappings which need no attention. A
  117. // suggested practice is to use groups of:
  118. // Do Not Migrate (or DNM) to indicate source fields which are not being used,
  119. // or destination fields not to be populated by migration.
  120. // Client Issues to indicate input from the client is needed to determine
  121. // how a given field is to be migrated.
  122. // Implementor Issues to indicate that the client has provided all the
  123. // necessary information, and now the implementor needs to complete the work.
  124. $this->addFieldMapping(NULL, 'hoppiness')
  125. ->description(t('This info will not be maintained in Drupal'))
  126. ->issueGroup(t('DNM'));
  127. // Open mapping issues can be assigned priorities (the default is
  128. // MigrateFieldMapping::ISSUE_PRIORITY_OK). If you're using an issue
  129. // tracking system, and have defined issuePattern (see BasicExampleMigration
  130. // above), you can specify a ticket/issue number in the system on the
  131. // mapping and migrate_ui will link directory to it.
  132. $this->addFieldMapping(NULL, 'region')
  133. ->description('Will a field be added to the vocabulary for this?')
  134. ->issueGroup(t('Client Issues'))
  135. ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
  136. ->issueNumber(770064);
  137. // It is good practice to account for all source and destination fields
  138. // explicitly - this makes sure that everyone understands exactly what is
  139. // being migrated and what is not. Also, migrate_ui highlights unmapped
  140. // fields, or mappings involving fields not in the source and destination,
  141. // so if (for example) a new field is added to the destination field it's
  142. // immediately visible, and you can find out if anything needs to be
  143. // migrated into it.
  144. $this->addFieldMapping('format')
  145. ->issueGroup(t('DNM'));
  146. $this->addFieldMapping('weight')
  147. ->issueGroup(t('DNM'));
  148. $this->addFieldMapping('parent')
  149. ->issueGroup(t('DNM'));
  150. // We conditionally DNM these fields, so your field mappings will be clean
  151. // whether or not you have path and or pathauto enabled
  152. if (module_exists('path')) {
  153. $this->addFieldMapping('path')
  154. ->issueGroup(t('DNM'));
  155. if (module_exists('pathauto')) {
  156. $this->addFieldMapping('pathauto')
  157. ->issueGroup(t('DNM'));
  158. }
  159. }
  160. }
  161. }
  162. /**
  163. * And that's it for the BeerTerm migration! For a simple migration, all you
  164. * have to do is define the source, the destination, and mappings between the
  165. * two - to import the data you simply do:
  166. * drush migrate-import BeerTerm
  167. *
  168. * However, in real-world migrations not everything can be represented simply
  169. * through static mappings - you will frequently need to do some run-time
  170. * transformations of the data.
  171. */
  172. class BeerUserMigration extends BasicExampleMigration {
  173. public function __construct() {
  174. // The basic setup is similar to BeerTermMigraiton
  175. parent::__construct();
  176. $this->description = t('Beer Drinkers of the world');
  177. $this->map = new MigrateSQLMap($this->machineName,
  178. array('aid' => array(
  179. 'type' => 'int',
  180. 'not null' => TRUE,
  181. 'description' => 'Account ID.'
  182. )
  183. ),
  184. MigrateDestinationUser::getKeySchema()
  185. );
  186. $query = db_select('migrate_example_beer_account', 'mea')
  187. ->fields('mea', array('aid', 'status', 'posted', 'name', 'nickname', 'password', 'mail', 'sex', 'beers'));
  188. $this->source = new MigrateSourceSQL($query);
  189. $this->destination = new MigrateDestinationUser();
  190. // One good way to organize your mappings is in three groups - mapped fields,
  191. // unmapped source fields, and unmapped destination fields
  192. // Mapped fields
  193. // This is a shortcut you can use when the source and destination field
  194. // names are identical (i.e., the email address field is named 'mail' in
  195. // both the source table and in Drupal).
  196. $this->addSimpleMappings(array('status', 'mail'));
  197. // Our source table has two entries for 'alice', but we must have a unique
  198. // username in the Drupal 'users' table. dedupe() creates new, unique
  199. // destination values when the source field of that value already exists.
  200. // For example, if we're importing a user with name 'test' and a user
  201. // 'test' already exists in the target, we'll create a new user named
  202. // 'test_1'.
  203. // dedupe() takes the Drupal table and column for determining uniqueness.
  204. $this->addFieldMapping('name', 'name')
  205. ->dedupe('users', 'name');
  206. // The migrate module automatically converts date/time strings to UNIX timestamps.
  207. $this->addFieldMapping('created', 'posted');
  208. $this->addFieldMapping('pass', 'password');
  209. // Instead of mapping a source field to a destination field, you can
  210. // hardcode a default value. You can also use both together - if a default
  211. // value is provided in addition to a source field, the default value will
  212. // be applied to any rows where the source field is empty or NULL.
  213. $this->addFieldMapping('roles')
  214. ->defaultValue(DRUPAL_AUTHENTICATED_RID);
  215. $this->addFieldMapping('field_migrate_example_gender', 'sex');
  216. // The source field has beer names separated by a pipe character ('|'). By
  217. // adding ->separator('|'), the migration will automatically break them out,
  218. // look up the node with each title, and assign the node reference to this
  219. // user.
  220. if (module_exists('node_reference')) {
  221. $this->addFieldMapping('field_migrate_example_favbeers', 'beers')
  222. ->separator('|');
  223. }
  224. // Unmapped source fields
  225. $this->addFieldMapping(NULL, 'nickname')
  226. ->issueGroup(t('DNM'));
  227. // Unmapped destination fields
  228. // This is a shortcut you can use to mark several destination fields as DNM
  229. // at once
  230. $this->addUnmigratedDestinations(array('theme', 'signature', 'access', 'login',
  231. 'timezone', 'language', 'picture', 'is_new', 'signature_format', 'role_names'));
  232. // Oops, we made a typo - this should have been 'init'! If you have
  233. // migrate_ui enabled, look at the BeerUser info page - you'll see that it
  234. // displays a warning "int used as destination field in mapping but not in
  235. // list of destination fields", and also lists "1 unmapped" under Destination,
  236. // where it highlights "init" as unmapped.
  237. $this->addFieldMapping('int')
  238. ->issueGroup(t('DNM'));
  239. if (module_exists('path')) {
  240. $this->addFieldMapping('path')
  241. ->issueGroup(t('DNM'));
  242. if (module_exists('pathauto')) {
  243. $this->addFieldMapping('pathauto')
  244. ->issueGroup(t('DNM'));
  245. }
  246. }
  247. }
  248. }
  249. /**
  250. * The BeerNodeMigration uses the migrate_example_beer_node table as source
  251. * and creates Drupal nodes of type 'Beer' as destination.
  252. */
  253. class BeerNodeMigration extends BasicExampleMigration {
  254. public function __construct() {
  255. parent::__construct();
  256. $this->description = t('Beers of the world');
  257. // You may optionally declare dependencies for your migration - other migrations
  258. // which should run first. In this case, terms assigned to our nodes and
  259. // the authors of the nodes should be migrated before the nodes themselves.
  260. $this->dependencies = array('BeerTerm', 'BeerUser');
  261. $this->map = new MigrateSQLMap($this->machineName,
  262. array(
  263. 'bid' => array(
  264. 'type' => 'int',
  265. 'not null' => TRUE,
  266. 'description' => 'Beer ID.',
  267. 'alias' => 'b',
  268. )
  269. ),
  270. MigrateDestinationNode::getKeySchema()
  271. );
  272. // We have a more complicated query. The Migration class fundamentally
  273. // depends on taking a single source row and turning it into a single
  274. // Drupal object, so how do we deal with zero or more terms attached to
  275. // each node? One way (demonstrated for MySQL) is to pull them into a single
  276. // comma-separated list.
  277. $query = db_select('migrate_example_beer_node', 'b')
  278. ->fields('b', array('bid', 'name', 'body', 'excerpt', 'aid', 'countries',
  279. 'image', 'image_alt', 'image_title', 'image_description'));
  280. $query->leftJoin('migrate_example_beer_topic_node', 'tb', 'b.bid = tb.bid');
  281. // Gives a single comma-separated list of related terms
  282. $query->groupBy('tb.bid');
  283. $query->addExpression('GROUP_CONCAT(tb.style)', 'terms');
  284. // By default, MigrateSourceSQL derives a count query from the main query -
  285. // but we can override it if we know a simpler way
  286. $count_query = db_select('migrate_example_beer_node', 'b');
  287. $count_query->addExpression('COUNT(bid)', 'cnt');
  288. // Passing the cache_counts option means the source count (shown in
  289. // drush migrate-status) will be cached - this can be very handy when
  290. // dealing with a slow source database.
  291. $this->source = new MigrateSourceSQL($query, array(), $count_query,
  292. array('cache_counts' => TRUE));
  293. // Set up our destination - nodes of type migrate_example_beer
  294. $this->destination = new MigrateDestinationNode('migrate_example_beer');
  295. // Mapped fields
  296. $this->addFieldMapping('title', 'name')
  297. ->description(t('Mapping beer name in source to node title'));
  298. $this->addFieldMapping('sticky')
  299. ->description(t('Should we default this to 0 or 1?'))
  300. ->issueGroup(t('Client questions'))
  301. ->issueNumber(765736)
  302. ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_LOW);
  303. // To maintain node identities between the old and new systems (i.e., have
  304. // the same unique IDs), map the ID column from the old system to nid and
  305. // set is_new to TRUE. This works only if we're importing into a system that
  306. // has no existing nodes with the nids being imported.
  307. $this->addFieldMapping('nid', 'bid')
  308. ->description(t('Preserve old beer ID as nid in Drupal'));
  309. $this->addFieldMapping('is_new')
  310. ->defaultValue(TRUE);
  311. // References to related objects (such as the author of the content) are
  312. // most likely going to be identifiers from the source data, not Drupal
  313. // identifiers (such as uids). You can use the mapping from the relevant
  314. // migration to translate from the old ID to the Drupal identifier.
  315. // Note that we also provide a default value of 1 - if the lookup fails to
  316. // find a corresponding uid for the aid, the owner will be the administrative
  317. // account.
  318. $this->addFieldMapping('uid', 'aid')
  319. ->sourceMigration('BeerUser')
  320. ->defaultValue(1);
  321. // This is a multi-value text field
  322. $this->addFieldMapping('field_migrate_example_country', 'countries')
  323. ->separator('|');
  324. // These are related terms, which by default will be looked up by name
  325. $this->addFieldMapping('migrate_example_beer_styles', 'terms')
  326. ->separator(',');
  327. // Some fields may have subfields such as text formats or summaries
  328. // (equivalent to teasers in previous Drupal versions).
  329. // These can be individually mapped as we see here.
  330. $this->addFieldMapping('body', 'body');
  331. $this->addFieldMapping('body:summary', 'excerpt');
  332. // Copy an image file, write DB record to files table, and save in Field storage.
  333. // We map the filename (relative to the source_dir below) to the field itself.
  334. $this->addFieldMapping('field_migrate_example_image', 'image');
  335. // Here we specify the directory containing the source files.
  336. $this->addFieldMapping('field_migrate_example_image:source_dir')
  337. ->defaultValue(drupal_get_path('module', 'migrate_example'));
  338. // And we map the alt and title values in the database to those on the image.
  339. $this->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
  340. $this->addFieldMapping('field_migrate_example_image:title', 'image_title');
  341. // No description for images, only alt and title
  342. $this->addUnmigratedSources(array('image_description'));
  343. // Unmapped destination fields
  344. $this->addUnmigratedDestinations(array('created', 'changed', 'status',
  345. 'promote', 'revision', 'language', 'revision_uid', 'log', 'tnid',
  346. 'body:format', 'body:language', 'migrate_example_beer_styles:source_type',
  347. 'migrate_example_beer_styles:create_term', 'field_migrate_example_image:destination_dir',
  348. 'field_migrate_example_image:language', 'field_migrate_example_image:file_replace',
  349. 'field_migrate_example_image:preserve_files', 'field_migrate_example_country:format',
  350. 'field_migrate_example_country:language', 'comment',
  351. 'field_migrate_example_image:file_class', 'field_migrate_example_image:destination_file'));
  352. if (module_exists('path')) {
  353. $this->addFieldMapping('path')
  354. ->issueGroup(t('DNM'));
  355. if (module_exists('pathauto')) {
  356. $this->addFieldMapping('pathauto')
  357. ->issueGroup(t('DNM'));
  358. }
  359. }
  360. if (module_exists('statistics')) {
  361. $this->addUnmigratedDestinations(array('totalcount', 'daycount', 'timestamp'));
  362. }
  363. }
  364. }
  365. /**
  366. * Import items from the migrate_example_beer_comment table and make them into
  367. * Drupal comment objects.
  368. */
  369. class BeerCommentMigration extends BasicExampleMigration {
  370. public function __construct() {
  371. parent::__construct();
  372. $this->description = 'Comments about beers';
  373. $this->dependencies = array('BeerUser', 'BeerNode');
  374. $this->map = new MigrateSQLMap($this->machineName,
  375. array('cid' => array(
  376. 'type' => 'int',
  377. 'not null' => TRUE,
  378. )
  379. ),
  380. MigrateDestinationComment::getKeySchema()
  381. );
  382. $query = db_select('migrate_example_beer_comment', 'mec')
  383. ->fields('mec', array('cid', 'cid_parent', 'name', 'mail', 'aid', 'body', 'bid', 'subject'))
  384. ->orderBy('cid_parent', 'ASC');
  385. $this->source = new MigrateSourceSQL($query);
  386. $this->destination = new MigrateDestinationComment('comment_node_migrate_example_beer');
  387. // Mapped fields
  388. $this->addSimpleMappings(array('name', 'subject', 'mail'));
  389. $this->addFieldMapping('status')
  390. ->defaultValue(COMMENT_PUBLISHED);
  391. // We preserved bid => nid ids during BeerNode import so simple mapping suffices.
  392. $this->addFieldMapping('nid', 'bid');
  393. $this->addFieldMapping('uid', 'aid')
  394. ->sourceMigration('BeerUser')
  395. ->defaultValue(0);
  396. $this->addFieldMapping('pid', 'cid_parent')
  397. ->sourceMigration('BeerComment')
  398. ->description('Parent comment.');
  399. $this->addFieldMapping('comment_body', 'body');
  400. // No unmapped source fields
  401. // Unmapped destination fields
  402. $this->addUnmigratedDestinations(array('hostname', 'created', 'changed',
  403. 'thread', 'homepage', 'language', 'comment_body:format', 'comment_body:language'));
  404. if (module_exists('path')) {
  405. $this->addFieldMapping('path')
  406. ->issueGroup(t('DNM'));
  407. }
  408. }
  409. }