diff --git a/admin/settings/users.php b/admin/settings/users.php index cd3060dcf3d78..aafbfffd2f64f 100644 --- a/admin/settings/users.php +++ b/admin/settings/users.php @@ -70,6 +70,7 @@ $ADMIN->add('accounts', new admin_externalpage('userbulk', get_string('userbulk','admin'), "$CFG->wwwroot/$CFG->admin/user/user_bulk.php", array('moodle/user:update', 'moodle/user:delete'))); $ADMIN->add('accounts', new admin_externalpage('addnewuser', get_string('addnewuser'), "$securewwwroot/user/editadvanced.php?id=-1", 'moodle/user:create')); $ADMIN->add('accounts', new admin_externalpage('uploadusers', get_string('uploadusers'), "$CFG->wwwroot/$CFG->admin/uploaduser.php", 'moodle/site:uploadusers')); + $ADMIN->add('accounts', new admin_externalpage('uploadpictures', get_string('uploadpictures','admin'), "$CFG->wwwroot/$CFG->admin/uploadpicture.php", 'moodle/site:uploadusers')); $ADMIN->add('accounts', new admin_externalpage('profilefields', get_string('profilefields','admin'), "$CFG->wwwroot/user/profile/index.php", 'moodle/site:config')); diff --git a/admin/uploadpicture.php b/admin/uploadpicture.php new file mode 100644 index 0000000000000..c785b02ac0c1b --- /dev/null +++ b/admin/uploadpicture.php @@ -0,0 +1,181 @@ +libdir.'/uploadlib.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/gdlib.php'); +require_once('uploadpicture_form.php'); + +$adminroot = admin_get_root(); + +admin_externalpage_setup('uploadpictures', $adminroot); + +require_login(); + +require_capability('moodle/site:uploadusers', get_context_instance(CONTEXT_SYSTEM, SITEID)); + +if (!$site = get_site()) { + error("Could not find site-level course"); +} + +if (!$adminuser = get_admin()) { + error("Could not find site admin"); +} + +$strfile = get_string('file'); +$struser = get_string('user'); +$strusersupdated = get_string('usersupdated'); +$struploadpictures = get_string('uploadpictures','admin'); +$usersupdated = 0; +$userserrors = 0; + +$userfields = array ( + 0 => 'username', + 1 => 'idnumber', + 2 => 'id' ); + +$userfield = optional_param('userfield', 0, PARAM_INT); +$overwritepicture = optional_param('overwritepicture', 0, PARAM_BOOL); + +/// Print the header +admin_externalpage_print_header(); +print_heading_with_help($struploadpictures, 'uploadpictures'); + +$mform = new admin_uploadpicture_form(); +if ($formdata = $mform->get_data()) { + if (!array_key_exists($userfield, $userfields)) { + notify(get_string('uploadpicture_baduserfield','admin')); + } else { + // Large files are likely to take their time and memory. Let PHP know + // that we'll take longer, and that the process should be recycled soon + // to free up memory. + @set_time_limit(0); + @raise_memory_limit("192M"); + if (function_exists('apache_child_terminate')) { + @apache_child_terminate(); + } + + // Create a unique temporary directory, to process the zip file + // contents. + $zipdir = my_mktempdir($CFG->dataroot.'/temp/', 'usrpic'); + + if (!$mform->save_files($zipdir)) { + notify(get_string('uploadpicture_cannotmovezip','admin')); + @remove_dir($zipdir); + } else { + $dstfile = $zipdir.'/'.$mform->get_new_filename(); + if(!unzip_file($dstfile, $zipdir, false)) { + notify(get_string('uploadpicture_cannotunzip','admin')); + @remove_dir($zipdir); + } else { + // We don't need the zip file any longer, so delete it to make + // it easier to process the rest of the files inside the directory. + @unlink($dstfile); + if(! ($handle = opendir($zipdir))) { + notify(get_string('uploadpicture_cannotprocessdir','admin')); + } else { + while (false !== ($item = readdir($handle))) { + if($item != '.' && $item != '..' && is_file($zipdir.'/'.$item)) { + + // Add additional checks on the filenames, as they are user + // controlled and we don't want to open any security holes. + $path_parts = pathinfo(cleardoubleslashes($item)); + $basename = $path_parts['basename']; + $extension = $path_parts['extension']; + if ($basename != clean_param($basename, PARAM_CLEANFILE)) { + // The original picture file name has invalid characters + notify(get_string('uploadpicture_invalidfilename', 'admin', + clean_param($basename, PARAM_CLEANHTML))); + continue; + } + + // The picture file name (without extension) must match the + // userfield attribute. + $uservalue = substr($basename, 0, + strlen($basename) - + strlen($extension) - 1); + // userfield names are safe, so don't quote them. + if (!($user = get_record('user', $userfields[$userfield], + addslashes($uservalue)))) { + $userserrors++; + $a = new Object(); + $a->userfield = clean_param($userfields[$userfield], PARAM_CLEANHTML); + $a->uservalue = clean_param($uservalue, PARAM_CLEANHTML); + notify(get_string('uploadpicture_usernotfound', 'admin', $a)); + continue; + } + $haspicture = get_field('user', 'picture', 'id', $user->id); + if ($haspicture && !$overwritepicture) { + notify(get_string('uploadpicture_userskipped', 'admin', $user->username)); + continue; + } + if (my_save_profile_image($user->id, $zipdir.'/'.$item)) { + set_field('user', 'picture', 1, 'id', $user->id); + $usersupdated++; + notify(get_string('uploadpicture_userupdated', 'admin', $user->username)); + } else { + $userserrors++; + notify(get_string('uploadpicture_cannotsave', 'admin', $user->username)); + } + } + } + } + closedir($handle); + + // Finally remove the temporary directory with all the user images and print some stats. + remove_dir($zipdir); + notify(get_string('usersupdated', 'admin') . ": $usersupdated"); + notify(get_string('errors', 'admin') . ": $userserrors"); + echo '
'; + } + } + } +} +$mform->display(); +admin_externalpage_print_footer(); +exit; + +// ----------- Internal functions ---------------- + +function my_mktempdir($dir, $prefix='', $mode=0700) { + if (substr($dir, -1) != '/') { + $dir .= '/'; + } + + do { + $path = $dir.$prefix.mt_rand(0, 9999999); + } while (!mkdir($path, $mode)); + + return $path; +} + +function my_save_profile_image($id, $originalfile) { + $destination = create_profile_image_destination($id, 'user'); + if ($destination === false) { + return false; + } + + return process_profile_image($originalfile, $destination); +} + +?> diff --git a/admin/uploadpicture_form.php b/admin/uploadpicture_form.php new file mode 100644 index 0000000000000..542477cc0edc8 --- /dev/null +++ b/admin/uploadpicture_form.php @@ -0,0 +1,29 @@ +libdir.'/formslib.php'; + + class admin_uploadpicture_form extends moodleform { + function definition (){ + global $CFG, $USER; + global $userfields; + + $mform =& $this->_form; + + $this->set_upload_manager(new upload_manager('userpicturesfile', false, false, null, false, 0, true, true, false)); + + $mform->addElement('header', 'settingsheader', get_string('upload')); + + $mform->addElement('file', 'userpicturesfile', get_string('file'), 'size="40"'); + $mform->addRule('userpicturesfile', null, 'required'); + + $choices = $userfields; + $mform->addElement('select', 'userfield', get_string('uploadpicture_userfield', 'admin'), $choices); + $mform->setType('userfield', PARAM_INT); + + $choices = array( 0 => get_string('no'), 1 => get_string('yes') ); + $mform->addElement('select', 'overwritepicture', get_string('uploadpicture_overwrite', 'admin'), $choices); + $mform->setType('overwritepicture', PARAM_INT); + + $this->add_action_buttons(false, get_string('uploadpictures', 'admin')); + } + } +?> diff --git a/lang/en_utf8/admin.php b/lang/en_utf8/admin.php index 5f3363afc25d2..fc24a371ab00d 100644 --- a/lang/en_utf8/admin.php +++ b/lang/en_utf8/admin.php @@ -670,6 +670,18 @@ $string['updateaccounts'] = 'Update existing accounts'; $string['updatecomponent'] = 'Update Component'; $string['updatelangs'] = 'Update all installed language packs'; +$string['uploadpictures'] = 'Upload user pictures'; +$string['uploadpicture_baduserfield'] = 'The user attribute specified is not valid. Please, try again.'; +$string['uploadpicture_cannotmovezip'] = 'Cannot move zip file to temporary directory.'; +$string['uploadpicture_cannotprocessdir'] = 'Cannot process unzipped files.'; +$string['uploadpicture_cannotunzip'] = 'Cannot unzip pictures file.'; +$string['uploadpicture_invalidfilename'] = 'Picture file $a has invalid characters in its name. Skipping.'; +$string['uploadpicture_overwrite'] = 'Overwrite existing user pictures?'; +$string['uploadpicture_userfield'] = 'User attribute to use to match pictures:'; +$string['uploadpicture_usernotfound'] = 'User with a \'$a->userfield\' value of \'$a->uservalue\' does not exist. Skipping.'; +$string['uploadpicture_userskipped'] = 'Skipping user $a (already has a picture).'; +$string['uploadpicture_userupdated'] = 'Picture updated for user $a.'; +$string['uploadpicture_cannotsave'] = 'Cannot save picture for user $a. Check original picture file.'; $string['updatetimezones'] = 'Update timezones'; $string['upgradeforumread'] = 'A new feature has been added in Moodle 1.5 to track read/unread forum posts.
To use this functionality you need to update your tables.'; $string['upgradeforumreadinfo'] = 'A new feature has been added in Moodle 1.5 to track read/unread forum posts. To use this functionality you need to update your tables with all the tracking information for existing posts. Depending on the size of your site this can take a long time (hours) and can be quite taxing on the database, so it\'s best to do it during a quiet period. However, your site will continue functioning during this upgrade and users won\'t be affected. Once you start this process you should let it finish (keep your browser window open). However, if you stop the process by closing the window: don\'t worry, you can start over.

Do you want to start the upgrading process now?'; diff --git a/lib/gdlib.php b/lib/gdlib.php index 932806d180253..24c95afbf533a 100644 --- a/lib/gdlib.php +++ b/lib/gdlib.php @@ -4,7 +4,7 @@ * gdlib.php - Collection of routines in Moodle related to * processing images using GD * - * @author ? + * @author Martin Dougiamas etc * @version $Id$ * @license http://www.gnu.org/copyleft/gpl.html GNU Public License * @package moodlecore @@ -95,21 +95,12 @@ function delete_profile_image($id, $dir='users') { * it and saves it in the right place to be a "user" or "group" image. * * @param int $id user or group id - * @param object $uploadmanager object referencing the image * @param string $dir type of entity - groups, user, ... - * @return boolean success + * @return string $destination (profile image destination path) or false on error */ -function save_profile_image($id, $uploadmanager, $dir='user') { +function create_profile_image_destination($id, $dir='user') { global $CFG; - if (empty($CFG->gdversion)) { - return false; - } - - if (!$uploadmanager) { - return false; - } - umask(0000); if (!file_exists($CFG->dataroot .'/'. $dir)) { @@ -129,12 +120,55 @@ function save_profile_image($id, $uploadmanager, $dir='user') { return false; } } + return $destination; +} + +/** + * Given an upload manager with the right settings, this function performs a virus scan, and then scales and crops + * it and saves it in the right place to be a "user" or "group" image. + * + * @param int $id user or group id + * @param object $uploadmanager object referencing the image + * @param string $dir type of entity - groups, user, ... + * @return boolean success + */ +function save_profile_image($id, $uploadmanager, $dir='user') { + + if (!$uploadmanager) { + return false; + } + + $destination = create_profile_image_destination($id, $dir); + if ($destination === false) { + return false; + } if (!$uploadmanager->save_files($destination)) { return false; } - $originalfile = $uploadmanager->get_new_filepath(); + return process_profile_image($uploadmanager->get_new_filepath(), $destination); +} + +/** + * Given a path to an image file this function scales and crops it and saves it in + * the right place to be a "user" or "group" image. + * + * @uses $CFG + * @param string $originalfile the path of the original image file + * @param string $destination the final destination directory of the profile image + * @return boolean + */ +function process_profile_image($originalfile, $destination) { + global $CFG; + + if(!(is_file($originalfile) && is_dir($destination))) { + return false; + } + + if (empty($CFG->gdversion)) { + return false; + } $imageinfo = GetImageSize($originalfile); @@ -150,7 +184,7 @@ function save_profile_image($id, $uploadmanager, $dir='user') { $image->type = $imageinfo[2]; switch ($image->type) { - case 1: + case IMAGETYPE_GIF: if (function_exists('ImageCreateFromGIF')) { $im = ImageCreateFromGIF($originalfile); } else { @@ -159,7 +193,7 @@ function save_profile_image($id, $uploadmanager, $dir='user') { return false; } break; - case 2: + case IMAGETYPE_JPEG: if (function_exists('ImageCreateFromJPEG')) { $im = ImageCreateFromJPEG($originalfile); } else { @@ -168,7 +202,7 @@ function save_profile_image($id, $uploadmanager, $dir='user') { return false; } break; - case 3: + case IMAGETYPE_PNG: if (function_exists('ImageCreateFromPNG')) { $im = ImageCreateFromPNG($originalfile); } else { diff --git a/lib/moodlelib.php b/lib/moodlelib.php index 3d42e9f09274b..afee285ca7d01 100644 --- a/lib/moodlelib.php +++ b/lib/moodlelib.php @@ -7190,7 +7190,9 @@ function unzip_file ($zipfile, $destination = '', $showstatus = true) { if (!$list = $archive->extract(PCLZIP_OPT_PATH, $destpath, PCLZIP_CB_PRE_EXTRACT, 'unzip_cleanfilename', PCLZIP_OPT_EXTRACT_DIR_RESTRICTION, $destpath)) { - notice($archive->errorInfo(true)); + if (!empty($showstatus)) { + notice($archive->errorInfo(true)); + } return false; }