diff --git a/admin/settings/users.php b/admin/settings/users.php
index 8ea2265..52324ff 100644
--- a/admin/settings/users.php
+++ b/admin/settings/users.php
@@ -16,6 +16,7 @@ $ADMIN->add('users', new admin_category('accounts', get_string('accounts', 'admi
 $ADMIN->add('accounts', new admin_externalpage('editusers', get_string('userlist','admin'), "$CFG->wwwroot/$CFG->admin/user.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 0000000..b81e288
--- /dev/null
+++ b/admin/uploadpicture.php
@@ -0,0 +1,209 @@
+<?php // $Id$
+
+///////////////////////////////////////////////////////////////////////////
+//                                                                       //
+// Copyright (C) 2007 Iņaki Arenaza                                      //
+//                                                                       //
+// Based on .../admin/uploaduser.php and .../lib/gdlib.php               //
+//                                                                       //
+// This program is free software; you can redistribute it and/or modify  //
+// it under the terms of the GNU General Public License as published by  //
+// the Free Software Foundation; either version 2 of the License, or     //
+// (at your option) any later version.                                   //
+//                                                                       //
+// This program is distributed in the hope that it will be useful,       //
+// but WITHOUT ANY WARRANTY; without even the implied warranty of        //
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
+// GNU General Public License for more details:                          //
+//                                                                       //
+//          http://www.gnu.org/copyleft/gpl.html                         //
+//                                                                       //
+///////////////////////////////////////////////////////////////////////////
+
+require_once('../config.php');
+require_once($CFG->libdir.'/uploadlib.php');
+require_once($CFG->libdir.'/adminlib.php');
+require_once($CFG->libdir.'/gdlib.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($adminroot);
+
+/// If a file has been uploaded, then process it
+$um = new upload_manager('userpicturesfile',false,false,null,false,0);
+
+if (!array_key_exists($userfield, $userfields)) {
+    notify(get_string('uploadpicture_baduserfield','admin'));
+} else {
+    if ($um->preprocess_files() && confirm_sesskey()) {
+        $filename = $um->files['userpicturesfile']['tmp_name'];
+        
+        // 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');
+        
+        // We are sure the destination file name is unique (even if we
+        // always name it userpics.zip) because we are using a unique
+        // temporary directory to place it.
+        $dstfile = $zipdir.'/userpics.zip';
+        if (!move_uploaded_file($filename, $dstfile)) {
+            notify(get_string('uploadpicture_cannotmovezip','admin'));
+        } else {
+            if(!unzip_file($dstfile, $zipdir, false)) {
+                notify(get_string('uploadpicture_cannotunzip','admin'));
+            } 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 '<hr />';
+            }
+        }
+    }
+}
+
+/// Print the form
+print_heading_with_help($struploadpictures, 'uploadpictures');
+
+$noyesoptions = array( get_string('no'), get_string('yes') );
+
+$maxuploadsize = get_max_upload_file_size();
+echo '<div style="text-align:center">';
+echo '<form method="post" enctype="multipart/form-data" action="uploadpicture.php"><div>'.
+$strfile.'&nbsp;<input type="hidden" name="MAX_FILE_SIZE" value="'.$maxuploadsize.'" />'.
+'<input type="hidden" name="sesskey" value="'.$USER->sesskey.'" />'.
+'<input type="file" name="userpicturesfile" size="30" /><br />';
+print_heading(get_string('settings'));
+echo '<table style="margin-left:auto;margin-right:auto">';
+echo '<tr><td>' . get_string('uploadpicture_userfield', 'admin') . '</td><td>';
+choose_from_menu ($userfields, 'userfiled', $userfield);
+echo '</td></tr>';
+echo '<tr><td>' . get_string ('uploadpicture_overwrite', 'admin') . '</td><td>';
+choose_from_menu($noyesoptions, 'overwritepicture', $overwritepicture);
+echo '</td></tr>';
+echo '</table><br />';
+echo '<input type="submit" value="'.$struploadpictures.'" />';
+echo '</div></form><br />';
+echo '</div>';
+
+admin_externalpage_print_footer($adminroot);
+
+
+// ----------- 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/lang/en_utf8/admin.php b/lang/en_utf8/admin.php
index 29c8f98..3fe25ed 100644
--- a/lang/en_utf8/admin.php
+++ b/lang/en_utf8/admin.php
@@ -557,6 +557,18 @@ $string['unsupported'] = 'Unsupported';
 $string['updateaccounts'] = 'Update existing accounts';
 $string['updatecomponent'] = 'Update Component';
 $string['updatelangs'] = 'Update all local 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.<br />To use this functionality you need to <a href=\"$a\">update your tables</a>.';
 $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.<br /><br />Do you want to start the upgrading process now?';
diff --git a/lib/gdlib.php b/lib/gdlib.php
index 29f8628..e1f228b 100644
--- a/lib/gdlib.php
+++ b/lib/gdlib.php
@@ -72,27 +72,18 @@ function ImageCopyBicubic ($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $
 }
 
 /** 
- * 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.
+ * Given a user or group id and a destination directory, it creates the user 
+ * or group directory needed to store its associated profile image, and returns
+ * the full path to that directory.
  *
  * @uses $CFG
- * @param int $id description?
- * @param object $uploadmanager description?
- * @param string $dir description?
- * @return boolean
- * @todo Finish documenting this function
+ * @param int $id user or group id
+ * @param string $dir 'user' or group directory name
+ * @return string $destination (profile image destination directory 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)) {
@@ -112,12 +103,56 @@ 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
+ * @todo Finish documenting this function
+ */
+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);
     
@@ -133,7 +168,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 {
@@ -142,7 +177,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 {
@@ -151,7 +186,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 15c35ed..052bd91 100644
--- a/lib/moodlelib.php
+++ b/lib/moodlelib.php
@@ -6478,7 +6478,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($showstatus) {
+                notice($archive->errorInfo(true));
+            }
             return false;
         }
 
