Remove upload module and provide upgrade path to file: http://drupal.org/node/563000 From: andrew morton --- modules/field_ui/field_ui.admin.inc | 2 modules/system/system.install | 300 +++++++++++++++++++++++++++++++++-- modules/upload/upload.install | 57 ------- 3 files changed, 282 insertions(+), 77 deletions(-) diff --git modules/field_ui/field_ui.admin.inc modules/field_ui/field_ui.admin.inc index 792b642..72cd112 100644 --- modules/field_ui/field_ui.admin.inc +++ modules/field_ui/field_ui.admin.inc @@ -839,7 +839,7 @@ function field_ui_field_settings_form($form, &$form_state, $obj_type, $bundle, $ '#options' => array(TRUE => t('Translatable field'), FALSE => t('Language neutral field')), '#default_value' => $field['translatable'], '#description' => t("Translatable fields can have a different value for each available language. An example of a translatable field is an article's body. Language neutral fields will retain the same value across all translations. An example of a language neutral field is a user profile's first name."), - ); + ); // Add settings provided by the field module. $form['field']['settings'] = array(); diff --git modules/system/system.install modules/system/system.install index 1242daa..e61ea10 100644 --- modules/system/system.install +++ modules/system/system.install @@ -2431,28 +2431,74 @@ function system_update_7034() { /** * Migrate upload module files to the new {file} table. */ -function system_update_7035() { - $ret = array(); +function system_update_7035(&$context) { + $ret = array('#finished' => 0); + + // Check for the existance of the table rather than using module_exists() + // so we can migrate even if the module is disabled. if (!db_table_exists('upload')) { - return $ret; + return array( + '#finished' => 1, + array('success' => TRUE, 'query' => "No upload module data to migrate."), + ); + } + + + if (!isset($context['total'])) { + // Initial invocation. + + // Initialize state for future calls. + $context['last'] = 0; + $context['count'] = 0; + + // Find a total number of records. We're joining to the node table here + // to ensure the count matches up later when we repeat the query. + $query = db_select('upload', 'u'); + $query->innerJoin('file', 'n', 'u.fid = f.fid'); + $query->addExpression('COUNT(u.fid)'); + $context['total'] = $query->execute()->fetchField(); } - // The old {files} tables still exists. We migrate core data from upload - // module, but any contrib module using it will need to do its own update. - $result = db_query('SELECT fid, uid, filename, filepath AS uri, filemime, filesize, status, timestamp FROM {files} f INNER JOIN {upload} u ON u.fid = f.fid', array(), array('fetch' => PDO::FETCH_ASSOC)); - - // We will convert filepaths to uri using the default schmeme - // and stripping off the existing file directory path. - $basename = variable_get('file_directory_path', conf_path() . '/files'); - $scheme = variable_get('file_default_scheme', 'public') . '://'; - $fids = array(); - // TODO: does this function need to run in batch mode? - foreach ($result as $file) { - $file['uri'] = $scheme . str_replace($basename, '', $file['uri']); - $file['uri'] = file_stream_wrapper_uri_normalize($file['uri']); - db_insert('file')->fields($file)->execute(); - $fids[] = $file['fid']; + else { + // Subsequent invocations. + + if ($context['total']) { + $batch_size = 100; + + // The old {files} tables still exists. We migrate core data from upload + // module, but any contrib module using it will need to do its own update. + $query = db_select('file', 'f'); + $query->innerJoin('upload', 'u', 'f.fid = u.fid'); + $result = $query + ->fields('f', array('fid', 'uid', 'filename', 'filepath', 'filemime', 'filesize', 'status', 'timestamp')) + ->range($context['last'], $batch_size) + ->orderBy('fid', 'ASC') + ->execute(); + + // We will convert filepath to uri using the default schmeme and stripping + // off the existing file directory path. + $basename = variable_get('file_directory_path', conf_path() . '/files'); + $scheme = variable_get('file_default_scheme', 'public') . '://'; + + foreach ($result as $file) { + $file->uri = file_stream_wrapper_uri_normalize($scheme . str_replace($basename, '', $file->filepath)); + unset($file->filepath); + db_insert('file')->fields((array) $file)->execute(); + + $context['last'] = $file->fid; + $context['count'] += 1; + } + + $ret['#finished'] = min(0.99, $context['count'] / $context['total']); + } + else { + // All rows are processed. + $ret[] = array('success' => TRUE, 'query' => "{$context['total']} upload records migrated from 'files' table to 'file' table."); + + // We're done. + $ret['#finished'] = 1; + } } - // TODO: delete the found fids from {files}? + return $ret; } @@ -2493,6 +2539,222 @@ function system_update_7037() { } /** + * Migrate the upload module's data to a file field. + * + * TODO: Test the translateabiltiy + */ +function system_update_7038(&$context) { + $ret = array('#finished' => 0); + + // Check for the existance of the table rather than using module_exists() + // so we can migrate even if the module is disabled. + if (!db_table_exists('upload')) { + return array( + '#finished' => 1, + array('success' => TRUE, 'query' => "No upload module data to migrate."), + ); + } + + if (!isset($context['total'])) { + // Initial invocation. + + // Install the file module if it isn't already. + if (!module_exists('file')) { + drupal_install_modules(array('file')); + } + + // Initialize state for future calls. + $context['last'] = 0; + $context['count'] = 0; + + // Find a total number of records. We're joining to the node table here + // to ensure the count matches up later when match to get the node type. + $query = db_select('upload', 'u'); + $query->innerJoin('node', 'n', 'u.nid = n.nid'); + $query->addExpression('COUNT(DISTINCT u.vid)', 'vid_count'); + $context['total'] = $query->execute()->fetchField(); + + + // Check which node types have upload.module attachements enabled. + $context['types'] = array(); + foreach (node_type_get_types() as $node_type => $node_info) { + if (variable_get("upload_$node_type", TRUE)) { + $context['types'][$node_type] = $node_type; + } + } + // The {upload} table will be deleted when this update is complete so we + // want to be careful to migrate all the data, even for node types that + // may have had attachments disabled after files were uploaded. Look for + // any other node types referenced by the upload records and add those to + // the list. The admin can always remove the field later. + $query = db_select('node', 'n')->distinct(TRUE); + $query->innerJoin('upload', 'u', 'n.vid = u.vid'); + $results = $query->fields('n', array('type'))->execute(); + foreach ($results as $row) { + $context['types'][$row->type] = $row->type; + } + + // If there are any any node types then create the file field and an + // instance for each type. + if (count($context['types'])) { + module_load_include('inc', 'field', 'field.crud'); + + $field = array( + 'field_name' => 'field_upload', + 'type' => 'file', + 'locked' => FALSE, + 'module' => 'file', + 'cardinality' => '-1', + 'translatable' => FALSE, + 'settings' => array( + 'display_field' => 1, + 'display_default' => 1, + 'uri_scheme' => variable_get('file_default_scheme', 'public'), + 'default_file' => 0, + ), + ); + + $instance = array( + 'field_name' => 'field_upload', + 'bundle' => NULL, + 'label' => 'File attachments', + 'required' => 0, + 'description' => '', + 'widget' => array( + 'weight' => '1', + 'module' => 'file', + 'active' => '1', + 'settings' => array( + 'progress_indicator' => 'throbber', + 'description_field' => 1, + ), + 'type' => 'file_generic', + ), + 'display' => array( + 'full' => array( + 'label' => 'above', + 'type' => 'file_table', + 'settings' => array(), + 'weight' => 0, + 'module' => 'file', + ), + 'teaser' => array( + 'label' => 'hidden', + 'type' => 'hidden', + 'settings' => array(), + 'weight' => 0, + 'module' => NULL, + ), + 'rss' => array( + 'label' => 'hidden', + 'type' => 'file_table', + 'settings' => array(), + 'weight' => 0, + 'module' => 'file', + ), + ), + 'settings' => array( + 'max_filesize' => variable_get('upload_uploadsize_default', 1), + 'file_extensions' => variable_get('upload_extensions_default', 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp'), + 'file_directory' => '', + ), + ); + + try { + // Create the field. + field_create_field($field); + $ret[] = array('success' => TRUE, 'query' => "Created 'attached files' field."); + + // Create the instances. + foreach ($context['types'] as $bundle) { + $instance['bundle'] = $bundle; + field_create_instance($instance); + $ret[] = array('success' => TRUE, 'query' => "Added 'attached files' field to " . check_plain($bundle) . " node type."); + } + } + catch (FieldException $e) { + $ret['#abort'] = array('success' => FALSE, 'query' => $e->getMessage()); + return $ret; + } + } + } + else { + // Subsequent invocations. + + $batch_size = 50; + $vids = array(); + if ($context['total']) { + // Each revision can have multiple files, all of which need to be + // assigned to the node object before we write the record. To account + // for this we grab a number of revisions and process all of their files. + $vids = db_select('upload', 'u') + ->distinct() + ->fields('u', array('vid')) + ->range($context['last'], $batch_size) + ->orderBy('u.vid', 'ASC') + ->execute() + ->fetchAllKeyed(0, 0); + } + + if (count($vids)) { + $query = db_select('upload', 'u'); + $n = $query->innerJoin('node', 'n', 'u.nid = n.nid'); + $results = $query + ->fields('u', array('nid', 'vid', 'fid', 'description', 'list', 'weight')) + ->fields($n, array('type')) + ->condition('u.vid', $vids, 'IN') + ->orderBy('u.vid', 'ASC') + ->orderBy('u.weight', 'ASC') + ->orderBy('u.fid', 'ASC') + ->execute(); + + // Group the upload records by revision. + $nodes = array(); + foreach ($results as $row) { + $nodes[$row->vid]['nid'] = $row->nid; + $nodes[$row->vid]['vid'] = $row->vid; + $nodes[$row->vid]['type'] = $row->type; + $nodes[$row->vid]['field_upload'][FIELD_LANGUAGE_NONE][] = array( + 'fid' => $row->fid, + 'data' => serialize(array('description' => $row->description)), + 'display' => $row->list, + ); + } + + // Then store each revision in the database. + foreach ($nodes as $vid => $node) { + $found = TRUE; + $node = (object) $node; + + // This is a core update and no contrib modules are enabled yet, so + // we can assume default field storage for a faster update. + field_sql_storage_field_storage_write('node', $node, FIELD_STORAGE_INSERT, array()); + + $context['last'] = $vid; + $context['count'] += 1; + } + + $ret['#finished'] = min(0.99, $context['count'] / $context['total']); + } + else { + // All rows are processed. + $ret[] = array('success' => TRUE, 'query' => "{$context['total']} upload records migrated to the 'attached files' field."); + + // Uninstall the upload module. + #TODO: Commenting this out because it removes the tables but doesn't + # change the module system.status to 0 so upload_file_load() still gets + # called resulting in fatal errors. +# drupal_uninstall_modules(array('upload')); + + // We're done. + $ret['#finished'] = 1; + } + } + + return $ret; +} + +/** * @} End of "defgroup updates-6.x-to-7.x" * The next series of updates should start at 8000. */ diff --git modules/upload/upload.install modules/upload/upload.install index e0f1ee8..ae18c21 100644 --- modules/upload/upload.install +++ modules/upload/upload.install @@ -76,60 +76,3 @@ function upload_schema() { return $schema; } - - -/** - * Migrate upload module files from {files} to {file}. - */ -function upload_update_7000(&$sandbox) { - $ret = array(); - - /* - TODO: Fix the updates. This is broken. See http://drupal.org/node/329301#comment-1404336 - Also note new DB structure http://drupal.org/node/227232#comment-1683976 - */ - - if (!isset($sandbox['progress'])) { - // Initialize batch update information. - $sandbox['progress'] = 0; - $sandbox['last_fid_processed'] = -1; - $sandbox['max'] = db_query("SELECT COUNT(DISTINCT u.fid) FROM {upload} u")->fetchField(); - } - - // As a batch operation move records from {files} into the {file} table. - $limit = 500; - $result = db_query_range("SELECT DISTINCT u.fid FROM {upload} u ORDER BY u.vid", array(), 0, $limit); - foreach ($result as $record) { - $old_file = db_query('SELECT f.* FROM {files} f WHERE f.fid = :fid', array(':fid' => $record->fid))->fetch(PDO::FETCH_OBJ); - if (!$old_file) { - continue; - } - - $new_file = db_query('SELECT f.* FROM {files} f WHERE f.filepath = :filepath', array(':filepath' => $old_file->uri))->fetch(PDO::FETCH_OBJ); - if (!$new_file) { - // Re-save the file into the new {file} table. - $new_file = clone $old_file; - drupal_write_record('file', $new_file); - } - - // If the fid has changed we need to update the {upload} record to use the - // new id. - if (!empty($new_file->fid) && ($new_file->fid != $old_file->fid)) { - db_update('upload') - ->fields(array('fid' => $new_file->fid)) - ->condition('fid', $old_file->fid) - ->execute(); - } - - // Update our progress information for the batch update. - $sandbox['progress']++; - $sandbox['last_fid_processed'] = $old_file->fid; - } - - // Indicate our current progress to the batch update system. If there's no - // max value then there's nothing to update and we're finished. - $ret['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); - - return $ret; -} -