Issue #1063852, go back to making all migrated data untranslated.
[project/cck.git] / modules / content_migrate / includes / content_migrate.admin.inc
CommitLineData
f5d210e1 1<?php
f5d210e1
KS
2/**
3 * @file content_migrate.admin.inc
f7cb36f7 4 * Code to process field data migration, moved into a separate file for efficiency.
f5d210e1
KS
5 */
6
7/**
70de9852
KS
8 * Determine which fields can be migrated, have already been migrated, and are
9 * unable to be migrated due to missing modules.
f5d210e1 10 */
70de9852 11function content_migrate_get_options() {
f5d210e1
KS
12 $options = array('available' => array(), 'converted' => array(), 'missing' => array());
13
14 $field_values = content_migrate_get_field_values();
cc335bda 15
f5d210e1
KS
16 if (empty($field_values)) {
17 drupal_set_message(t('There is no D6 field information in this database.'));
70de9852 18 return FALSE;
f5d210e1
KS
19 }
20
21 $type_names = node_type_get_names();
22 $new_fields = array_keys(field_info_fields());
23
24 // Figure out which field and widget modules are available.
8376bb8b 25 $available_modules = array_unique(array_merge(module_implements('field_info'), module_implements('field_widget_info')));
f5d210e1
KS
26
27 foreach ($field_values as $field_name => $field_value) {
cc335bda 28 $messages = array();
f5d210e1
KS
29 $bundles = array();
30 $missing_module = !in_array($field_value['module'], $available_modules);
cc335bda
KS
31 if ($missing_module) {
32 $messages[] = '<span class="error">' . t("Missing field module: '@field'. This field cannot be migrated.", array('@field' => $field_value['module'])) . '</span>';
33 }
34 if (isset($field_value['messages'])) {
35 $messages = array_merge($messages, $field_value['messages']);
36 unset($field_value['messages']);
37 }
38
f5d210e1 39 $instance_values = content_migrate_get_instance_values(NULL, $field_name);
cc335bda 40
0373a5db 41
8376bb8b
KS
42 // Debug
43 //dsm($field_value);
44 //dsm($instance_values);
45
f5d210e1 46 foreach ($instance_values as $bundle => $instance_value) {
cc335bda
KS
47 if (isset($instance_value['messages'])) {
48 $messages = array_merge($messages, $instance_value['messages']);
49 unset($instance_values[$bundle]['messages']);
50 }
f5d210e1
KS
51 $bundles[] = $type_names[$bundle];
52 $label = $instance_value['label'];
f5d210e1
KS
53 }
54 $data = array(
55 0 => $field_name,
56 1 => $field_value['type'],
cc335bda
KS
57 2 => theme('item_list', array('items' => $bundles)),
58 3 => theme('item_list', array('items' => $messages)),
f5d210e1
KS
59 );
60 if (in_array($field_name, $new_fields)) {
61 $options['converted'][$field_name] = $data;
62 }
63 // TODO, do we need to check for more than the mere presence of a module?
64 elseif ($missing_module) {
65 $options['missing'][$field_name] = $data;
66 }
67 else {
68 $options['available'][$field_name] = $data;
69 }
70 }
70de9852
KS
71 return $options;
72}
73
74/**
75 * Form generator for the migration selection form.
76 *
77 * @todo Make this into a nice table where you have
78 * an option to check all available fields to migrate
79 * them all at once.
80 */
81function content_migrate_select($form, &$form_state) {
82 $form = array();
83 $options = content_migrate_get_options();
84 if (!$options) {
85 return $form;
86 }
f5d210e1 87
8376bb8b 88 $header = array(t('Field'), t('Field type'), t('Content type(s)'), t('Other information'));
f5d210e1
KS
89 $form['#tree'] = TRUE;
90 $form['available'] = array(
91 '#type' => 'fieldset',
92 '#collapsible' => TRUE,
93 '#collapsed' => count($options['available']) < 1,
94 '#title' => t('Available fields'),
cc335bda 95 '#description' => t('Fields that have not yet been migrated but are available for migration. <strong>Please carefully read the messages next to each field before migrating it to understand changes that might be made.</strong>'),
f5d210e1
KS
96 );
97 $form['available']['data'] = array(
98 '#type' => 'tableselect',
99 '#header' => $header,
100 '#options' => $options['available'],
101 '#empty' => t('No fields are available to be migrated.'),
102 );
103 $form['available']['submit'] = array(
104 '#type' => 'submit',
105 '#value' => t('Migrate selected fields'),
106 '#submit' => array('content_migrate_select_submit'),
107 );
108
109 $form['converted'] = array(
110 '#type' => 'fieldset',
111 '#collapsible' => TRUE,
112 '#collapsed' => count($options['converted']) < 1,
113 '#title' => t('Converted fields'),
114 '#description' => '<p>'. t('Fields that have already been converted. You can choose to roll them back if the conversion did not work correctly. Note that rolling fields back will completely destroy the new field tables.') . ' <span class="error"><strong>' . t('This operation cannot be undone!') . '</strong></span>',
115 );
116 $form['converted']['data'] = array(
117 '#type' => 'tableselect',
118 '#header' => $header,
119 '#options' => $options['converted'],
120 '#empty' => t('No fields are already converted.'),
121 );
122 $form['converted']['submit'] = array(
123 '#type' => 'submit',
124 '#value' => t('Roll back selected fields'),
125 '#submit' => array('content_migrate_rollback_submit'),
126 );
127
128 $form['missing'] = array(
129 '#type' => 'fieldset',
130 '#collapsible' => TRUE,
131 '#collapsed' => count($options['missing']) < 1,
132 '#title' => t('Unavailable fields'),
133 '#description' => t('Fields that cannot be migrated because some modules are missing.'),
134 );
135 $form['missing']['data'] = array(
136 '#type' => 'tableselect',
137 '#header' => $header,
138 '#options' => $options['missing'],
139 '#empty' => t('No fields have missing modules.'),
140 );
141
142 return $form;
143}
144
145/**
146 * Submit handler.
147 *
148 * @TODO add a confirmation on the rollback submission.
149 */
150function content_migrate_rollback_submit($form, &$form_state) {
f5d210e1 151 $field_names = array_filter($form_state['values']['converted']['data']);
9067b59a 152 content_migrate_rollback($field_names);
e44c4441
KS
153}
154
155/**
156 * Helper function to perform rollback.
157 */
158function content_migrate_rollback($field_names) {
f5d210e1
KS
159 foreach ($field_names as $field_name) {
160 $field = field_info_field($field_name);
161
162 // Deleting the field only marks it for deletion.
163 field_delete_field($field_name);
164
165 // We are bypassing the field batch processing
166 // and simply deleting all the data.
167 // The assumption is that the migration was
168 // unsuccessful and will be re-attempted
169 // and we need to remove all traces of the
170 // new field for later migrations to work.
171 $new_table = content_migrate_new_table($field);
b338b91f 172 db_drop_table($new_table);
f5d210e1
KS
173
174 $instances = field_read_instances(array('field_id' => $field['id']), array('include_deleted' => 1));
175 foreach ($instances as $instance) {
176 field_purge_instance($instance);
177 }
178 field_purge_field($field);
179 drupal_set_message(t('Rolling back @field_name.', array('@field_name' => $field_name)));
180 }
181}
182
183/**
184 * Submit handler.
185 */
186function content_migrate_select_submit($form, &$form_state) {
187 $field_names = array_filter($form_state['values']['available']['data']);
188 _content_migrate_batch($field_names);
189}
190
191/**
192 * Helper function to create a batch.
193 */
194function _content_migrate_batch($field_names) {
195 $batch = array(
196 'title' => t('Migrating data'),
f7cb36f7 197 'file' => drupal_get_path('module', 'content_migrate') . '/includes/content_migrate.admin.inc',
f73fc580 198 'operations' => array(),
f5d210e1
KS
199 'finished' => "Field migration is finished",
200 'init_message' => t("Fields migration is starting."),
201 'progress_message' => t('Processed @current out of @total.'),
202 'error_message' => t('Field migration has encountered an error.'),
203 );
204 // Migrate field data one field at a time.
205 foreach ($field_names as $field_name) {
f73fc580 206 $batch['operations'][] = array('_content_migrate_batch_process_create_fields', array($field_name));
f5d210e1
KS
207 $batch['operations'][] = array('_content_migrate_batch_process_migrate_data', array($field_name));
208 }
209 batch_set($batch);
210}
211
212/**
213 * Batch operation callback to create fields.
214 */
f73fc580 215function _content_migrate_batch_process_create_fields($field_name, &$context) {
cc335bda
KS
216 $messages = array();
217 $errors = array();
f5d210e1 218 $type_names = node_type_get_names();
c4e54e0e
KS
219 $allowed_fields = field_info_field_types();
220 $allowed_widgets = field_info_widget_types();
221 $allowed_formatters = field_info_formatter_types();
222
f73fc580
KS
223 $context['message'] = t('"Creating field: %field', array('%field' => $field_name));
224 $field_value = content_migrate_get_field_values($field_name);
cc335bda
KS
225 if (isset($field_value['messages'])) {
226 $messages = $field_value['messages'];
227 unset($field_value['messages']);
228 }
df844aae 229 $instance_info = field_info_instances('node');
59ea776a 230
f73fc580
KS
231 // Create the field and store the new field
232 // definition in $context so we can retrieve it later.
233 try {
234 // A shared field may already have been created, check first.
df844aae 235 $field = field_info_field($field_value['field_name']);
f73fc580
KS
236 if (empty($field)) {
237 unset($field_value['columns']);
238 unset($field_value['db_storage']);
239 $field = field_create_field($field_value);
240 $context['fields'][$field_name] = $field;
cc335bda 241 $messages[] = t("Created field @field_name", array('@field_name' => $field_name));
f73fc580 242 }
bccb3da9 243
f73fc580
KS
244 // Create each of the new instances and store
245 // the new instance definitions in $context.
246 $instance_values = content_migrate_get_instance_values(NULL, $field_name);
59ea776a 247
f73fc580
KS
248 foreach ($instance_values as $bundle => $instance_value) {
249 try {
c4e54e0e 250
cc335bda
KS
251 if (isset($instance_value['messages'])) {
252 $messages = array_merge($messages, $instance_value['messages']);
253 unset($instance_value['messages']);
c4e54e0e
KS
254 }
255
df844aae
KS
256 if (!isset($instance_info[$bundle][$field_name])) {
257 $instance = field_create_instance($instance_value);
258 $context['instances'][$field_name][$bundle] = $instance;
cc335bda
KS
259 $messages[] = t("Created instance of @field_name in bundle @bundle.", array(
260 '@field_name' => $field_name, '@bundle' => $type_names[$bundle]));
df844aae 261 }
f73fc580
KS
262
263 }
264 catch (Exception $e) {
cc335bda
KS
265 $errors[] = t('Error creating instance of @field_name in bundle @bundle.', array(
266 '@field_name' => $field_name, '@bundle' => $type_names[$bundle]));
267 $errors[] = $e;
f5d210e1
KS
268 }
269 }
f5d210e1 270 }
f73fc580 271 catch (Exception $e) {
cc335bda
KS
272 $errors[] = t("Error creating field @field_name", array('@field_name' => $field_name));
273 $errors[] = $e;
f73fc580
KS
274 }
275 field_info_cache_clear();
276
cc335bda
KS
277 foreach ($messages as $message) {
278 drupal_set_message($message, 'warning');
279 }
280 foreach ($errors as $error) {
281 drupal_set_message($error, 'error');
282 }
283
f5d210e1
KS
284 $context['finished'] = TRUE;
285}
286
287/**
288 * Batch operation callback to migrate data.
289 * Copy old table data to new field table.
f5d210e1 290 */
763b3b8c
KS
291function _content_migrate_batch_process_migrate_data($field_name, &$context) {
292
293 // The first time through, find all the nodes that have this field.
294 if (!isset($context['sandbox']['progress'])) {
295
296 $field_value = content_migrate_get_field_values($field_name);
cc335bda
KS
297 if (isset($field_value['messages'])) {
298 unset($field_value['messages']);
299 }
763b3b8c 300 $instance_values = content_migrate_get_instance_values(NULL, $field_name);
cc335bda
KS
301 if (isset($instance_values['messages'])) {
302 unset($instance_values['messages']);
303 }
763b3b8c
KS
304 $types = array();
305 foreach ($instance_values as $bundle => $instance_value) {
306 $types[] = $bundle;
307 }
308 $field = field_info_field($field_name);
f5d210e1
KS
309 $old_table = content_migrate_old_table($field_value, $instance_value);
310 $old_cols = content_migrate_old_columns($field_value, $instance_value);
311 $new_table = content_migrate_new_table($field);
312 $new_revision_table = content_migrate_new_revision($field);
313 $new_columns = content_migrate_new_columns($field);
763b3b8c
KS
314 // Shared, non-multiple fields do not have a delta but are still in per-field tables.
315 $add_delta = $field_value['cardinality'] != 1 && content_migrate_storage_type($field_value, $instance_value) == CONTENT_DB_STORAGE_PER_FIELD;
316
317 $query = db_select($old_table, 'old_table', array('fetch' => PDO::FETCH_ASSOC));
318 $node_alias = $query->join('node', 'n', 'old_table.nid=n.nid');
319 $result = $query
819e88cc 320 ->fields($node_alias, array('title', 'type', 'vid', 'language'))
763b3b8c
KS
321 ->fields('old_table', array('nid'))
322 ->orderBy('nid', 'ASC')
8043533c 323 ->distinct()
763b3b8c 324 ->execute();
8376bb8b 325
763b3b8c
KS
326 $nodes = array();
327 foreach ($result as $row) {
819e88cc 328 $nodes[] = array('nid' => $row['nid'], 'title' => $row['title'], 'type' => $row['type'], 'vid' => $row['vid'], 'language' => $row['language']);
763b3b8c
KS
329 }
330 $context['sandbox']['progress'] = 0;
331 $context['sandbox']['max'] = count($nodes);
332 $context['sandbox']['nodes'] = $nodes;
333 $context['sandbox']['old_table'] = $old_table;
334 $context['sandbox']['new_table'] = $new_table;
335 $context['sandbox']['new_revision_table'] = $new_revision_table;
336 $context['sandbox']['old_cols'] = $old_cols;
337 $context['sandbox']['new_cols'] = $new_columns;
338 $context['sandbox']['types'] = $types;
339 $context['sandbox']['field'] = $field;
340 $context['sandbox']['add_delta'] = $add_delta;
341
342 }
343
344 // Process one node in each batch.
345
346 $node = array_shift($context['sandbox']['nodes']);
680215e8
KS
347 if (!$node) {
348 return;
349 }
763b3b8c 350
575ce50d
KS
351 $field = field_info_field($field_name);
352 $instance = field_info_instance('node', $field_name, $node['type']);
ce009a02 353
763b3b8c
KS
354 // Construct an record to insert into the new field table
355 // from the data in the old table.
356
357 $query = db_select($context['sandbox']['old_table'], 'old_table', array('fetch' => PDO::FETCH_ASSOC));
f5d210e1 358
763b3b8c 359 // We need new columns for bundle name, entity type, and language.
0373a5db
KS
360 // See the debate going on at http://drupal.org/node/1164852.
361 // Reverting back to setting all nodes as untranslated.
362 $language = LANGUAGE_NONE;
363 if ($field['translatable']) {
364 //$language = $node['language'];
365 }
763b3b8c
KS
366 $query->addExpression("'". $node['type'] ."'", 'bundle');
367 $query->addExpression("'node'", 'entity_type');
0373a5db 368 $query->addExpression("'". $language ."'", 'language');
f5d210e1 369
763b3b8c
KS
370 // There are new names for what were the nid and vid columns.
371 $query->addField('old_table', 'nid', 'entity_id');
372 $query->addField('old_table', 'vid', 'revision_id');
f5d210e1 373
763b3b8c
KS
374 // Add the field columns to the select query.
375 // Use the new column names as aliases in case the
376 // name changed, hopefully none did.
df844aae
KS
377 foreach ($context['sandbox']['old_cols'] as $column_name => $db_column_name) {
378 $query->addField('old_table', $db_column_name, $context['sandbox']['new_cols'][$column_name]);
763b3b8c 379 }
f5d210e1 380
763b3b8c
KS
381 // Add delta, or construct it if missing.
382 if ($context['sandbox']['add_delta']) {
383 $query->addField('old_table', 'delta', 'delta');
384 }
385 else {
386 $query->addExpression(0, 'delta');
387 }
388 $query->condition('nid', $node['nid']);
389 $result = $query->execute();
bccb3da9 390
763b3b8c
KS
391 foreach ($result as $record) {
392
393 // Let modules alter this before the insert.
ce009a02 394 drupal_alter('content_migrate_data_record', $record, $field, $instance);
763b3b8c 395
d28bbf01
KS
396 // Don't save empty values.
397 if (!empty($record)) {
398 $function = $field['module'] . '_field_is_empty';
df844aae
KS
399 if (function_exists($function)) {
400 // The $record array has the database columns as keys, which drupal_write_record() will need,
401 // but the _field_is_empty() function will be looking for the short, normalized column name.
402 $item = array();
403 foreach ($context['sandbox']['new_cols'] as $column_name => $db_column_name) {
404 if (array_key_exists($db_column_name, $record)) {
405 $item[$column_name] = $record[$db_column_name];
406 }
407 }
408 if ($function($item, $field)) {
409 $record = NULL;
410 }
d28bbf01
KS
411 }
412 }
413
bdf8319f 414 if (!empty($record)) {
f5df077d
KS
415 if ($record['revision_id'] == $node['vid']) {
416 drupal_write_record($context['sandbox']['new_table'], $record);
417 }
bdf8319f
KS
418 drupal_write_record($context['sandbox']['new_revision_table'], $record);
419 }
763b3b8c
KS
420 }
421
422 // Update our progress information.
423 $context['sandbox']['progress']++;
424 $context['message'] = t('Processing %nid : %title', array('%title' => $node['title'], '%nid' => $node['nid']));
425
426 // Inform the batch engine that we are not finished,
427 // and provide an estimation of the completion level we reached.
428 if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
429 $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
f5d210e1 430 }
763b3b8c 431
6fb49f76 432}
763b3b8c
KS
433
434