Index: modules/user/user.install =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.install,v retrieving revision 1.16 diff -u -p -r1.16 user.install --- modules/user/user.install 8 Jan 2009 08:42:13 -0000 1.16 +++ modules/user/user.install 12 Jan 2009 06:17:44 -0000 @@ -169,12 +169,11 @@ function user_schema() { 'default' => '', 'description' => "User's default language.", ), - 'picture' => array( - 'type' => 'varchar', - 'length' => 255, + 'picture_fid' => array( + 'type' => 'int', 'not null' => TRUE, - 'default' => '', - 'description' => "Path to the user's uploaded picture.", + 'default' => 0, + 'description' => "Foriegn key: {files}.fid of user's picture.", ), 'init' => array( 'type' => 'varchar', @@ -388,6 +387,82 @@ function user_update_7003() { } /** + * Add the user's pictures to the {files} table and make them managed files. + */ +function user_update_7004(&$sandbox) { + $ret = array(); + + $picture_fid_field = array( + 'type' => 'int', + 'not null' => TRUE, + 'default' => 0, + 'description' => t("Foriegn key: {files}.fid of user's picture."), + ); + + if (!isset($sandbox['progress'])) { + // Check that the field hasn't been updated in an aborted run of this + // update. + if (!db_column_exists('users', 'picture_fid')) { + // Add a new field for the fid. + db_add_field($ret, 'users', 'picture_fid', $picture_fid_field); + } + + // Initialize batch update information. + $sandbox['progress'] = 0; + $sandbox['last_user_processed'] = -1; + $sandbox['max'] = db_query("SELECT COUNT(*) FROM {users} WHERE picture <> ''")->fetchField(); + } + + // As a batch operation move the photos into the {files} table and update the + // {user} records. + $limit = 10; // TODO: starting this at a low number for testing + $result = db_query_range("SELECT uid, picture FROM {users} WHERE uid > %d AND picture <> '' ORDER BY uid", $sandbox['last_user_processed'], 0, $limit); + foreach ($result as $user) { + // Don't bother adding files that don't exist. + if (!file_exists($user->picture)) { + continue; + } + + // Check if the file already exists. + $files = file_load_multiple(array(), array('filepath' => $user->picture)); + if (count($files)) { + $file = reset($files); + } + else { + // Create a file object. + $file = new stdClass(); + $file->filepath = $user->picture; + $file->filename = basename($file->filepath); + $file->filemime = file_get_mimetype($file->filepath); + $file->uid = 1; + $file->status = FILE_STATUS_PERMANENT; + $file = file_save($file); + } + + db_update('users') + ->fields(array('picture_fid' => $file->fid)) + ->condition('uid', $user->uid) + ->execute(); + + // Update our progress information for the batch update. + $sandbox['progress']++; + $sandbox['last_user_processed'] = $user->uid; + } + + // Indicate our current progress to the batch update system. + $ret['#finished'] = $sandbox['progress'] / $sandbox['max']; + + // If we're finished, remove the old field and rename the new on to replace + // it. + if (isset($ret['#finished']) && $ret['#finished'] == 1) { + db_drop_field($ret, 'users', 'picture'); + db_change_field($ret, 'users', 'picture_fid', 'picture', $picture_fid_field); + } + + return $ret; +} + +/** * @} End of "defgroup user-updates-6.x-to-7.x" * The next series of updates should start at 8000. */ Index: modules/user/user.module =================================================================== RCS file: /cvs/drupal/drupal/modules/user/user.module,v retrieving revision 1.953 diff -u -p -r1.953 user.module --- modules/user/user.module 8 Jan 2009 08:42:13 -0000 1.953 +++ modules/user/user.module 12 Jan 2009 06:17:45 -0000 @@ -186,6 +186,14 @@ function user_load($array = array()) { while ($role = db_fetch_object($result)) { $user->roles[$role->rid] = $role->name; } + + if (!empty($user->picture) && ($file = file_load($user->picture))) { + $user->picture = $file; + } + else { + $user->picture = NULL; + } + user_module_invoke('load', $array, $user); } else { @@ -244,7 +252,7 @@ function user_save($account, $edit = arr foreach ($edit as $key => $value) { // Fields that don't pertain to the users or user_roles // automatically serialized into the users.data column. - if ($key != 'roles' && empty($user_fields[$key])) { + if ($key != 'roles' && $key != 'picture' && empty($user_fields[$key])) { if ($value === NULL) { unset($data[$key]); } @@ -254,6 +262,22 @@ function user_save($account, $edit = arr } } + // Process picture uploads. + if (!empty($edit['picture']->fid)) { + $picture = $edit['picture']; + // If the picture is a temporary file move it to its final location and + // make it permanent. + if ($picture->status & FILE_STATUS_PERMANENT == 0) { + $info = image_get_info($picture->filepath); + $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $account->uid . '.' . $info['extension']); + if ($picture = file_move($picture, $destination, FILE_EXISTS_REPLACE)) { + $picture->status |= FILE_STATUS_PERMANENT; + $picture = file_save($picture); + } + } + } + $edit['picture'] = empty($edit['picture']->fid) ? NULL : $edit['picture']->fid; + $edit['data'] = $data; $edit['uid'] = $account->uid; // Save changes to the users table. @@ -263,6 +287,13 @@ function user_save($account, $edit = arr return FALSE; } + // If the picture changed or was unset, remove the old one. This step needs + // to occur after update the {users} record so that user_file_references() + // doesn't report it in use and block the deletion. + if (!empty($account->picture->fid) && ($edit['picture'] != $account->picture->fid)) { + file_delete($account->picture); + } + // Reload user roles if provided. if (isset($edit['roles']) && is_array($edit['roles'])) { db_query('DELETE FROM {users_roles} WHERE uid = %d', $account->uid); @@ -405,23 +436,14 @@ function user_validate_picture(&$form, & 'file_validate_image_resolution' => array(variable_get('user_picture_dimensions', '85x85')), 'file_validate_size' => array(variable_get('user_picture_file_size', '30') * 1024), ); - if ($file = file_save_upload('picture_upload', $validators)) { - // Remove the old picture. - if (isset($form_state['values']['_account']->picture) && file_exists($form_state['values']['_account']->picture)) { - file_unmanaged_delete($form_state['values']['_account']->picture); - } - // The image was saved using file_save_upload() and was added to the - // files table as a temporary file. We'll make a copy and let the garbage - // collector delete the original upload. - $info = image_get_info($file->filepath); - $destination = file_create_path(variable_get('user_picture_path', 'pictures') . '/picture-' . $form['#uid'] . '.' . $info['extension']); - if ($filepath = file_unmanaged_copy($file->filepath, $destination, FILE_EXISTS_REPLACE)) { - $form_state['values']['picture'] = $filepath; - } - else { - form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures')))); - } + // Save the file as a temporary file. + $file = file_save_upload('picture_upload', $validators); + if ($file === FALSE) { + form_set_error('picture_upload', t("Failed to upload the picture image; the %directory directory doesn't exist or is not writable.", array('%directory' => variable_get('user_picture_path', 'pictures')))); + } + elseif ($file !== NULL) { + $form_state['values']['picture'] = $file; } } @@ -604,14 +626,37 @@ function user_perm() { * * Ensure that user pictures (avatars) are always downloadable. */ -function user_file_download($file) { - if (strpos($file, variable_get('user_picture_path', 'pictures') . '/picture-') === 0) { - $info = image_get_info(file_create_path($file)); +function user_file_download($filepath) { + if (strpos($filepath, variable_get('user_picture_path', 'pictures') . '/picture-') === 0) { + $info = image_get_info(file_create_path($filepath)); return array('Content-type: ' . $info['mime_type']); } } /** + * Implementation of hook_file_references(). + */ +function user_file_references($file) { + // If upload.module is still using a file, do not let other modules delete it. + $count = db_query('SELECT COUNT(*) FROM {users} WHERE picture = :fid', array(':fid' => $file->fid))->fetchField(); + if ($count) { + // Return the name of the module and how many references it has to the file. + return array('user' => $count); + } +} + +/** + * Implementatin of hook_file_delete(). + */ +function user_file_delete($file) { + // Remove any references to the file. + db_update('users') + ->fields(array('picture' => NULL)) + ->condition('picture', $file->fid) + ->execute(); +} + +/** * Implementation of hook_search(). */ function user_search($op = 'search', $keys = NULL, $skip_access_check = FALSE) { @@ -688,20 +733,44 @@ function user_user_form(&$edit, &$accoun } /** - * Implementation of hook_user_validate. + * Implementation of hook_user_validate(). */ function user_user_validate(&$edit, &$account, $category = NULL) { if ($category == 'account') { - return _user_edit_validate($account, $edit); + $uid = isset($account->uid) ? $account->uid : FALSE; + // Validate the username when: new user account; or user is editing own account and can change username; or an admin user. + if (!$uid || ($GLOBALS['user']->uid == $uid && user_access('change own username')) || user_access('administer users')) { + if ($error = user_validate_name($edit['name'])) { + form_set_error('name', $error); + } + elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) { + form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name']))); + } + } + + // Validate the e-mail address: + if ($error = user_validate_mail($edit['mail'])) { + form_set_error('mail', $error); + } + elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) { + form_set_error('mail', t('The e-mail address %email is already registered. Have you forgotten your password?', array('%email' => $edit['mail'], '@password' => url('user/password')))); + } } } /** - * Implementation of hook_user_submit. + * Implementation of hook_user_submit(). */ function user_user_submit(&$edit, &$account, $category = NULL) { if ($category == 'account') { - return _user_edit_submit($account, $edit); + // Delete picture if requested, and if no replacement picture was given. + if (!empty($edit['picture_delete']) && !empty($user->picture)) { + $edit['picture'] = NULL; + } + unset($edit['picture_delete']); + if (isset($edit['roles'])) { + $edit['roles'] = array_filter($edit['roles']); + } } } @@ -880,7 +949,7 @@ function user_block_view($delta = '') { * Process variables for user-picture.tpl.php. * * The $variables array contains the following arguments: - * - $account + * - $account a user, node or comment object. * * @see user-picture.tpl.php */ @@ -888,16 +957,23 @@ function template_preprocess_user_pictur $variables['picture'] = ''; if (variable_get('user_pictures', 0)) { $account = $variables['account']; - if (!empty($account->picture) && file_exists($account->picture)) { - $picture = file_create_url($account->picture); + if (!empty($account->picture)) { + // It's kind of dirty to do this way but it works. It would be best to + // get the node and comment code loading the files. + if (is_numeric($account->picture)) { + $account->picture = file_load($account->picture); + } + if (!empty($account->picture->filepath)) { + $filepath = $account->picture->filepath; + } } elseif (variable_get('user_picture_default', '')) { - $picture = variable_get('user_picture_default', ''); + $filepath = variable_get('user_picture_default', ''); } - if (isset($picture)) { + if (isset($filepath)) { $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous')))); - $variables['picture'] = theme('image', $picture, $alt, $alt, '', FALSE); + $variables['picture'] = theme('image', $filepath, $alt, $alt, '', FALSE); if (!empty($account->uid) && user_access('access user profiles')) { $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE); $variables['picture'] = l($variables['picture'], "user/$account->uid", $attributes); @@ -1583,13 +1659,12 @@ function user_edit_form(&$form_state, $u // Picture/avatar: if (variable_get('user_pictures', 0) && !$register) { $form['picture'] = array('#type' => 'fieldset', '#title' => t('Picture'), '#weight' => 1); - $picture = theme('user_picture', (object)$edit); - if ($edit['picture']) { - $form['picture']['current_picture'] = array('#markup' => $picture); + if (!empty($edit['picture']->fid)) { + $form['picture']['current_picture'] = array('#markup' => theme('user_picture', (object)$edit)); $form['picture']['picture_delete'] = array('#type' => 'checkbox', '#title' => t('Delete picture'), '#description' => t('Check this box to delete your current picture.')); } else { - $form['picture']['picture_delete'] = array('#type' => 'hidden'); + $form['picture']['picture_delete'] = array('#type' => 'value'); } $form['picture']['picture_upload'] = array('#type' => 'file', '#title' => t('Upload picture'), '#size' => 48, '#description' => t('Your virtual face or picture. Maximum dimensions are %dimensions and the maximum size is %size kB.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'), '%size' => variable_get('user_picture_file_size', '30'))) . ' ' . variable_get('user_picture_guidelines', '')); $form['#validate'][] = 'user_validate_picture'; @@ -1599,40 +1674,6 @@ function user_edit_form(&$form_state, $u return $form; } -function _user_edit_validate($account, &$edit) { - $uid = isset($account->uid) ? $account->uid : FALSE; - // Validate the username when: new user account; or user is editing own account and can change username; or an admin user. - if (!$uid || ($GLOBALS['user']->uid == $uid && user_access('change own username')) || user_access('administer users')) { - if ($error = user_validate_name($edit['name'])) { - form_set_error('name', $error); - } - elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(name) = LOWER('%s')", $uid, $edit['name'])) > 0) { - form_set_error('name', t('The name %name is already taken.', array('%name' => $edit['name']))); - } - } - - // Validate the e-mail address: - if ($error = user_validate_mail($edit['mail'])) { - form_set_error('mail', $error); - } - elseif (db_result(db_query("SELECT COUNT(*) FROM {users} WHERE uid != %d AND LOWER(mail) = LOWER('%s')", $uid, $edit['mail'])) > 0) { - form_set_error('mail', t('The e-mail address %email is already registered. Have you forgotten your password?', array('%email' => $edit['mail'], '@password' => url('user/password')))); - } -} - -function _user_edit_submit($account, &$edit) { - // Delete picture if requested, and if no replacement picture was given. - if (!empty($edit['picture_delete'])) { - if ($account->picture && file_exists($account->picture)) { - file_unmanaged_delete($account->picture); - } - $edit['picture'] = ''; - } - if (isset($edit['roles'])) { - $edit['roles'] = array_filter($edit['roles']); - } -} - /** * Cancel a user account. *