Issue #737170 by mikeryan: Added support for field level callbacks
[project/migrate.git] / migrate_example / wine.inc
CommitLineData
79f72dcc 1<?php
79f72dcc
MR
2
3/**
4 * @file
5 * Advanced migration examples. These serve two purposes:
6 *
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.
10 *
11 */
12
13/**
14 * Abstract intermediate class holding common settings.
15 */
16abstract class AdvancedExampleMigration extends Migration {
e0c558fa
MR
17 public $basicFormat;
18
79f72dcc 19 public function __construct() {
79f72dcc 20 parent::__construct();
79f72dcc 21 $this->team = array(
2518dfe3
MR
22 new MigrateTeamMember('Jack Kramer', 'jkramer@example.com', t('Taster')),
23 new MigrateTeamMember('Linda Madison', 'lmadison@example.com', t('Winemaker')),
79f72dcc 24 );
79f72dcc 25 $this->issuePattern = 'http://drupal.org/node/:id:';
e0c558fa
MR
26
27 // A format of our own, for testing migration of formats
3b7ece73 28 $this->basicFormat = filter_format_load('migrate_example');
79f72dcc
MR
29 }
30}
31
32/**
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
4ab61127 36 * to disable auto_nodetitle while the migration steps run.
79f72dcc 37 */
20d432de 38class WinePrepMigration extends MigrationBase {
81da2cf8
MR
39 // Remember whether the auto_nodetitle was originally enabled, so we know whether
40 // to re-enable it
41 public static $wasEnabled = FALSE;
42
79f72dcc
MR
43 public function __construct() {
44 parent::__construct();
4ab61127 45 $this->description = t('If auto_nodetitle is present, disable it for the duration');
79f72dcc
MR
46 // TIP: Regular dependencies, besides enforcing (in the absence of --force)
47 // the run order of migrations, affect the sorting of migrations on display.
2518dfe3 48 // You can use soft dependencies to affect just the display order when the
79f72dcc
MR
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.
7a68c5aa 53 $this->softDependencies = array('BeerComment');
79f72dcc
MR
54 }
55 // Define isComplete(), returning a boolean, to indicate whether dependent
56 // migrations may proceed
57 public function isComplete() {
cdfe3ddb 58 // If Auto Node Title is disabled, other migrations are free to go
4ab61127 59 if (module_exists('auto_nodetitle')) {
79f72dcc
MR
60 return FALSE;
61 }
62 else {
63 return TRUE;
64 }
65 }
66 // Implement any action you want to occur during an import process in an
70380ea8 67 // import() method (alternatively, if you have an action which you want to
79f72dcc
MR
68 // run during rollbacks, define a rollback() method).
69 public function import() {
4ab61127 70 if (module_exists('auto_nodetitle')) {
81da2cf8 71 self::$wasEnabled = TRUE;
4ab61127 72 module_disable(array('auto_nodetitle'));
73 $this->showMessage(t('Disabled auto_nodetitle module'), 'success');
79f72dcc 74 }
2518dfe3 75 else {
81da2cf8 76 self::$wasEnabled = FALSE;
4ab61127 77 $this->showMessage(t('Auto_nodetitle is already disabled'), 'success');
2518dfe3
MR
78 }
79 // Must return one of the MigrationBase RESULT constants
80 return MigrationBase::RESULT_COMPLETED;
79f72dcc
MR
81 }
82}
83
2518dfe3 84// The term migrations are very similar - implement the commonalities here
20d432de 85abstract class WineTermMigration extends AdvancedExampleMigration {
2518dfe3 86 public function __construct($type, $vocabulary_name, $description) {
79f72dcc 87 parent::__construct();
2518dfe3 88 $this->description = $description;
7a68c5aa 89 $this->dependencies = array('WinePrep');
79f72dcc
MR
90 $this->map = new MigrateSQLMap($this->machineName,
91 array(
2518dfe3
MR
92 'categoryid' => array('type' => 'int',
93 'unsigned' => TRUE,
94 'not null' => TRUE,
95 )
79f72dcc
MR
96 ),
97 MigrateDestinationTerm::getKeySchema()
98 );
99
2518dfe3 100 $query = db_select('migrate_example_wine_categories', 'wc')
e0c558fa 101 ->fields('wc', array('categoryid', 'name', 'details', 'category_parent', 'ordering'))
2518dfe3 102 ->condition('type', $type)
79f72dcc 103 // This sort assures that parents are saved before children.
2518dfe3 104 ->orderBy('category_parent', 'ASC');
9fadaa93 105 $this->source = new MigrateSourceSQL($query);
2518dfe3
MR
106 $this->destination = new MigrateDestinationTerm($vocabulary_name);
107
108 // Mapped fields
109 $this->addFieldMapping('name', 'name');
79f72dcc 110 $this->addFieldMapping('description', 'details');
2518dfe3 111 $this->addFieldMapping('parent', 'category_parent')
7a68c5aa 112 ->sourceMigration($this->getMachineName());
e0c558fa
MR
113 $this->addFieldMapping('weight', 'ordering');
114 $this->addFieldMapping('format')
c67c70af 115 ->defaultValue($this->basicFormat->format);
2518dfe3
MR
116
117 // Unmapped source fields
118
119 // Unmapped destination fields
2518dfe3 120 $this->addFieldMapping('parent_name')
79f72dcc
MR
121 ->issueGroup(t('DNM'));
122 }
123}
124
20d432de 125class WineVarietyMigration extends WineTermMigration {
2518dfe3
MR
126 public function __construct() {
127 parent::__construct('variety', 'migrate_example_wine_varieties',
128 t('Migrate varieties from the source database to taxonomy terms'));
129 }
130}
131
20d432de 132class WineRegionMigration extends WineTermMigration {
2518dfe3
MR
133 public function __construct() {
134 parent::__construct('region', 'migrate_example_wine_regions',
135 t('Migrate regions from the source database to taxonomy terms'));
136 }
137}
138
20d432de 139class WineBestWithMigration extends WineTermMigration {
2518dfe3
MR
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'));
143 }
144}
145
15c0ee92
MR
146/**
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.
150 */
151class 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(
158 'type' => 'int',
159 'unsigned' => TRUE,
160 'not null' => TRUE,
161 'description' => 'Image ID.'
162 )
163 ),
164 MigrateDestinationFile::getKeySchema()
165 );
166 $query = db_select('migrate_example_wine_files', 'wf')
cc029251
MR
167 ->fields('wf', array('imageid', 'url'))
168 ->isNull('wineid');
15c0ee92
MR
169 $this->source = new MigrateSourceSQL($query);
170
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));
174
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'));
179 }
180}
181
c5ee5d7b
MR
182class 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');
187
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
190 $fields = array(
191 'name' => t('Position name'),
192 );
193
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,
197 array(
198 'sourceid' => array(
199 'type' => 'int',
200 'unsigned' => TRUE,
201 'not null' => TRUE,
202 )
203 ),
204 MigrateDestinationRole::getKeySchema()
205 );
206
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
213
214 $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
215 $this->source = new MigrateSourceMultiItems($items_class, $fields);
216
217 $this->destination = new MigrateDestinationRole();
218
219 $this->addFieldMapping('name', 'name')
220 ->xpath('name');
221 $this->addUnmigratedDestinations(array('weight'));
222 }
223}
224
20d432de 225class WineUserMigration extends AdvancedExampleMigration {
79f72dcc
MR
226 public function __construct() {
227 parent::__construct();
228 $this->description = t('Wine Drinkers of the world');
c5ee5d7b 229 $this->dependencies = array('WinePrep', 'WineFile', 'WineRole');
79f72dcc
MR
230 $this->map = new MigrateSQLMap($this->machineName,
231 array('accountid' => array(
232 'type' => 'int',
2518dfe3 233 'unsigned' => TRUE,
79f72dcc
MR
234 'not null' => TRUE,
235 'description' => 'Account ID.'
236 )
237 ),
238 MigrateDestinationUser::getKeySchema()
239 );
2518dfe3 240 $query = db_select('migrate_example_wine_account', 'wa')
e0c558fa
MR
241 ->fields('wa', array('accountid', 'status', 'posted', 'name',
242 'password', 'mail', 'last_access', 'last_login',
c5ee5d7b 243 'original_mail', 'sig', 'sex', 'imageid', 'positions'));
9fadaa93 244 $this->source = new MigrateSourceSQL($query);
79f72dcc
MR
245 $this->destination = new MigrateDestinationUser();
246
247 // Mapped fields
552d9ae3 248 $this->addSimpleMappings(array('name', 'status', 'mail'));
79f72dcc
MR
249 $this->addFieldMapping('created', 'posted')
250 ->description('See prepare method');
e0c558fa
MR
251 $this->addFieldMapping('access', 'last_access')
252 ->description('See prepare method');
253 $this->addFieldMapping('login', 'last_login')
254 ->description('See prepare method');
79f72dcc 255 $this->addFieldMapping('pass', 'password');
c5ee5d7b
MR
256 $this->addFieldMapping('roles', 'positions')
257 ->separator(',')
258 ->sourceMigration('WineRole');
e0c558fa
MR
259 $this->addFieldMapping('signature', 'sig');
260 $this->addFieldMapping('signature_format')
c67c70af 261 ->defaultValue($this->basicFormat->format);
e0c558fa
MR
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'));
15c0ee92
MR
265 $this->addFieldMapping('picture', 'imageid')
266 ->sourceMigration('WineFile');
79f72dcc
MR
267
268 // Unmapped source fields
79f72dcc
MR
269
270 // Unmapped destination fields
15c0ee92 271 $this->addUnmigratedDestinations(array('theme', 'timezone', 'language'));
79f72dcc
MR
272 }
273
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);
e0c558fa
MR
279 $account->access = strtotime($account->access);
280 $account->login = strtotime($account->login);
281
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.
286 switch ($row->sex) {
287 case 'm':
288 case 'M':
289 $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
290 break;
291 case 'f':
292 case 'F':
293 $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
294 break;
295 default:
296 unset($account->field_migrate_example_gender);
297 break;
298 }
79f72dcc
MR
299 }
300}
301
20d432de 302class WineProducerMigration extends AdvancedExampleMigration {
2518dfe3
MR
303 public function __construct() {
304 parent::__construct();
305 $this->description = t('Wine producers of the world');
7a68c5aa 306 $this->dependencies = array('WineRegion', 'WineUser');
2518dfe3
MR
307
308 $this->map = new MigrateSQLMap($this->machineName,
309 array(
310 'producerid' => array(
311 'type' => 'int',
312 'unsigned' => TRUE,
313 'not null' => TRUE,
314 'alias' => 'p',
315 )
316 ),
317 MigrateDestinationNode::getKeySchema()
318 );
319
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');
326
9fadaa93 327 $this->source = new MigrateSourceSQL($query);
2518dfe3
MR
328 $this->destination = new MigrateDestinationNode('migrate_example_producer');
329
330 // Mapped fields
331 $this->addFieldMapping('title', 'name')
332 ->description(t('Mapping producer name in source to node title'));
333 $this->addFieldMapping('uid', 'accountid')
7a68c5aa 334 ->sourceMigration('WineUser')
2518dfe3
MR
335 ->defaultValue(1);
336 $this->addFieldMapping('migrate_example_wine_regions', 'region')
7a68c5aa 337 ->sourceMigration('WineRegion')
990a4601 338 ->arguments(array('source_type' => 'tid'));
2518dfe3
MR
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')
344 ->defaultValue(0);
345
346 // No unmapped source fields
347
348 // Unmapped destination fields
552d9ae3
MR
349 $this->addUnmigratedDestinations(array('is_new', 'name', 'created', 'changed',
350 'status', 'promote', 'revision', 'language'));
2518dfe3
MR
351 }
352}
353
990a4601
MR
354/**
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.
358 *
990a4601
MR
359 * Note that, if basing a migration on an XML source, you need to derive it
360 * from XMLMigration instead of Migration.
361 */
20d432de 362class WineProducerXMLMigration extends XMLMigration {
990a4601
MR
363 public function __construct() {
364 parent::__construct();
365 $this->description = t('XML feed of wine producers of the world');
7a68c5aa 366 $this->dependencies = array('WineRegion', 'WineUser');
990a4601 367
9023a77e
MR
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
370 $fields = array(
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'),
375 );
376
990a4601 377 // The source ID here is the one retrieved from the XML listing file, and
9023a77e 378 // used to identify the specific item's file
990a4601
MR
379 $this->map = new MigrateSQLMap($this->machineName,
380 array(
381 'sourceid' => array(
382 'type' => 'varchar',
383 'length' => 4,
384 'not null' => TRUE,
385 )
386 ),
387 MigrateDestinationNode::getKeySchema()
388 );
389
b14ba196 390 // This can also be an URL instead of a file path.
391 $xml_folder = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migrate_example') . '/xml/';
9023a77e
MR
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';
396
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
401 // with a JSON item.
9fadaa93 402 $this->source = new MigrateSourceList(new MigrateListXML($list_url),
9023a77e 403 new MigrateItemXML($item_url), $fields);
990a4601
MR
404
405 $this->destination = new MigrateDestinationNode('migrate_example_producer');
406
70380ea8 407 // TIP: Note that for XML sources, in addition to the source field passed to
990a4601
MR
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.
9023a77e 411 $this->addFieldMapping('title', 'name')
990a4601
MR
412 ->xpath('/producer/name');
413 $this->addFieldMapping('uid', 'authorid')
414 ->xpath('/producer/authorid')
7a68c5aa 415 ->sourceMigration('WineUser')
990a4601
MR
416 ->defaultValue(1);
417 $this->addFieldMapping('migrate_example_wine_regions', 'region')
418 ->xpath('/producer/region');
419 $this->addFieldMapping('body', 'description')
420 ->xpath('/producer/description');
421 }
422}
423
8e570661
MR
424/**
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.
429 *
430 * Note that, if basing a migration on an XML source, you need to derive it
431 * from XMLMigration instead of Migration.
432 */
433class 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');
438
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
441 $fields = array(
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'),
446 );
447
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,
451 array(
452 'sourceid' => array(
453 'type' => 'varchar',
454 'length' => 4,
455 'not null' => TRUE,
456 )
457 ),
458 MigrateDestinationNode::getKeySchema()
459 );
460
461
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';
465
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.
471
472 $item_xpath = '/producers/producer'; // relative to document
473
474 $item_ID_xpath = 'sourceid'; // relative to item_xpath and gets assembled
475 // into full path /producers/producer/sourceid
476
477 $items_class = new MigrateItemsXML($items_url, $item_xpath, $item_ID_xpath);
478 $this->source = new MigrateSourceMultiItems($items_class, $fields);
479
480 $this->destination = new MigrateDestinationNode('migrate_example_producer');
481
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')
490 ->xpath('name');
491 $this->addFieldMapping('uid', 'authorid')
492 ->xpath('authorid')
493 ->sourceMigration('WineUser')
494 ->defaultValue(1);
495 $this->addFieldMapping('migrate_example_wine_regions', 'region')
496 ->xpath('region');
497 $this->addFieldMapping('body', 'description')
498 ->xpath('description');
499 }
500}
501
2518dfe3 502// TODO: Add node_reference field pointing to producer
20d432de 503class WineWineMigration extends AdvancedExampleMigration {
79f72dcc
MR
504 public function __construct() {
505 parent::__construct();
506 $this->description = t('Wines of the world');
7a68c5aa
MR
507 $this->dependencies = array('WineVariety', 'WineRegion',
508 'WineBestWith', 'WineUser', 'WineProducer');
79f72dcc
MR
509
510 $this->map = new MigrateSQLMap($this->machineName,
511 array(
2518dfe3 512 'wineid' => array(
79f72dcc 513 'type' => 'int',
2518dfe3 514 'unsigned' => TRUE,
79f72dcc 515 'not null' => TRUE,
2518dfe3
MR
516 'description' => 'Wine ID',
517 'alias' => 'w',
79f72dcc
MR
518 )
519 ),
520 MigrateDestinationNode::getKeySchema()
521 );
522
2518dfe3 523 $query = db_select('migrate_example_wine', 'w')
e0c558fa 524 ->fields('w', array('wineid', 'name', 'body', 'excerpt', 'accountid',
35e060d3 525 'posted', 'last_changed', 'variety', 'region', 'rating'));
2518dfe3
MR
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'");
79f72dcc 530 // Gives a single comma-separated list of related terms
c67c70af 531 $query->groupBy('w.wineid');
2518dfe3
MR
532 $query->addExpression('GROUP_CONCAT(bw.categoryid)', 'best_with');
533
534 $count_query = db_select('migrate_example_wine', 'w');
535 $count_query->addExpression('COUNT(wineid)', 'cnt');
79f72dcc 536
68ffadc1
MR
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?'),
35e060d3 545 'images' => t('Images attached to this wine; populated in prepareRow()'),
68ffadc1
MR
546 );
547
c67c70af
MR
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
557 );
558 // Note that it is important to process rows in the order of the highwater mark
559 $query->orderBy('last_changed');
560
9fadaa93 561 $this->source = new MigrateSourceSQL($query, $source_fields, $count_query);
79f72dcc
MR
562 $this->destination = new MigrateDestinationNode('migrate_example_wine');
563
564 // Mapped fields
565 $this->addFieldMapping('title', 'name')
566 ->description(t('Mapping wine name in source to node title'));
79f72dcc 567 $this->addFieldMapping('uid', 'accountid')
7a68c5aa 568 ->sourceMigration('WineUser')
79f72dcc 569 ->defaultValue(1);
e0c558fa
MR
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
2518dfe3 574 $this->addFieldMapping('migrate_example_wine_varieties', 'variety')
7a68c5aa 575 ->sourceMigration('WineVariety')
e0c558fa 576 ->arguments(array('source_type' => 'tid'));
2518dfe3 577 $this->addFieldMapping('migrate_example_wine_regions', 'region')
7a68c5aa 578 ->sourceMigration('WineRegion')
e0c558fa 579 ->arguments(array('source_type' => 'tid'));
2518dfe3
MR
580 $this->addFieldMapping('migrate_example_wine_best_with', 'best_with')
581 ->separator(',')
7a68c5aa 582 ->sourceMigration('WineBestWith')
e0c558fa 583 ->arguments(array('source_type' => 'tid'));
a934982b 584 $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
70380ea8 585 $this->addFieldMapping('field_migrate_example_top_vintag', 'best_vintages');
1e61b858
MR
586
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.
79f72dcc
MR
593 $arguments = MigrateTextFieldHandler::arguments(array('source_field' => 'excerpt'));
594 $this->addFieldMapping('body', 'body')
1e61b858
MR
595 ->arguments($arguments)
596 ->callbacks(array($this, 'addTitlePrefix'), 'drupal_strtoupper');
79f72dcc 597 $this->addFieldMapping(NULL, 'excerpt');
35e060d3 598 // We will get the image data from a related table in prepareRow()
15c0ee92 599 $arguments = MigrateFileFieldHandler::arguments(NULL, 'file_copy', FILE_EXISTS_REPLACE);
35e060d3 600 $this->addFieldMapping('field_migrate_example_image', 'images')
79f72dcc
MR
601 ->arguments($arguments);
602 $this->addFieldMapping('sticky')
603 ->defaultValue(0);
e0c558fa
MR
604 // These are already UNIX timestamps, so just pass through
605 $this->addFieldMapping('created', 'posted');
606 $this->addFieldMapping('changed', 'last_changed');
79f72dcc
MR
607
608 // No unmapped source fields
609
610 // Unmapped destination fields
552d9ae3
MR
611 $this->addUnmigratedDestinations(array('is_new', 'status', 'promote',
612 'revision', 'language'));
79f72dcc 613 }
70380ea8 614
1e61b858
MR
615 protected function addTitlePrefix($source_title) {
616 return t('review: ') . $source_title;
617 }
618
70380ea8
MR
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)
629 ->execute();
630 foreach ($result as $row) {
631 $current_row->best_vintages[] = $row->vintage;
632 }
35e060d3
MR
633
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)
640 ->execute();
641 $current_row->images = array();
642 foreach ($result as $row) {
643 $image_data = array(
644 'path' => $row->url,
645 'alt' => $row->image_alt,
646 'title' => $row->image_title,
647 );
648 $current_row->images[] = drupal_json_encode($image_data);
649 }
650
70380ea8 651 // We could also have used this function to decide to skip a row, in cases
35e060d3 652 // where that couldn't easily be done through the original query. Simply
70380ea8
MR
653 // return FALSE in such cases.
654 return TRUE;
655 }
79f72dcc
MR
656}
657
20d432de 658class WineCommentMigration extends AdvancedExampleMigration {
79f72dcc
MR
659 public function __construct() {
660 parent::__construct();
661 $this->description = 'Comments about wines';
7a68c5aa 662 $this->dependencies = array('WineUser', 'WineWine');
79f72dcc 663 $this->map = new MigrateSQLMap($this->machineName,
2518dfe3 664 array('commentid' => array(
79f72dcc 665 'type' => 'int',
2518dfe3 666 'unsigned' => TRUE,
79f72dcc
MR
667 'not null' => TRUE,
668 )
669 ),
670 MigrateDestinationComment::getKeySchema()
671 );
2518dfe3 672 $query = db_select('migrate_example_wine_comment', 'wc')
4b52550a
MR
673 ->fields('wc', array('commentid', 'comment_parent', 'name', 'mail',
674 'accountid', 'body', 'wineid', 'subject', 'commenthost', 'userpage',
675 'posted', 'lastchanged'))
2518dfe3 676 ->orderBy('comment_parent');
9fadaa93 677 $this->source = new MigrateSourceSQL($query);
79f72dcc
MR
678 $this->destination = new MigrateDestinationComment('comment_node_migrate_example_wine');
679
680 // Mapped fields
552d9ae3 681 $this->addSimpleMappings(array('name', 'subject', 'mail'));
79f72dcc
MR
682 $this->addFieldMapping('status')
683 ->defaultValue(COMMENT_PUBLISHED);
4b52550a 684 $this->addFieldMapping('nid', 'wineid')
7a68c5aa 685 ->sourceMigration('WineWine');
79f72dcc 686 $this->addFieldMapping('uid', 'accountid')
7a68c5aa 687 ->sourceMigration('WineUser')
79f72dcc 688 ->defaultValue(0);
2518dfe3 689 $this->addFieldMapping('pid', 'comment_parent')
7a68c5aa 690 ->sourceMigration('WineComment')
2518dfe3 691 ->description('Parent comment');
79f72dcc 692 $this->addFieldMapping('comment_body', 'body');
4b52550a
MR
693 $this->addFieldMapping('hostname', 'commenthost');
694 $this->addFieldMapping('created', 'posted');
695 $this->addFieldMapping('changed', 'lastchanged');
696 $this->addFieldMapping('homepage', 'userpage');
79f72dcc
MR
697
698 // No unmapped source fields
699
700 // Unmapped destination fields
552d9ae3 701 $this->addUnmigratedDestinations(array('thread', 'language'));
79f72dcc
MR
702 }
703}
704
5ba25ed4
MR
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.
708class 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(
716 'type' => 'int',
717 'unsigned' => TRUE,
718 'not null' => TRUE,
719 )
720 ),
721 MigrateDestinationTable::getKeySchema($table_name)
722 );
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);
727
728 // Mapped fields
729 $this->addFieldMapping('drupal_text', 'field1');
730 $this->addFieldMapping('drupal_int', 'field2');
731
732 $this->addUnmigratedDestinations(array('recordid'));
733 }
734}
735
20d432de 736class WineFinishMigration extends MigrationBase {
79f72dcc
MR
737 public function __construct() {
738 parent::__construct();
4ab61127 739 $this->description = t('If auto_nodetitle is present and was previously enabled,
79f72dcc 740 re-enable it');
7a68c5aa 741 $this->dependencies = array('WineComment');
79f72dcc
MR
742 }
743 public function isComplete() {
4ab61127 744 if (module_exists('auto_nodetitle')) {
79f72dcc
MR
745 return TRUE;
746 }
747 else {
748 return FALSE;
749 }
750 }
751 public function import() {
4ab61127 752 if (!module_exists('auto_nodetitle')) {
81da2cf8
MR
753 if (WinePrepMigration::$wasEnabled) {
754 module_enable(array('auto_nodetitle'));
755 $this->showMessage(t('Re-enabled auto_nodetitle module'), 'success');
756 }
757 else {
758 $this->showMessage(t('auto_nodetitle was not originally enabled'), 'success');
759 }
2518dfe3
MR
760 }
761 else {
81da2cf8 762 $this->showMessage(t('Auto_nodetitle module already enabled'), 'success');
79f72dcc 763 }
79f72dcc
MR
764 return Migration::RESULT_COMPLETED;
765 }
766}
176e6c7c
MR
767
768/**
70380ea8 769 * TIP: This demonstrates a migration designed not to import new content, but
176e6c7c
MR
770 * to update existing content (in this case, revised wine ratings)
771 */
20d432de 772class WineUpdatesMigration extends AdvancedExampleMigration {
176e6c7c
MR
773 public function __construct() {
774 parent::__construct();
775 $this->description = t('Update wine ratings');
7a68c5aa
MR
776 $this->dependencies = array('WineWine');
777 $this->softDependencies = array('WineFinish');
176e6c7c
MR
778
779 $this->map = new MigrateSQLMap($this->machineName,
780 array(
781 'wineid' => array(
782 'type' => 'int',
783 'unsigned' => TRUE,
784 'not null' => TRUE,
785 'description' => 'Wine ID',
786 'alias' => 'w',
787 )
788 ),
789 MigrateDestinationNode::getKeySchema()
790 );
791
792 $query = db_select('migrate_example_wine_updates', 'w')
793 ->fields('w', array('wineid', 'rating'));
794
9fadaa93 795 $this->source = new MigrateSourceSQL($query);
176e6c7c
MR
796 $this->destination = new MigrateDestinationNode('migrate_example_wine');
797
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;
802
803 // Mapped fields
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')
7a68c5aa 808 ->sourceMigration('WineWine');
176e6c7c
MR
809 $this->addFieldMapping('field_migrate_example_wine_ratin', 'rating');
810
811 // No unmapped source fields
812
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');
552d9ae3
MR
823 $this->addUnmigratedDestinations(array('is_new', 'status', 'promote',
824 'revision', 'language'));
176e6c7c
MR
825 }
826}
827
20d432de 828class WineCommentUpdatesMigration extends AdvancedExampleMigration {
176e6c7c
MR
829 public function __construct() {
830 parent::__construct();
831 $this->description = 'Update wine comments';
7a68c5aa
MR
832 $this->dependencies = array('WineComment');
833 $this->softDependencies = array('WineUpdates');
176e6c7c
MR
834 $this->map = new MigrateSQLMap($this->machineName,
835 array('commentid' => array(
836 'type' => 'int',
837 'unsigned' => TRUE,
838 'not null' => TRUE,
839 )
840 ),
841 MigrateDestinationComment::getKeySchema()
842 );
843 $query = db_select('migrate_example_wine_comment_updates', 'wc')
844 ->fields('wc', array('commentid', 'subject'));
9fadaa93 845 $this->source = new MigrateSourceSQL($query);
176e6c7c
MR
846 $this->destination = new MigrateDestinationComment('comment_node_migrate_example_wine');
847 $this->systemOfRecord = Migration::DESTINATION;
848
849 // Mapped fields
850 $this->addFieldMapping('cid', 'commentid')
7a68c5aa 851 ->sourceMigration('WineComment');
176e6c7c
MR
852 $this->addFieldMapping('subject', 'subject');
853
854 // No unmapped source fields
855
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');
552d9ae3 868 $this->addUnmigratedDestinations(array('thread', 'language'));
176e6c7c
MR
869 }
870}
871
20d432de 872class WineVarietyUpdatesMigration extends AdvancedExampleMigration {
176e6c7c
MR
873 public function __construct() {
874 parent::__construct();
875 $this->description = t('Migrate varieties from the source database to taxonomy terms');
7a68c5aa
MR
876 $this->dependencies = array('WineVariety');
877 $this->softDependencies = array('WineUpdates');
176e6c7c
MR
878 $this->map = new MigrateSQLMap($this->machineName,
879 array(
880 'categoryid' => array('type' => 'int',
881 'unsigned' => TRUE,
882 'not null' => TRUE,
883 )
884 ),
885 MigrateDestinationTerm::getKeySchema()
886 );
887
888 $query = db_select('migrate_example_wine_variety_updates', 'wc')
889 ->fields('wc', array('categoryid', 'details'));
9fadaa93 890 $this->source = new MigrateSourceSQL($query);
176e6c7c
MR
891 $this->destination = new MigrateDestinationTerm('migrate_example_wine_varieties');
892 $this->systemOfRecord = Migration::DESTINATION;
893
894 // Mapped fields
895 $this->addFieldMapping('tid', 'categoryid')
7a68c5aa 896 ->sourceMigration('WineVariety');
176e6c7c
MR
897 $this->addFieldMapping('description', 'details');
898
899 // Unmapped source fields
900
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'));
908 }
909}
910
911
20d432de 912class WineUserUpdatesMigration extends AdvancedExampleMigration {
176e6c7c
MR
913 public function __construct() {
914 parent::__construct();
915 $this->description = t('Account updates');
7a68c5aa
MR
916 $this->dependencies = array('WineUser');
917 $this->softDependencies = array('WineUpdates');
176e6c7c
MR
918 $this->map = new MigrateSQLMap($this->machineName,
919 array('accountid' => array(
920 'type' => 'int',
921 'unsigned' => TRUE,
922 'not null' => TRUE,
923 'description' => 'Account ID.'
924 )
925 ),
926 MigrateDestinationUser::getKeySchema()
927 );
928 $query = db_select('migrate_example_wine_account_updates', 'wa')
929 ->fields('wa', array('accountid', 'sex'));
9fadaa93 930 $this->source = new MigrateSourceSQL($query);
176e6c7c
MR
931 $this->destination = new MigrateDestinationUser();
932 $this->systemOfRecord = Migration::DESTINATION;
933
934 // Mapped fields
935 $this->addFieldMapping('uid', 'accountid')
7a68c5aa 936 ->sourceMigration('WineUser');
176e6c7c
MR
937 $this->addFieldMapping('field_migrate_example_gender', 'sex')
938 ->description(t('Map from M/F to 0/1 in prepare method'));
939
940 // Unmapped source fields
941
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');
552d9ae3 954 $this->addUnmigratedDestinations(array('theme', 'timezone', 'language', 'picture'));
176e6c7c
MR
955 }
956
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.
962 switch ($row->sex) {
963 case 'm':
964 case 'M':
965 $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 0;
966 break;
967 case 'f':
968 case 'F':
969 $account->field_migrate_example_gender[LANGUAGE_NONE][0]['value'] = 1;
970 break;
971 default:
73658591 972 $account->field_migrate_example_gender = NULL;
176e6c7c
MR
973 break;
974 }
975 }
976}