5 * Advanced migration examples. These serve two purposes:
7 * 1. To demonstrate some of the more advanced usages of the Migrate module.
8 * Search for "TIP:" below for features not found in the basic example.
9 * 2. To provide thorough test cases for the simpletest suite.
14 * Abstract intermediate class holding common settings.
16 abstract
class AdvancedExampleMigration
extends Migration
{
19 public
function __construct() {
20 parent
::__construct();
22 new
MigrateTeamMember('Jack Kramer', 'jkramer@example.com', t('Taster')),
23 new
MigrateTeamMember('Linda Madison', 'lmadison@example.com', t('Winemaker')),
25 $this->issuePattern
= 'http://drupal.org/node/:id:';
27 // A format of our own, for testing migration of formats
28 $this->basicFormat
= filter_format_load('migrate_example');
33 * TIP: While usually you'll create true migrations - processes that copy data
34 * from some source into Drupal - you can also define processing steps for either
35 * the import or rollback stages that take other actions. In this case, we want
36 * to disable auto_nodetitle while the migration steps run.
38 class WinePrepMigration
extends MigrationBase
{
39 // Remember whether the auto_nodetitle was originally enabled, so we know whether
41 public static
$wasEnabled = FALSE
;
43 public
function __construct() {
44 parent
::__construct();
45 $this->description
= t('If auto_nodetitle is present, disable it for the duration');
46 // TIP: Regular dependencies, besides enforcing (in the absence of --force)
47 // the run order of migrations, affect the sorting of migrations on display.
48 // You can use soft dependencies to affect just the display order when the
49 // migrations aren't technically required to run in a certain order. In this
50 // case, we want the wine migrations to appear after the beer migrations -
51 // without this line, they would be intermingled due to their lack of
52 // (formal) interdependencies.
53 $this->softDependencies
= array('BeerComment');
55 // Define isComplete(), returning a boolean, to indicate whether dependent
56 // migrations may proceed
57 public
function isComplete() {
58 // If Auto Node Title is disabled, other migrations are free to go
59 if (module_exists('auto_nodetitle')) {
66 // Implement any action you want to occur during an import process in an
67 // import() method (alternatively, if you have an action which you want to
68 // run during rollbacks, define a rollback() method).
69 public
function import() {
70 if (module_exists('auto_nodetitle')) {
71 self
::$wasEnabled = TRUE
;
72 module_disable(array('auto_nodetitle'));
73 $this->showMessage(t('Disabled auto_nodetitle module'), 'success');
76 self
::$wasEnabled = FALSE
;
77 $this->showMessage(t('Auto_nodetitle is already disabled'), 'success');
79 // Must return one of the MigrationBase RESULT constants
80 return MigrationBase
::RESULT_COMPLETED
;
84 // The term migrations are very similar - implement the commonalities here
85 abstract
class WineTermMigration
extends AdvancedExampleMigration
{
86 public
function __construct($type, $vocabulary_name, $description) {
87 parent
::__construct();
88 $this->description
= $description;
89 $this->dependencies
= array('WinePrep');
90 $this->map
= new
MigrateSQLMap($this->machineName
,
92 'categoryid' => array('type' => 'int',
97 MigrateDestinationTerm
::getKeySchema()
100 $query = db_select('migrate_example_wine_categories', 'wc')
101 ->fields('wc', array('categoryid', 'name', 'details', 'category_parent', 'ordering'))
102 ->condition('type', $type)
103 // This sort assures that parents are saved before children.
104 ->orderBy('category_parent', 'ASC');
105 $this->source
= new
MigrateSourceSQL($query);
106 $this->destination
= new
MigrateDestinationTerm($vocabulary_name);
109 $this->addFieldMapping('name', 'name');
110 $this->addFieldMapping('description', 'details');
111 $this->addFieldMapping('parent', 'category_parent')
112 ->sourceMigration($this->getMachineName());
113 $this->addFieldMapping('weight', 'ordering');
114 $this->addFieldMapping('format')
115 ->defaultValue($this->basicFormat
->format
);
117 // Unmapped source fields
119 // Unmapped destination fields
120 $this->addFieldMapping('parent_name')
121 ->issueGroup(t('DNM'));
125 class WineVarietyMigration
extends WineTermMigration
{
126 public
function __construct() {
127 parent
::__construct('variety', 'migrate_example_wine_varieties',
128 t('Migrate varieties from the source database to taxonomy terms'));
132 class WineRegionMigration
extends WineTermMigration
{
133 public
function __construct() {
134 parent
::__construct('region', 'migrate_example_wine_regions',
135 t('Migrate regions from the source database to taxonomy terms'));
139 class WineBestWithMigration
extends WineTermMigration
{
140 public
function __construct() {
141 parent
::__construct('best_with', 'migrate_example_wine_best_with',
142 t('Migrate "Best With" from the source database to taxonomy terms'));
147 * TIP: Files can be migrated directly by themselves, by using the MigrateDestinationFile
148 * class. This will copy the files themselves from the source, and set up the
149 * Drupal file tables appropriately.
151 class WineFileMigration
extends AdvancedExampleMigration
{
152 public
function __construct() {
153 parent
::__construct();
154 $this->description
= t('Profile images');
155 $this->dependencies
= array('WinePrep');
156 $this->map
= new
MigrateSQLMap($this->machineName
,
157 array('imageid' => array(
161 'description' => 'Image ID.'
164 MigrateDestinationFile
::getKeySchema()
166 $query = db_select('migrate_example_wine_files', 'wf')
167 ->fields('wf', array('imageid', 'url'))
169 $this->source
= new
MigrateSourceSQL($query);
171 // TIP: Set copy_file to copy the file from its source (which could be a
172 // remote server, i.e. the uri is of the form http://example.com/images/foo.jpg).
173 $this->destination
= new
MigrateDestinationFile(array('copy_file' => TRUE
));
175 // Just map the incoming URL to the destination's 'uri'
176 $this->addFieldMapping('uri', 'url');
177 $this->addUnmigratedDestinations(array('fid', 'uid', 'filename', 'status',
178 'filemime', 'timestamp'));
182 class WineRoleMigration
extends XMLMigration
{
183 public
function __construct() {
184 parent
::__construct();
185 $this->description
= t('XML feed (multi items) of roles (positions)');
186 $this->softDependencies
= array('WineFile');
188 // There isn't a consistent way to automatically identify appropriate "fields"
189 // from an XML feed, so we pass an explicit list of source fields
191 'name' => t('Position name'),
194 // The source ID here is the one retrieved from each data item in the XML file, and
195 // used to identify specific items
196 $this->map
= new
MigrateSQLMap($this->machineName
,
204 MigrateDestinationRole
::getKeySchema()
207 // This can also be an URL instead of a file path.
208 $xml_folder = DRUPAL_ROOT .
'/' .
drupal_get_path('module', 'migrate_example') .
'/xml/';
209 $items_url = $xml_folder .
'positions.xml';
210 $item_xpath = '/positions/position'; // relative to document
211 $item_ID_xpath = 'sourceid'; // relative to item_xpath and gets assembled
212 // into full path /producers/producer/sourceid
214 $items_class = new
MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
215 $this->source
= new
MigrateSourceMultiItems($items_class, $fields);
217 $this->destination
= new
MigrateDestinationRole();
219 $this->addFieldMapping('name', 'name')
221 $this->addUnmigratedDestinations(array('weight'));
225 class WineUserMigration
extends AdvancedExampleMigration
{
226 public
function __construct() {
227 parent
::__construct();
228 $this->description
= t('Wine Drinkers of the world');
229 $this->dependencies
= array('WinePrep', 'WineFile', 'WineRole');
230 $this->map
= new
MigrateSQLMap($this->machineName
,
231 array('accountid' => array(
235 'description' => 'Account ID.'
238 MigrateDestinationUser
::getKeySchema()
240 $query = db_select('migrate_example_wine_account', 'wa')
241 ->fields('wa', array('accountid', 'status', 'posted', 'name',
242 'password', 'mail', 'last_access', 'last_login',
243 'original_mail', 'sig', 'sex', 'imageid', 'positions'));
244 $this->source
= new
MigrateSourceSQL($query);
245 $this->destination
= new
MigrateDestinationUser();
248 $this->addSimpleMappings(array('name', 'status', 'mail'));
249 $this->addFieldMapping('created', 'posted')
250 ->description('See prepare method');
251 $this->addFieldMapping('access', 'last_access')
252 ->description('See prepare method');
253 $this->addFieldMapping('login', 'last_login')
254 ->description('See prepare method');
255 $this->addFieldMapping('pass', 'password');
256 $this->addFieldMapping('roles', 'positions')
258 ->sourceMigration('WineRole');
259 $this->addFieldMapping('signature', 'sig');
260 $this->addFieldMapping('signature_format')
261 ->defaultValue($this->basicFormat
->format
);
262 $this->addFieldMapping('init', 'original_mail');
263 $this->addFieldMapping('field_migrate_example_gender', 'sex')
264 ->description(t('Map from M/F to 0/1 in prepare method'));
265 $this->addFieldMapping('picture', 'imageid')
266 ->sourceMigration('WineFile');
268 // Unmapped source fields
270 // Unmapped destination fields
271 $this->addUnmigratedDestinations(array('theme', 'timezone', 'language'));
274 public
function prepare(stdClass
$account, stdClass
$row) {
275 // Source dates are in ISO format.
276 // Because the mappings above have been applied, $account->created contains
277 // the date/time string now - we could also pass $row->posted here.
278 $account->created
= strtotime($account->created
);
279 $account->access
= strtotime($account->access
);
280 $account->login
= strtotime($account->login
);
282 // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
283 // TIP: Note that the Migration prepare method is called after all other
284 // prepare handlers. Most notably, the field handlers have had their way
285 // and created field arrays, so we have to save in the same format.
289 $account->field_migrate_example_gender
[LANGUAGE_NONE
][0]['value'] = 0;
293 $account->field_migrate_example_gender
[LANGUAGE_NONE
][0]['value'] = 1;
296 unset($account->field_migrate_example_gender
);
302 class WineProducerMigration
extends AdvancedExampleMigration
{
303 public
function __construct() {
304 parent
::__construct();
305 $this->description
= t('Wine producers of the world');
306 $this->dependencies
= array('WineRegion', 'WineUser');
308 $this->map
= new
MigrateSQLMap($this->machineName
,
310 'producerid' => array(
317 MigrateDestinationNode
::getKeySchema()
320 $query = db_select('migrate_example_wine_producer', 'p')
321 ->fields('p', array('producerid', 'name', 'body', 'excerpt', 'accountid'));
322 // Region term is singletons, handled straighforwardly
323 $query->leftJoin('migrate_example_wine_category_producer', 'reg',
324 "p.producerid = reg.producerid");
325 $query->addField('reg', 'categoryid', 'region');
327 $this->source
= new
MigrateSourceSQL($query);
328 $this->destination
= new
MigrateDestinationNode('migrate_example_producer');
331 $this->addFieldMapping('title', 'name')
332 ->description(t('Mapping producer name in source to node title'));
333 $this->addFieldMapping('uid', 'accountid')
334 ->sourceMigration('WineUser')
336 $this->addFieldMapping('migrate_example_wine_regions', 'region')
337 ->sourceMigration('WineRegion')
338 ->arguments(array('source_type' => 'tid'));
339 $arguments = MigrateTextFieldHandler
::arguments(array('source_field' => 'excerpt'));
340 $this->addFieldMapping('body', 'body')
341 ->arguments($arguments);
342 $this->addFieldMapping(NULL
, 'excerpt');
343 $this->addFieldMapping('sticky')
346 // No unmapped source fields
348 // Unmapped destination fields
349 $this->addUnmigratedDestinations(array('is_new', 'name', 'created', 'changed',
350 'status', 'promote', 'revision', 'language'));
355 * TIP: An example of importing from an XML feed. See the files in the xml
356 * directory - index.xml contains a list of IDs to import, and <id>.xml
357 * is the data for a given producer.
359 * Note that, if basing a migration on an XML source, you need to derive it
360 * from XMLMigration instead of Migration.
362 class WineProducerXMLMigration
extends XMLMigration
{
363 public
function __construct() {
364 parent
::__construct();
365 $this->description
= t('XML feed of wine producers of the world');
366 $this->dependencies
= array('WineRegion', 'WineUser');
368 // There isn't a consistent way to automatically identify appropriate "fields"
369 // from an XML feed, so we pass an explicit list of source fields
371 'name' => t('Producer name'),
372 'description' => t('Description of producer'),
373 'authorid' => t('Numeric ID of the author'),
374 'region' => t('Name of region'),
377 // The source ID here is the one retrieved from the XML listing file, and
378 // used to identify the specific item's file
379 $this->map
= new
MigrateSQLMap($this->machineName
,
387 MigrateDestinationNode
::getKeySchema()
390 // This can also be an URL instead of a file path.
391 $xml_folder = DRUPAL_ROOT .
'/' .
drupal_get_path('module', 'migrate_example') .
'/xml/';
392 $list_url = $xml_folder .
'index.xml';
393 // Each ID retrieved from the list URL will be plugged into :id in the
394 // item URL to fetch the specific objects.
395 $item_url = $xml_folder .
':id.xml';
397 // We use the MigrateSourceList class for any source where we obtain the list
398 // of IDs to process separately from the data for each item. The listing
399 // and item are represented by separate classes, so for example we could
400 // replace the XML listing with a file directory listing, or the XML item
402 $this->source
= new
MigrateSourceList(new
MigrateListXML($list_url),
403 new
MigrateItemXML($item_url), $fields);
405 $this->destination
= new
MigrateDestinationNode('migrate_example_producer');
407 // TIP: Note that for XML sources, in addition to the source field passed to
408 // addFieldMapping (the name under which it will be saved in the data row
409 // passed through the migration process) we specify the Xpath used to retrieve
410 // the value from the XML.
411 $this->addFieldMapping('title', 'name')
412 ->xpath('/producer/name');
413 $this->addFieldMapping('uid', 'authorid')
414 ->xpath('/producer/authorid')
415 ->sourceMigration('WineUser')
417 $this->addFieldMapping('migrate_example_wine_regions', 'region')
418 ->xpath('/producer/region');
419 $this->addFieldMapping('body', 'description')
420 ->xpath('/producer/description');
425 * TIP: An example of importing from an XML feed where both the id and the
426 * data to import are in the same file. The id is a part of the data. See
427 * the file in the xml directory - producers.xml which contains all IDs and
428 * producer data for this example.
430 * Note that, if basing a migration on an XML source, you need to derive it
431 * from XMLMigration instead of Migration.
433 class WineProducerMultiXMLMigration
extends XMLMigration
{
434 public
function __construct() {
435 parent
::__construct();
436 $this->description
= t('XML feed (multi items) of wine producers of the world');
437 $this->dependencies
= array('WineRegion', 'WineUser');
439 // There isn't a consistent way to automatically identify appropriate "fields"
440 // from an XML feed, so we pass an explicit list of source fields
442 'name' => t('Producer name'),
443 'description' => t('Description of producer'),
444 'authorid' => t('Numeric ID of the author'),
445 'region' => t('Name of region'),
448 // The source ID here is the one retrieved from each data item in the XML file, and
449 // used to identify specific items
450 $this->map
= new
MigrateSQLMap($this->machineName
,
458 MigrateDestinationNode
::getKeySchema()
462 // This can also be an URL instead of a file path.
463 $xml_folder = DRUPAL_ROOT .
'/' .
drupal_get_path('module', 'migrate_example') .
'/xml/';
464 $items_url = $xml_folder .
'producers.xml';
466 // We use the MigrateSourceMultiItems class for any source where we obtain the list
467 // of IDs to process and the data for each item from the same file. Typically the data
468 // for an item is not contained in a single line within the source file. Examples include
469 // multiple items defined in a single xml file or a single json file where in both cases
470 // the id is part of the item.
472 $item_xpath = '/producers/producer'; // relative to document
474 $item_ID_xpath = 'sourceid'; // relative to item_xpath and gets assembled
475 // into full path /producers/producer/sourceid
477 $items_class = new
MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
478 $this->source
= new
MigrateSourceMultiItems($items_class, $fields);
480 $this->destination
= new
MigrateDestinationNode('migrate_example_producer');
482 // TIP: Note that for XML sources, in addition to the source field passed to
483 // addFieldMapping (the name under which it will be saved in the data row
484 // passed through the migration process) we specify the Xpath used to retrieve
485 // the value from the XML.
486 // TIP: Note that all xpaths for fields begin at the last element of the item
487 // xpath since each item xml chunk is processed individually.
488 // (ex. xpath=name is equivalent to a full xpath of /producers/producer/name)
489 $this->addFieldMapping('title', 'name')
491 $this->addFieldMapping('uid', 'authorid')
493 ->sourceMigration('WineUser')
495 $this->addFieldMapping('migrate_example_wine_regions', 'region')
497 $this->addFieldMapping('body', 'description')
498 ->xpath('description');
502 // TODO: Add node_reference field pointing to producer
503 class WineWineMigration
extends AdvancedExampleMigration
{
504 public
function __construct() {
505 parent
::__construct();
506 $this->description
= t('Wines of the world');
507 $this->dependencies
= array('WineVariety', 'WineRegion',
508 'WineBestWith', 'WineUser', 'WineProducer');
510 $this->map
= new
MigrateSQLMap($this->machineName
,
516 'description' => 'Wine ID',
520 MigrateDestinationNode
::getKeySchema()
523 $query = db_select('migrate_example_wine', 'w')
524 ->fields('w', array('wineid', 'name', 'body', 'excerpt', 'accountid',
525 'posted', 'last_changed', 'variety', 'region', 'rating'));
526 $query->leftJoin('migrate_example_wine_category_wine', 'cwbw',
527 "w.wineid = cwbw.wineid");
528 $query->leftJoin('migrate_example_wine_categories', 'bw',
529 "cwbw.categoryid = bw.categoryid AND bw.type = 'best_with'");
530 // Gives a single comma-separated list of related terms
531 $query->groupBy('w.wineid');
532 $query->addExpression('GROUP_CONCAT(bw.categoryid)', 'best_with');
534 $count_query = db_select('migrate_example_wine', 'w');
535 $count_query->addExpression('COUNT(wineid)', 'cnt');
537 // TIP: By passing an array of source fields to the MigrateSourceSQL constructor,
538 // we can modify the descriptions of source fields (which just default, for
539 // SQL migrations, to table_alias.column_name), as well as add additional fields
540 // (which may be populated in prepareRow()).
541 $source_fields = array(
542 'wineid' => t('Wine ID in the old system'),
543 'name' => t('The name of the wine'),
544 'best_vintages' => t('What years were best for this wine?'),
545 'images' => t('Images attached to this wine; populated in prepareRow()'),
548 // TIP: By default, each time a migration is run, any previously unimported source items
549 // are imported (along with any previously-imported items marked for update). If the
550 // source data contains a timestamp that is set to the creation time of each new item,
551 // as well as set to the update time for any existing items that are updated, then
552 // you can have those updated items automatically reimported by setting the field as
553 // your highwater field.
554 $this->highwaterField
= array(
555 'name' => 'last_changed', // Column to be used as highwater mark
556 'alias' => 'w', // Table alias containing that column
558 // Note that it is important to process rows in the order of the highwater mark
559 $query->orderBy('last_changed');
561 $this->source
= new
MigrateSourceSQL($query, $source_fields, $count_query);
562 $this->destination
= new
MigrateDestinationNode('migrate_example_wine');
565 $this->addFieldMapping('title', 'name')
566 ->description(t('Mapping wine name in source to node title'));
567 $this->addFieldMapping('uid', 'accountid')
568 ->sourceMigration('WineUser')
570 // TIP: By default, term relationship are assumed to be passed by name.
571 // In this case, the source values are IDs, so we specify the relevant
572 // migration (so the tid can be looked up in the map), and tell the term
573 // field handler that it is receiving tids instead of names
574 $this->addFieldMapping('migrate_example_wine_varieties', 'variety')
575 ->sourceMigration('WineVariety')
576 ->arguments(array('source_type' => 'tid'));
577 $this->addFieldMapping('migrate_example_wine_regions', 'region')
578 ->sourceMigration('WineRegion')
579 ->arguments(array('source_type' => 'tid'));
580 $this->addFieldMapping('migrate_example_wine_best_with', 'best_with')
582 ->sourceMigration('WineBestWith')
583 ->arguments(array('source_type' => 'tid'));
584 $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
585 $this->addFieldMapping('field_migrate_example_top_vintag', 'best_vintages');
587 // TIP: You can apply one or more functions to a source value using ->callbacks().
588 // The function must take a single argument and return a value which is a
589 // transformation of the argument. As this example shows, you can have multiple
590 // callbacks, and they can either be straight functions or class methods. In
591 // this case, our custom method prepends 'review: ' to the body, and then we
592 // call a standard Drupal function to uppercase the whole body.
593 $arguments = MigrateTextFieldHandler
::arguments(array('source_field' => 'excerpt'));
594 $this->addFieldMapping('body', 'body')
595 ->arguments($arguments)
596 ->callbacks(array($this, 'addTitlePrefix'), 'drupal_strtoupper');
597 $this->addFieldMapping(NULL
, 'excerpt');
598 // We will get the image data from a related table in prepareRow()
599 $arguments = MigrateFileFieldHandler
::arguments(NULL
, 'file_copy', FILE_EXISTS_REPLACE
);
600 $this->addFieldMapping('field_migrate_example_image', 'images')
601 ->arguments($arguments);
602 $this->addFieldMapping('sticky')
604 // These are already UNIX timestamps, so just pass through
605 $this->addFieldMapping('created', 'posted');
606 $this->addFieldMapping('changed', 'last_changed');
608 // No unmapped source fields
610 // Unmapped destination fields
611 $this->addUnmigratedDestinations(array('is_new', 'status', 'promote',
612 'revision', 'language'));
615 protected
function addTitlePrefix($source_title) {
616 return t('review: ') .
$source_title;
619 // TIP: Implement a prepareRow() method to manipulate the source row between
620 // retrieval from the database and the automatic applicaton of mappings
621 public
function prepareRow($current_row) {
622 // We can only handle a single multi-value source field using GROUP_CONCAT
623 // as we did above - insert others with a query against the related table
624 // with multiple values here, so the values can run through the mapping process
625 $source_id = $current_row->wineid
;
626 $result = db_select('migrate_example_wine_vintages', 'v')
627 ->fields('v', array('vintage'))
628 ->condition('wineid', $source_id)
630 foreach ($result as
$row) {
631 $current_row->best_vintages
[] = $row->vintage
;
634 // An advanced feature of the file field handler is that in addition to the
635 // path to the image itself, we can add image properties like ALT text,
636 // encapsulating them as JSON
637 $result = db_select('migrate_example_wine_files', 'f')
638 ->fields('f', array('url', 'image_alt', 'image_title'))
639 ->condition('wineid', $source_id)
641 $current_row->images
= array();
642 foreach ($result as
$row) {
645 'alt' => $row->image_alt
,
646 'title' => $row->image_title
,
648 $current_row->images
[] = drupal_json_encode($image_data);
651 // We could also have used this function to decide to skip a row, in cases
652 // where that couldn't easily be done through the original query. Simply
653 // return FALSE in such cases.
658 class WineCommentMigration
extends AdvancedExampleMigration
{
659 public
function __construct() {
660 parent
::__construct();
661 $this->description
= 'Comments about wines';
662 $this->dependencies
= array('WineUser', 'WineWine');
663 $this->map
= new
MigrateSQLMap($this->machineName
,
664 array('commentid' => array(
670 MigrateDestinationComment
::getKeySchema()
672 $query = db_select('migrate_example_wine_comment', 'wc')
673 ->fields('wc', array('commentid', 'comment_parent', 'name', 'mail',
674 'accountid', 'body', 'wineid', 'subject', 'commenthost', 'userpage',
675 'posted', 'lastchanged'))
676 ->orderBy('comment_parent');
677 $this->source
= new
MigrateSourceSQL($query);
678 $this->destination
= new
MigrateDestinationComment('comment_node_migrate_example_wine');
681 $this->addSimpleMappings(array('name', 'subject', 'mail'));
682 $this->addFieldMapping('status')
683 ->defaultValue(COMMENT_PUBLISHED
);
684 $this->addFieldMapping('nid', 'wineid')
685 ->sourceMigration('WineWine');
686 $this->addFieldMapping('uid', 'accountid')
687 ->sourceMigration('WineUser')
689 $this->addFieldMapping('pid', 'comment_parent')
690 ->sourceMigration('WineComment')
691 ->description('Parent comment');
692 $this->addFieldMapping('comment_body', 'body');
693 $this->addFieldMapping('hostname', 'commenthost');
694 $this->addFieldMapping('created', 'posted');
695 $this->addFieldMapping('changed', 'lastchanged');
696 $this->addFieldMapping('homepage', 'userpage');
698 // No unmapped source fields
700 // Unmapped destination fields
701 $this->addUnmigratedDestinations(array('thread', 'language'));
705 // TIP: An easy way to simply migrate into a Drupal table (i.e., one defined
706 // through the Schema API) is to use the MigrateDestinationTable destination.
707 // Just pass the table name to getKeySchema and the MigrateDestinationTable constructor.
708 class WineTableMigration
extends AdvancedExampleMigration
{
709 public
function __construct() {
710 parent
::__construct();
711 $this->description
= 'Miscellaneous table data';
712 $this->softDependencies
= array('WineComment');
713 $table_name = 'migrate_example_wine_table_dest';
714 $this->map
= new
MigrateSQLMap($this->machineName
,
715 array('fooid' => array(
721 MigrateDestinationTable
::getKeySchema($table_name)
723 $query = db_select('migrate_example_wine_table_source', 't')
724 ->fields('t', array('fooid', 'field1', 'field2'));
725 $this->source
= new
MigrateSourceSQL($query);
726 $this->destination
= new
MigrateDestinationTable($table_name);
729 $this->addFieldMapping('drupal_text', 'field1');
730 $this->addFieldMapping('drupal_int', 'field2');
732 $this->addUnmigratedDestinations(array('recordid'));
736 class WineFinishMigration
extends MigrationBase
{
737 public
function __construct() {
738 parent
::__construct();
739 $this->description
= t('If auto_nodetitle is present and was previously enabled,
741 $this->dependencies
= array('WineComment');
743 public
function isComplete() {
744 if (module_exists('auto_nodetitle')) {
751 public
function import() {
752 if (!module_exists('auto_nodetitle')) {
753 if (WinePrepMigration
::$wasEnabled) {
754 module_enable(array('auto_nodetitle'));
755 $this->showMessage(t('Re-enabled auto_nodetitle module'), 'success');
758 $this->showMessage(t('auto_nodetitle was not originally enabled'), 'success');
762 $this->showMessage(t('Auto_nodetitle module already enabled'), 'success');
764 return Migration
::RESULT_COMPLETED
;
769 * TIP: This demonstrates a migration designed not to import new content, but
770 * to update existing content (in this case, revised wine ratings)
772 class WineUpdatesMigration
extends AdvancedExampleMigration
{
773 public
function __construct() {
774 parent
::__construct();
775 $this->description
= t('Update wine ratings');
776 $this->dependencies
= array('WineWine');
777 $this->softDependencies
= array('WineFinish');
779 $this->map
= new
MigrateSQLMap($this->machineName
,
785 'description' => 'Wine ID',
789 MigrateDestinationNode
::getKeySchema()
792 $query = db_select('migrate_example_wine_updates', 'w')
793 ->fields('w', array('wineid', 'rating'));
795 $this->source
= new
MigrateSourceSQL($query);
796 $this->destination
= new
MigrateDestinationNode('migrate_example_wine');
798 // Indicate we're updating existing data. The default, Migration::SOURCE, would
799 // cause existing nodes to be completely replaced by the source data. In this
800 // case, the existing node will be loaded and only the rating altered.
801 $this->systemOfRecord
= Migration
::DESTINATION
;
804 // The destination handler needs the nid to change - since the incoming data
805 // has a source id, not a nid, we need to apply the original wine migration
806 // mapping to populate the nid.
807 $this->addFieldMapping('nid', 'wineid')
808 ->sourceMigration('WineWine');
809 $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
811 // No unmapped source fields
813 // Unmapped destination fields
814 $this->addFieldMapping('uid');
815 $this->addFieldMapping('migrate_example_wine_varieties');
816 $this->addFieldMapping('migrate_example_wine_regions');
817 $this->addFieldMapping('migrate_example_wine_best_with');
818 $this->addFieldMapping('body');
819 $this->addFieldMapping('field_migrate_example_image');
820 $this->addFieldMapping('sticky');
821 $this->addFieldMapping('created');
822 $this->addFieldMapping('changed');
823 $this->addUnmigratedDestinations(array('is_new', 'status', 'promote',
824 'revision', 'language'));
828 class WineCommentUpdatesMigration
extends AdvancedExampleMigration
{
829 public
function __construct() {
830 parent
::__construct();
831 $this->description
= 'Update wine comments';
832 $this->dependencies
= array('WineComment');
833 $this->softDependencies
= array('WineUpdates');
834 $this->map
= new
MigrateSQLMap($this->machineName
,
835 array('commentid' => array(
841 MigrateDestinationComment
::getKeySchema()
843 $query = db_select('migrate_example_wine_comment_updates', 'wc')
844 ->fields('wc', array('commentid', 'subject'));
845 $this->source
= new
MigrateSourceSQL($query);
846 $this->destination
= new
MigrateDestinationComment('comment_node_migrate_example_wine');
847 $this->systemOfRecord
= Migration
::DESTINATION
;
850 $this->addFieldMapping('cid', 'commentid')
851 ->sourceMigration('WineComment');
852 $this->addFieldMapping('subject', 'subject');
854 // No unmapped source fields
856 // Unmapped destination fields
857 $this->addFieldMapping('name');
858 $this->addFieldMapping('mail');
859 $this->addFieldMapping('status');
860 $this->addFieldMapping('nid');
861 $this->addFieldMapping('uid');
862 $this->addFieldMapping('pid');
863 $this->addFieldMapping('comment_body');
864 $this->addFieldMapping('hostname');
865 $this->addFieldMapping('created');
866 $this->addFieldMapping('changed');
867 $this->addFieldMapping('homepage');
868 $this->addUnmigratedDestinations(array('thread', 'language'));
872 class WineVarietyUpdatesMigration
extends AdvancedExampleMigration
{
873 public
function __construct() {
874 parent
::__construct();
875 $this->description
= t('Migrate varieties from the source database to taxonomy terms');
876 $this->dependencies
= array('WineVariety');
877 $this->softDependencies
= array('WineUpdates');
878 $this->map
= new
MigrateSQLMap($this->machineName
,
880 'categoryid' => array('type' => 'int',
885 MigrateDestinationTerm
::getKeySchema()
888 $query = db_select('migrate_example_wine_variety_updates', 'wc')
889 ->fields('wc', array('categoryid', 'details'));
890 $this->source
= new
MigrateSourceSQL($query);
891 $this->destination
= new
MigrateDestinationTerm('migrate_example_wine_varieties');
892 $this->systemOfRecord
= Migration
::DESTINATION
;
895 $this->addFieldMapping('tid', 'categoryid')
896 ->sourceMigration('WineVariety');
897 $this->addFieldMapping('description', 'details');
899 // Unmapped source fields
901 // Unmapped destination fields
902 $this->addFieldMapping('name');
903 $this->addFieldMapping('parent');
904 $this->addFieldMapping('weight');
905 $this->addFieldMapping('format');
906 $this->addFieldMapping('parent_name')
907 ->issueGroup(t('DNM'));
912 class WineUserUpdatesMigration
extends AdvancedExampleMigration
{
913 public
function __construct() {
914 parent
::__construct();
915 $this->description
= t('Account updates');
916 $this->dependencies
= array('WineUser');
917 $this->softDependencies
= array('WineUpdates');
918 $this->map
= new
MigrateSQLMap($this->machineName
,
919 array('accountid' => array(
923 'description' => 'Account ID.'
926 MigrateDestinationUser
::getKeySchema()
928 $query = db_select('migrate_example_wine_account_updates', 'wa')
929 ->fields('wa', array('accountid', 'sex'));
930 $this->source
= new
MigrateSourceSQL($query);
931 $this->destination
= new
MigrateDestinationUser();
932 $this->systemOfRecord
= Migration
::DESTINATION
;
935 $this->addFieldMapping('uid', 'accountid')
936 ->sourceMigration('WineUser');
937 $this->addFieldMapping('field_migrate_example_gender', 'sex')
938 ->description(t('Map from M/F to 0/1 in prepare method'));
940 // Unmapped source fields
942 // Unmapped destination fields
943 $this->addFieldMapping('name');
944 $this->addFieldMapping('status');
945 $this->addFieldMapping('created');
946 $this->addFieldMapping('access');
947 $this->addFieldMapping('login');
948 $this->addFieldMapping('mail');
949 $this->addFieldMapping('pass');
950 $this->addFieldMapping('roles');
951 $this->addFieldMapping('signature');
952 $this->addFieldMapping('signature_format');
953 $this->addFieldMapping('init');
954 $this->addUnmigratedDestinations(array('theme', 'timezone', 'language', 'picture'));
957 public
function prepare(stdClass
$account, stdClass
$row) {
958 // Gender data comes in as M/F, needs to be saved as Male=0/Female=1
959 // TIP: Note that the Migration prepare method is called after all other
960 // prepare handlers. Most notably, the field handlers have had their way
961 // and created field arrays, so we have to save in the same format.
965 $account->field_migrate_example_gender
[LANGUAGE_NONE
][0]['value'] = 0;
969 $account->field_migrate_example_gender
[LANGUAGE_NONE
][0]['value'] = 1;
972 $account->field_migrate_example_gender
= NULL
;