#781088 by mradcliffe and KarenS, initial work to get D6 to D7 field migration code...
[project/cck.git] / modules / content_migrate / content_migrate.module
1 <?php
2 // $Id$
3 /**
4 * @file
5 * Code For D6 to D7 field update.
6 *
7 * Modules can implement hook_content_migrate_field_alter()
8 * and hook_content_migrate_instance_alter()
9 */
10 define('CONTENT_DB_STORAGE_PER_FIELD', 0);
11 define('CONTENT_DB_STORAGE_PER_CONTENT_TYPE', 1);
12
13 /**
14 * Implements hook_menu().
15 */
16 function content_migrate_menu() {
17 // Demo page.
18 $items['admin/structure/content_migrate'] = array(
19 'title' => 'Migrate fields',
20 'description' => 'Migrate field settings and data from the Drupal 6 version to the Drupal 7 version.',
21 'page callback' => 'drupal_get_form',
22 'page arguments' => array('content_migrate_select'),
23 'access arguments' => array('administer content types'),
24 'file' => 'content_migrate.admin.inc',
25 );
26
27 return $items;
28 }
29
30 /**
31 * Create a D7-style field array from data stored
32 * in the D6 content field tables.
33 *
34 * @param $field_name
35 * Optionally request only a specific field name.
36 */
37 function content_migrate_get_field_values($field_name = NULL) {
38 $field_values = &drupal_static(__FUNCTION__);
39 if (!is_array($field_values)) {
40 $field_values = array();
41 }
42 if (empty($field_values) && db_table_exists('content_node_field')) {
43 $field_values = array();
44 $query = db_select('content_node_field', 'nf', array('fetch' => PDO::FETCH_ASSOC));
45 $node_instance_alias = $query->join('content_node_field_instance', 'ni', 'ni.field_name=nf.field_name');
46 $result = $query
47 ->fields($node_instance_alias, array('widget_type'))
48 ->fields('nf')
49 ->execute();
50
51 foreach ($result as $row) {
52 $field_value = $row;
53
54 // All Drupal 6 fields were attached to nodes.
55 $field_value['entity_types'] = array('node');
56
57 $field_value['cardinality'] = $field_value['multiple'] != 1 ? $field_value['multiple'] : FIELD_CARDINALITY_UNLIMITED;
58
59 // We need column information for the old table.
60 $field_value['columns'] = unserialize($field_value['db_columns']);
61
62 // Field settings.
63 $default_settings = field_info_field_settings($row['type']);
64 $field_value['settings'] = array_merge($default_settings, unserialize($field_value['global_settings']));
65
66 unset($field_value['multiple'], $field_value['global_settings'], $field_value['required'], $field_value['db_columns']);
67
68 // Let modules change these values.
69 drupal_alter('content_migrate_field', $field_value);
70
71 // We left the widget type in the $field_value so modules
72 // could adjust the field based on that information.
73 // Needed so optionwidgets can change the field type
74 // from text or numeric to list.
75 unset($field_value['widget_type'], $field_value['allowed_values_php']);
76
77 // We retain $field_value['columns'] and $field_value['db_storage']
78 // even though they are not used or different in D7
79 // so we can find the old table information.
80
81 // Add field definiation to $field_values array.
82 $field_values[$field_value['field_name']] = $field_value;
83 }
84 }
85 if (!empty($field_name)) {
86 return $field_values[$field_name];
87 }
88 return $field_values;
89 }
90
91 /**
92 * Create a D7-style instance array from data stored
93 * in the D6 content field tables.
94 *
95 * @param $bundle
96 * Optionally request only instances of a specific bundle.
97 * @param $field_name
98 * Optionally request only instances of a specific field_name.
99 */
100 function content_migrate_get_instance_values($bundle = NULL, $field_name = NULL) {
101 $instance_values = &drupal_static(__FUNCTION__);
102
103 if (empty($instance_values) && db_table_exists('content_node_field_instance')) {
104 $instance_values = array();
105 $query = db_select('content_node_field_instance', 'ni', array('fetch' => PDO::FETCH_ASSOC));
106 $node_field_alias = $query->join('content_node_field', 'nf', 'ni.field_name=nf.field_name');
107 $result = $query
108 ->fields('ni')
109 ->fields($node_field_alias, array('required'))
110 ->orderBy('label', 'ASC')
111 ->execute();
112
113 foreach ($result as $row) {
114 $instance_value = $row;
115
116 // The instance has the same module as the field,
117 // not the widget. May have been altered
118 // from the original values, so get the altered
119 // field values.
120 $field_value = content_migrate_get_field_values($row['field_name']);
121 $instance_value['module'] = $field_value['module'];
122
123 // All Drupal 6 instances were attached to nodes.
124 $instance_value['entity_type'] = 'node';
125
126 // Unserialize arrays.
127 foreach (array('widget_settings', 'display_settings', 'global_settings') as $key) {
128 $instance_value[$key] = (!empty($instance_value[$key])) ? (array) unserialize($instance_value[$key]) : array();
129 }
130
131 // Build instance values.
132 $instance_value['bundle'] = $instance_value['type_name'];
133 $instance_value['active'] = $instance_value['widget_active'];
134 $instance_value['default_value'] = $instance_value['widget_settings']['default_value'];
135
136 // Core does not support this, but retain it so
137 // another module can do something with it
138 // in drupal_alter.
139 if (isset($instance_value['widget_settings']['default_value_php'])) {
140 $instance_value['settings']['default_value_php'] = $instance_value['widget_settings']['default_value_php'];
141 }
142
143 // Build widget values.
144 $instance_value['widget'] = array();
145
146 // TODO Some widget types have been renamed in D7.
147 $instance_value['widget']['type'] = $instance_value['widget_type'];
148 $instance_value['widget']['weight'] = $instance_value['weight'];
149 $instance_value['widget']['module'] = $instance_value['widget_module'];
150
151 $default_settings = field_info_widget_settings($field_value['type']);
152 $instance_value['widget']['settings'] = array_merge($default_settings, $instance_value['widget_settings']);
153
154 // Build display values.
155 $instance_value['display'] = $instance_value['display_settings'];
156 $label = $instance_value['display_settings']['label'];
157 foreach ($instance_value['display_settings'] as $context => $settings) {
158
159 // @TODO Multigroup fields have some unexpected elements, we need to work out what should happen to them.
160 if ($context == 'parent' || $context == 'weight') {
161 continue;
162 }
163 $instance_value['display'][$context]['label'] = $label['format'];
164
165 // The format used in D6 may not match the formatter in D7.
166 // Fix it using drupal_alter().
167 $instance_value['display'][$context]['type'] = $settings['format'];
168 $instance_value['display'][$context]['settings'] = field_info_formatter_settings($settings['format']);
169
170 }
171
172 // Unset unneeded values.
173 unset($instance_value['type_name'], $instance_value['global_settings'], $instance_value['widget_settings'], $instance_value['display_settings'], $instance_value['widget_module'], $instance_value['widget_active']);
174
175 // Unset some values that don't exist on all fields.
176 if (isset($instance_value['widget']['settings']['default_value'])) unset($instance_value['widget']['settings']['default_value']);
177 if (isset($instance_value['widget']['settings']['default_value_php'])) unset($instance_value['widget']['settings']['default_value_php']);
178
179 // Let modules change these values.
180 drupal_alter('content_migrate_instance', $instance_value);
181
182 // Get rid of this value once CCK or some other module has handled it.
183 if (isset($instance_value['settings']['default_value_php'])) unset($instance_value['settings']['default_value_php']);
184
185 // Add instance information to instance array.
186 $instance_values['instances'][$instance_value['bundle']][$instance_value['field_name']] = $instance_value;
187 $instance_values['fields'][$instance_value['field_name']][$instance_value['bundle']] = $instance_value;
188
189 }
190 }
191 if (!empty($bundle)) {
192 if (!empty($field_name)) {
193 return $instance_values['instances'][$bundle][$field_name];
194 }
195 else {
196 return $instance_values['instances'][$bundle];
197 }
198 }
199 elseif (!empty($field_name)) {
200 return $instance_values['fields'][$field_name];
201 }
202 return $instance_values;
203 }
204
205 /**
206 * Helper function for finding the table name
207 * used to store the D6 field data.
208 *
209 * @param $field_value
210 * @param $instance_value
211 */
212 function content_migrate_old_table($field_value, $instance_value) {
213 $storage = content_migrate_storage_type($field_value);
214 switch ($storage) {
215 case CONTENT_DB_STORAGE_PER_CONTENT_TYPE :
216 $name = $instance_value['bundle'];
217 return "content_type_$name";
218 case CONTENT_DB_STORAGE_PER_FIELD :
219 $name = $field_value['field_name'];
220 return "content_$name";
221 }
222 }
223
224 /**
225 * Helper function for finding the type of table
226 * used for storing the D6 field data.
227 *
228 * @param $field_value
229 * @param $instance_value
230 */
231 function content_migrate_storage_type($field_value) {
232 $storage = CONTENT_DB_STORAGE_PER_CONTENT_TYPE;
233 if (isset($field_value['db_storage'])) {
234 return $field_value['db_storage'];
235 }
236 elseif ($field_value['cardinality'] > 0 || $field_value['cardinality'] == FIELD_CARDINALITY_UNLIMITED) {
237 $storage = CONTENT_DB_STORAGE_PER_FIELD;
238 }
239 else {
240 $instance_values = content_migrate_get_instance_values(NULL, $field_value['field_name']);
241 if (count($instance_values) > 1) {
242 $storage = CONTENT_DB_STORAGE_PER_FIELD;
243 }
244 }
245 return $storage;
246 }
247
248 /**
249 * Helper function to find the table for a
250 * D7 field array.
251 *
252 * @param $field
253 */
254 function content_migrate_new_table($field) {
255 if (empty($field['storage']['details'])) {
256 return 'field_data_'. $field['field_name'];
257 }
258 $data = $field['storage']['details']['sql'][FIELD_LOAD_CURRENT];
259 return key($data);
260 }
261
262 function content_migrate_new_revision($field) {
263 if (empty($field['storage']['details'])) {
264 return 'field_revision_'. $field['field_name'];
265 }
266 $data = $field['storage']['details']['sql'][FIELD_LOAD_REVISION];
267 return key($data);
268 }
269
270 /**
271 * Helper function for finding the column names
272 * used for storing the D6 field data.
273 *
274 * @param $field_value
275 * @param $instance_value
276 */
277 function content_migrate_old_columns($field_value, $instance_value) {
278 $columns = array();
279 foreach ($field_value['columns'] as $col => $values) {
280 $columns[] = $field_value['field_name'] .'_'. $col;
281 }
282 return $columns;
283 }
284 /**
285 * Helper function for figuring out column names
286 * to be used when storing D7 field data.
287 *
288 * @param unknown_type $field
289 * @return unknown
290 */
291 function content_migrate_new_columns($field) {
292 $columns = array();
293 if (empty($field['storage']['details'])) {
294 foreach ($field['columns'] as $col => $values) {
295 $columns[] = $field['field_name'] .'_'. $col;
296 }
297 return $columns;
298 }
299 else {
300 foreach ($field['storage']['details']['sql'][FIELD_LOAD_CURRENT] as $table => $cols) {
301 foreach ($cols as $col) {
302 $columns[] = $col;
303 }
304 }
305 return $columns;
306 }
307 }
308
309 /**
310 * Implements hook_content_migrate_field_alter().
311 *
312 * Use this to tweak the conversion of field settings
313 * from the D6 style to the D7 style for specific
314 * situations not handled by basic conversion,
315 * as when field types or settings are changed.
316 *
317 * $field_value['widget_type'] is available to
318 * see what widget type was originally used.
319 */
320 function content_migrate_content_migrate_field_alter(&$field_value) {
321
322 switch ($field_value['type']) {
323 case 'text':
324
325 // The max_length field can no longer be empty
326 // or it will create a SQL error.
327 if (empty($field_value['settings']['max_length'])) {
328 $field_value['settings']['max_length'] = 255;
329 }
330
331 // Text fields using optionwidgets are
332 // now List fields.
333 switch ($field_value['widget_type']) {
334 case 'optionwidgets_buttons':
335 case 'optionwidgets_select':
336 $field_value['type'] = 'list_text';
337 $field_value['module'] = 'list';
338 break;
339 case 'optionwidgets_onoff':
340 $field_value['type'] = 'list_boolean';
341 $field_value['module'] = 'list';
342 break;
343 case 'text_textarea':
344 $field_value['type'] = 'text_long';
345 unset($field_value['settings']['max_length']);
346 break;
347 }
348 break;
349
350 case 'number_integer':
351 case 'number_decimal':
352 case 'number_float':
353
354 // Changed name of setting from 'decimal' to
355 // 'decimal_separator'.
356 if (isset($field_value['settings']['decimal'])) {
357 $field_value['settings']['decimal_separator'] = $field_value['settings']['decimal'];
358 unset($field_value['settings']['decimal']);
359 }
360 // Add a decimal_separator setting to floats.
361 if ($field_value['type'] == 'number_float') {
362 $field_value['settings']['decimal_separator'] = '.';
363 }
364
365 // Number fields using optionwidgets are
366 // now List fields.
367 switch ($field_value['widget_type']) {
368 case 'optionwidgets_buttons':
369 case 'optionwidgets_select':
370 $field_value['type'] = 'list_number';
371 $field_value['module'] = 'list';
372 break;
373 case 'optionwidgets_onoff':
374 $field_value['type'] = 'list_boolean';
375 $field_value['module'] = 'list';
376 break;
377 }
378 break;
379 }
380 }
381
382 /**
383 * Implements hook_content_migrate_instance_alter().
384 *
385 * Use this to tweak the conversion of instance or widget settings
386 * from the D6 style to the D7 style for specific
387 * situations not handled by basic conversion, as when
388 * formatter or widget names or settings are changed.
389 */
390 function content_migrate_content_migrate_instance_alter(&$instance_value) {
391 //$field = content_migrate_get_field_values($instance_value['field_name']);
392
393 switch ($instance_value['widget']['module']) {
394 // Optionswidgets module became Options module
395 // and widget type names changed.
396 case ('optionwidgets'):
397 $replace = array(
398 'optionwidgets_select' => 'options_select',
399 'optionwidgets_buttons' => 'options_buttons',
400 'optionwidgets_onoff' => 'options_onoff',
401 );
402 $instance_value['widget']['module'] = 'options';
403 $instance_value['widget']['type'] = strtr($instance_value['widget']['type'], $replace);
404 break;
405 }
406
407 switch ($instance_value['module']) {
408 case 'text':
409 // The formatter names changed, all are prefixed
410 // with 'text_'.
411 foreach ($instance_value['display'] as $context => $settings) {
412 $instance_value['display'][$context]['type'] = 'text_'. $settings['type'];
413 }
414 break;
415
416 case 'number':
417 // The number formatters and formatter settings
418 // have changed.
419 $new_type = array(
420 'unformatted' => 'number_unformatted',
421 'default' => 'number_decimal',
422 'us_0' => 'number_integer',
423 'us_1' => 'number_decimal',
424 'us_2' => 'number_decimal',
425 'be_0' => 'number_integer',
426 'be_1' => 'number_decimal',
427 'be_2' => 'number_decimal',
428 'fr_0' => 'number_integer',
429 'fr_1' => 'number_decimal',
430 'fr_2' => 'number_decimal',
431 );
432 $new_settings = array(
433 'default' => array(
434 'thousand_separator' => '',
435 'decimal_separator' => '.',
436 'scale' => 0,
437 'prefix_suffix' => TRUE,
438 ),
439 'us_0' => array(
440 'thousand_separator' => ',',
441 'decimal_separator' => '.',
442 'scale' => 0,
443 'prefix_suffix' => TRUE,
444 ),
445 'us_1' => array(
446 'thousand_separator' => ',',
447 'decimal_separator' => '.',
448 'scale' => 1,
449 'prefix_suffix' => TRUE,
450 ),
451 'us_2' => array(
452 'thousand_separator' => ',',
453 'decimal_separator' => '.',
454 'scale' => 2,
455 'prefix_suffix' => TRUE,
456 ),
457 'be_0' => array(
458 'thousand_separator' => '',
459 'decimal_separator' => ',',
460 'scale' => 0,
461 'prefix_suffix' => TRUE,
462 ),
463 'be_1' => array(
464 'thousand_separator' => '.',
465 'decimal_separator' => ',',
466 'scale' => 1,
467 'prefix_suffix' => TRUE,
468 ),
469 'be_2' => array(
470 'thousand_separator' => '.',
471 'decimal_separator' => ',',
472 'scale' => 2,
473 'prefix_suffix' => TRUE,
474 ),
475 'fr_0' => array(
476 'thousand_separator' => '',
477 'decimal_separator' => ', ',
478 'scale' => 0,
479 'prefix_suffix' => TRUE,
480 ),
481 'fr_1' => array(
482 'thousand_separator' => ' ',
483 'decimal_separator' => ', ',
484 'scale' => 1,
485 'prefix_suffix' => TRUE,
486 ),
487 'fr_2' => array(
488 'thousand_separator' => ' ',
489 'decimal_separator' => ', ',
490 'scale' => 2,
491 'prefix_suffix' => TRUE,
492 ),
493 );
494 foreach ($instance_value['display'] as $context => $settings) {
495 $instance_value['display'][$context]['type'] = $new_type[$settings['type']];
496 $instance_value['display'][$context]['settings'] = $new_settings[$settings['type']];
497 }
498 break;
499 }
500 }