beer.inc 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  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 DynamicMigration {
  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. 'init', 'data'));
  233. // Oops, we made a typo - this should have been 'init'! If you have
  234. // migrate_ui enabled, look at the BeerUser info page - you'll see that it
  235. // displays a warning "int used as destination field in mapping but not in
  236. // list of destination fields", and also lists "1 unmapped" under Destination,
  237. // where it highlights "init" as unmapped.
  238. $this->addFieldMapping('int')
  239. ->issueGroup(t('DNM'));
  240. if (module_exists('path')) {
  241. $this->addFieldMapping('path')
  242. ->issueGroup(t('DNM'));
  243. if (module_exists('pathauto')) {
  244. $this->addFieldMapping('pathauto')
  245. ->issueGroup(t('DNM'));
  246. }
  247. }
  248. }
  249. }
  250. /**
  251. * The BeerNodeMigration uses the migrate_example_beer_node table as source
  252. * and creates Drupal nodes of type 'Beer' as destination.
  253. */
  254. class BeerNodeMigration extends BasicExampleMigration {
  255. public function __construct() {
  256. parent::__construct();
  257. $this->description = t('Beers of the world');
  258. // You may optionally declare dependencies for your migration - other migrations
  259. // which should run first. In this case, terms assigned to our nodes and
  260. // the authors of the nodes should be migrated before the nodes themselves.
  261. $this->dependencies = array('BeerTerm', 'BeerUser');
  262. $this->map = new MigrateSQLMap($this->machineName,
  263. array(
  264. 'bid' => array(
  265. 'type' => 'int',
  266. 'not null' => TRUE,
  267. 'description' => 'Beer ID.',
  268. 'alias' => 'b',
  269. )
  270. ),
  271. MigrateDestinationNode::getKeySchema()
  272. );
  273. // We have a more complicated query. The Migration class fundamentally
  274. // depends on taking a single source row and turning it into a single
  275. // Drupal object, so how do we deal with zero or more terms attached to
  276. // each node? One way (demonstrated for MySQL) is to pull them into a single
  277. // comma-separated list.
  278. $query = db_select('migrate_example_beer_node', 'b')
  279. ->fields('b', array('bid', 'name', 'body', 'excerpt', 'aid', 'countries',
  280. 'image', 'image_alt', 'image_title', 'image_description'));
  281. $query->leftJoin('migrate_example_beer_topic_node', 'tb', 'b.bid = tb.bid');
  282. // Gives a single comma-separated list of related terms
  283. $query->groupBy('tb.bid');
  284. $query->addExpression('GROUP_CONCAT(tb.style)', 'terms');
  285. // By default, MigrateSourceSQL derives a count query from the main query -
  286. // but we can override it if we know a simpler way
  287. $count_query = db_select('migrate_example_beer_node', 'b');
  288. $count_query->addExpression('COUNT(bid)', 'cnt');
  289. // Passing the cache_counts option means the source count (shown in
  290. // drush migrate-status) will be cached - this can be very handy when
  291. // dealing with a slow source database.
  292. $this->source = new MigrateSourceSQL($query, array(), $count_query,
  293. array('cache_counts' => TRUE));
  294. // Set up our destination - nodes of type migrate_example_beer
  295. $this->destination = new MigrateDestinationNode('migrate_example_beer');
  296. // Mapped fields
  297. $this->addFieldMapping('title', 'name')
  298. ->description(t('Mapping beer name in source to node title'));
  299. $this->addFieldMapping('sticky')
  300. ->description(t('Should we default this to 0 or 1?'))
  301. ->issueGroup(t('Client questions'))
  302. ->issueNumber(765736)
  303. ->issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_LOW);
  304. // To maintain node identities between the old and new systems (i.e., have
  305. // the same unique IDs), map the ID column from the old system to nid and
  306. // set is_new to TRUE. This works only if we're importing into a system that
  307. // has no existing nodes with the nids being imported.
  308. $this->addFieldMapping('nid', 'bid')
  309. ->description(t('Preserve old beer ID as nid in Drupal'));
  310. $this->addFieldMapping('is_new')
  311. ->defaultValue(TRUE);
  312. // References to related objects (such as the author of the content) are
  313. // most likely going to be identifiers from the source data, not Drupal
  314. // identifiers (such as uids). You can use the mapping from the relevant
  315. // migration to translate from the old ID to the Drupal identifier.
  316. // Note that we also provide a default value of 1 - if the lookup fails to
  317. // find a corresponding uid for the aid, the owner will be the administrative
  318. // account.
  319. $this->addFieldMapping('uid', 'aid')
  320. ->sourceMigration('BeerUser')
  321. ->defaultValue(1);
  322. // This is a multi-value text field
  323. $this->addFieldMapping('field_migrate_example_country', 'countries')
  324. ->separator('|');
  325. // These are related terms, which by default will be looked up by name
  326. $this->addFieldMapping('migrate_example_beer_styles', 'terms')
  327. ->separator(',');
  328. // Some fields may have subfields such as text formats or summaries
  329. // (equivalent to teasers in previous Drupal versions).
  330. // These can be individually mapped as we see here.
  331. $this->addFieldMapping('body', 'body');
  332. $this->addFieldMapping('body:summary', 'excerpt');
  333. // Copy an image file, write DB record to files table, and save in Field storage.
  334. // We map the filename (relative to the source_dir below) to the field itself.
  335. $this->addFieldMapping('field_migrate_example_image', 'image');
  336. // Here we specify the directory containing the source files.
  337. $this->addFieldMapping('field_migrate_example_image:source_dir')
  338. ->defaultValue(drupal_get_path('module', 'migrate_example'));
  339. // And we map the alt and title values in the database to those on the image.
  340. $this->addFieldMapping('field_migrate_example_image:alt', 'image_alt');
  341. $this->addFieldMapping('field_migrate_example_image:title', 'image_title');
  342. // No description for images, only alt and title
  343. $this->addUnmigratedSources(array('image_description'));
  344. // Unmapped destination fields
  345. $this->addUnmigratedDestinations(array('created', 'changed', 'status',
  346. 'promote', 'revision', 'language', 'revision_uid', 'log', 'tnid',
  347. 'body:format', 'body:language', 'migrate_example_beer_styles:source_type',
  348. 'migrate_example_beer_styles:create_term', 'field_migrate_example_image:destination_dir',
  349. 'field_migrate_example_image:language', 'field_migrate_example_image:file_replace',
  350. 'field_migrate_example_image:preserve_files', '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. }
  405. }