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