diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index c458db823946cfaab8f59808c426975a84908361..92d66c08ae6f0cd46d5a1f932cae2a7474f3ea6d 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -116,7 +116,7 @@ class TicketsAjaxAPI extends AjaxController {
             if ($lock->getStaffId() != $thisstaff->getId())
                 return $this->json_encode(array('id'=>0, 'retry'=>false,
                     'msg' => sprintf(__('Currently locked by %s'),
-                        $lock->getStaffName())
+                        $lock->getStaff()->getAvatarAndName())
                     ));
 
             //Ticket already locked by staff...try renewing it.
@@ -152,7 +152,7 @@ class TicketsAjaxAPI extends AjaxController {
             // user doesn't own the lock anymore??? sorry...try to next time.
             Http::response(403, $this->encode(array('id'=>0, 'retry'=>false,
                 'msg' => sprintf(__('Currently locked by %s'),
-                    $lock->getStaffName())
+                    $lock->getStaff->getAvatarAndName())
             ))); //Give up...
 
         // Ensure staff still has access
diff --git a/include/class.avatar.php b/include/class.avatar.php
new file mode 100644
index 0000000000000000000000000000000000000000..7adefad798c989e40d268eebf376cf1b18b55e5c
--- /dev/null
+++ b/include/class.avatar.php
@@ -0,0 +1,135 @@
+<?php
+/*********************************************************************
+    class.avatar.php
+
+    Avatar sources for users and agents
+
+    Jared Hancock <jared@osticket.com>
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2006-2015 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+abstract class Avatar {
+    var $user;
+
+    function __construct($user) {
+        $this->user = $user;
+    }
+
+    abstract function getUrl($size);
+    abstract function __toString();
+}
+
+abstract class AvatarSource {
+    static $id;
+    static $name;
+
+    function getName() {
+        return __(static::$name);
+    }
+
+    abstract function getAvatar($user);
+
+    static $registry = array();
+    static function register($class) {
+        if (!class_exists($class))
+            throw new Exception($class.': Does not exist');
+        if (!isset($class::$id))
+            throw new Exception($class.': AvatarClass must specify $id');
+        static::$registry[$class::$id] = $class;
+    }
+
+    static function lookup($id, $mode=null) {
+        $class = static::$registry[$id];
+        if (!isset($class))
+            ; // TODO: Return built-in avatar source
+        if (is_string($class))
+            $class = static::$registry[$id] = new $class($mode);
+        return $class;
+    }
+
+    static function allSources() {
+        return static::$registry;
+    }
+
+    static function getModes() {
+        return null;
+    }
+}
+
+class AvatarsByGravatar
+extends AvatarSource {
+    static $name = 'Gravatar';
+    static $id = 'gravatar';
+    var $mode;
+
+    function __construct($mode=null) {
+        $this->mode = $mode ?: 'retro';
+    }
+
+    static function getModes() {
+        return array(
+            'mm' => __('Mystery Man'),
+            'identicon' => 'Identicon',
+            'monsterid' => 'Monster',
+            'wavatar' => 'Wavatar',
+            'retro' => 'Retro',
+            'blank' => 'Blank',
+        );
+    }
+
+    function getAvatar($user) {
+        return new Gravatar($user, $this->mode);
+    }
+}
+AvatarSource::register('AvatarsByGravatar');
+
+class Gravatar
+extends Avatar {
+    var $email;
+    var $d;
+    var $size;
+
+    function __construct($user, $imageset) {
+        $this->email = $user->getEmail();
+        $this->d = $imageset;
+    }
+
+    function setSize($size) {
+        $this->size = $size;
+    }
+
+    /**
+     * Get either a Gravatar URL or complete image tag for a specified email address.
+     *
+     * @param string $email The email address
+     * @param string $s Size in pixels, defaults to 80px [ 1 - 2048 ]
+     * @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
+     * @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
+     * @param boole $img True to return a complete IMG tag False for just the URL
+     * @param array $atts Optional, additional key/value attributes to include in the IMG tag
+     * @return String containing either just a URL or a complete image tag
+     * @source http://gravatar.com/site/implement/images/php/
+     */
+    function getUrl($size=null) {
+        $size = $this->size ?: 80;
+        $url = '//www.gravatar.com/avatar/';
+        $url .= md5( strtolower( $this->email ) );
+        $url .= "?s=$size&d={$this->d}";
+        return $url;
+    }
+
+    function getImageTag($size=null) {
+        return '<img class="avatar" alt="'.__('Avatar').'" src="'.$this->getUrl($size).'" />';
+    }
+
+    function __toString() {
+        return $this->getImageTag();
+    }
+}
diff --git a/include/class.config.php b/include/class.config.php
index 1c2de449977114090433b6733c3c1893b7c8c62f..5a169199d1cca71c2f269c59620ec619ffcfee22 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -173,6 +173,8 @@ class OsticketConfig extends Config {
         'help_topic_sort_mode' => 'a',
         'client_verify_email' => 1,
         'verify_email_addrs' => 1,
+        'client_avatar' => 'gravatar.mm',
+        'agent_avatar' => 'gravatar.mm',
     );
 
     function OsticketConfig($section=null) {
@@ -405,6 +407,18 @@ class OsticketConfig extends Config {
         return $this->get('staff_max_logins');
     }
 
+    function getStaffAvatarSource() {
+        require_once INCLUDE_DIR . 'class.avatar.php';
+        list($source, $mode) = explode('.', $this->get('agent_avatar'), 2);
+        return AvatarSource::lookup($source, $mode);
+    }
+
+    function getClientAvatarSource() {
+        require_once INCLUDE_DIR . 'class.avatar.php';
+        list($source, $mode) = explode('.', $this->get('client_avatar'), 2);
+        return AvatarSource::lookup($source, $mode);
+    }
+
     function getLockTime() {
         return $this->get('autolock_minutes');
     }
@@ -1099,6 +1113,11 @@ class OsticketConfig extends Config {
         $f['pw_reset_window']=array('type'=>'int', 'required'=>1, 'min'=>1,
             'error'=>__('Valid password reset window required'));
 
+        require_once INCLUDE_DIR.'class.avatar.php';
+        list($avatar_source) = explode('.', $vars['agent_avatar']);
+        if (!AvatarSource::lookup($avatar_source))
+            $errors['agent_avatar'] = __('Select a value from the list');
+
         if(!Validator::process($f, $vars, $errors) || $errors)
             return false;
 
@@ -1111,7 +1130,7 @@ class OsticketConfig extends Config {
             'allow_pw_reset'=>isset($vars['allow_pw_reset'])?1:0,
             'pw_reset_window'=>$vars['pw_reset_window'],
             'agent_name_format'=>$vars['agent_name_format'],
-
+            'agent_avatar'=>$vars['agent_avatar'],
         ));
     }
 
@@ -1119,6 +1138,11 @@ class OsticketConfig extends Config {
         $f=array();
         $f['client_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
 
+        require_once INCLUDE_DIR.'class.avatar.php';
+        list($avatar_source) = explode('.', $vars['client_avatar']);
+        if (!AvatarSource::lookup($avatar_source))
+            $errors['client_avatar'] = __('Select a value from the list');
+
         if(!Validator::process($f, $vars, $errors) || $errors)
             return false;
 
@@ -1130,7 +1154,7 @@ class OsticketConfig extends Config {
             'client_registration'=>$vars['client_registration'],
             'client_verify_email'=>isset($vars['client_verify_email'])?1:0,
             'client_name_format'=>$vars['client_name_format'],
-
+            'client_avatar'=>$vars['client_avatar'],
         ));
     }
 
diff --git a/include/class.lock.php b/include/class.lock.php
index acbe1365e2c3f8fa2dbb64c4f4f39b227c418890..1a758a46877260111f21b4a06c5a9d73a2bebb44 100644
--- a/include/class.lock.php
+++ b/include/class.lock.php
@@ -50,6 +50,10 @@ class Lock extends VerySimpleModel {
         return $this->staff->getName();
     }
 
+    function getStaff() {
+        return $this->staff;
+    }
+
     function getCreateTime() {
         return $this->created;
     }
diff --git a/include/class.staff.php b/include/class.staff.php
index ed93f6441f7115cda215f6555a6544e39c989c48..407ab13e20290dbdd4ca6b7f958f20f9344bd4ce 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -240,29 +240,14 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
     function getEmail() {
         return $this->email;
     }
-    /**
-     * Get either a Gravatar URL or complete image tag for a specified email address.
-     *
-     * @param string $email The email address
-     * @param string $s Size in pixels, defaults to 80px [ 1 - 2048 ]
-     * @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
-     * @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
-     * @param boole $img True to return a complete IMG tag False for just the URL
-     * @param array $atts Optional, additional key/value attributes to include in the IMG tag
-     * @return String containing either just a URL or a complete image tag
-     * @source http://gravatar.com/site/implement/images/php/
-     */
-    function get_gravatar($s = 80, $img = false, $atts = array(), $d = 'retro', $r = 'g' ) {
-        $url = '//www.gravatar.com/avatar/';
-        $url .= md5( strtolower( $this->getEmail() ) );
-        $url .= "?s=$s&d=$d&r=$r";
-        if ( $img ) {
-            $url = '<img src="' . $url . '"';
-            foreach ( $atts as $key => $val )
-                $url .= ' ' . $key . '="' . $val . '"';
-            $url .= ' />';
-        }
-        return $url;
+
+    function getAvatar($size=null) {
+        global $cfg;
+        $source = $cfg->getStaffAvatarSource();
+        $avatar = $source->getAvatar($this);
+        if (isset($size))
+            $avatar->setSize($size);
+        return $avatar;
     }
 
     function getUserName() {
@@ -277,6 +262,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
         return new AgentsName(array('first' => $this->ht['firstname'], 'last' => $this->ht['lastname']));
     }
 
+    function getAvatarAndName() {
+        return $this->getAvatar().Format::htmlchars((string) $this->getName());
+    }
+
     function getFirstName() {
         return $this->firstname;
     }
diff --git a/include/class.thread.php b/include/class.thread.php
index 81fb47aed86b942e0ffe61a3de87b8a6bc5a51ad..c3649b280f4ed3c4b1677704a97baa479b434e1e 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -1555,11 +1555,11 @@ class ThreadEvent extends VerySimpleModel {
 
     var $_data;
 
-    function getAvatar($size=16) {
+    function getAvatar($size=null) {
         if ($this->uid && $this->uid_type == 'S')
-            return $this->agent->get_gravatar($size);
+            return $this->agent->getAvatar($size);
         if ($this->uid && $this->uid_type == 'U')
-            return $this->user->get_gravatar($size);
+            return $this->user->getAvatar($size);
     }
 
     function getUserName() {
@@ -1601,9 +1601,9 @@ class ThreadEvent extends VerySimpleModel {
                 case 'assignees':
                     $assignees = array();
                     if ($S = $self->staff) {
-                        $url = $S->get_gravatar(16);
+                        $avatar = $S->getAvatar();
                         $assignees[] =
-                            "<img class=\"avatar\" src=\"{$url}\"> ".$S->getName();
+                            $avatar.$S->getName();
                     }
                     if ($T = $self->team) {
                         $assignees[] = $T->getLocalName();
@@ -1611,8 +1611,8 @@ class ThreadEvent extends VerySimpleModel {
                     return implode('/', $assignees);
                 case 'somebody':
                     $name = $self->getUserName();
-                    if ($url = $self->getAvatar())
-                        $name = "<img class=\"avatar\" src=\"{$url}\"> ".$name;
+                    if ($avatar = $self->getAvatar())
+                        $name = $avatar.$name;
                     return $name;
                 case 'timestamp':
                     return sprintf('<time class="relative" datetime="%s" title="%s">%s</time>',
@@ -1622,8 +1622,8 @@ class ThreadEvent extends VerySimpleModel {
                     );
                 case 'agent':
                     $name = $self->agent->getName();
-                    if ($url = $self->getAvatar())
-                        $name = "<img class=\"avatar\" src=\"{$url}\"> ".$name;
+                    if ($avatar = $self->getAvatar())
+                        $name = $avatar.$name;
                     return $name;
                 case 'dept':
                     if ($dept = $self->getDept())
diff --git a/include/class.user.php b/include/class.user.php
index 16279f76eb834247940fc8f96f2e7807af497f64..79370c0fcb4cced661dbe4c9d149b98790bbc200 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -267,29 +267,11 @@ implements TemplateVariable {
     function getEmail() {
         return new EmailAddress($this->default_email->address);
     }
-    /**
-     * Get either a Gravatar URL or complete image tag for a specified email address.
-     *
-     * @param string $email The email address
-     * @param string $s Size in pixels, defaults to 80px [ 1 - 2048 ]
-     * @param string $d Default imageset to use [ 404 | mm | identicon | monsterid | wavatar ]
-     * @param string $r Maximum rating (inclusive) [ g | pg | r | x ]
-     * @param boole $img True to return a complete IMG tag False for just the URL
-     * @param array $atts Optional, additional key/value attributes to include in the IMG tag
-     * @return String containing either just a URL or a complete image tag
-     * @source http://gravatar.com/site/implement/images/php/
-     */
-    function get_gravatar($s = 80, $img = false, $atts = array(), $d = 'retro', $r = 'g' ) {
-        $url = '//www.gravatar.com/avatar/';
-        $url .= md5( strtolower( $this->default_email->address ) );
-        $url .= "?s=$s&d=$d&r=$r";
-        if ( $img ) {
-            $url = '<img src="' . $url . '"';
-            foreach ( $atts as $key => $val )
-                $url .= ' ' . $key . '="' . $val . '"';
-            $url .= ' />';
-        }
-        return $url;
+
+    function getAvatar() {
+        global $cfg;
+        $source = $cfg->getClientAvatarSource();
+        return $source->getAvatar($this);
     }
 
     function getFullName() {
diff --git a/include/client/templates/thread-entry.tmpl.php b/include/client/templates/thread-entry.tmpl.php
index fbad6983cbe25791ec07af612fbd243e7280b4b1..9e42b053a841fbd971eaef0165ae29cc79b4c283 100644
--- a/include/client/templates/thread-entry.tmpl.php
+++ b/include/client/templates/thread-entry.tmpl.php
@@ -3,8 +3,8 @@ $entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note');
 $user = $entry->getUser() ?: $entry->getStaff();
 $name = $user ? $user->getName() : $entry->poster;
 $avatar = '';
-if ($user && ($url = $user->get_gravatar(48)))
-    $avatar = "<img class=\"avatar\" src=\"{$url}\"> ";
+if ($user)
+    $avatar = $user->getAvatar();
 ?>
 
 <div class="thread-entry <?php echo $entryTypes[$entry->type]; ?> <?php if ($avatar) echo 'avatar'; ?>">
diff --git a/include/staff/settings-agents.inc.php b/include/staff/settings-agents.inc.php
index bd51beb31f1f1c3e776c1ca8933c09cc216571de..05d07d1e28f67e04b538eb941b5adc22abe50234 100644
--- a/include/staff/settings-agents.inc.php
+++ b/include/staff/settings-agents.inc.php
@@ -35,6 +35,31 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config
                 <i class="help-tip icon-question-sign" href="#agent_name_format"></i>
             </td>
         </tr>
+        <tr>
+            <td width="180"><?php echo __('Avatar Source'); ?>:</td>
+            <td>
+                <select name="agent_avatar">
+<?php           require_once INCLUDE_DIR . 'class.avatar.php';
+                foreach (AvatarSource::allSources() as $id=>$class) {
+                    $modes = $class::getModes();
+                    if ($modes) {
+                        echo "<optgroup label=\"{$class::getName()}\">";
+                        foreach ($modes as $mid=>$mname) {
+                            $oid = "$id.$mid";
+                            $selected = ($config['agent_avatar'] == $oid) ? 'selected="selected"' : '';
+                            echo "<option {$selected} value=\"{$oid}\">{$mname}</option>";
+                        }
+                        echo "</optgroup>";
+                    }
+                    else {
+                        $selected = ($config['agent_avatar'] == $id) ? 'selected="selected"' : '';
+                        echo "<option {$selected} value=\"{$id}\">{$class::getName()}</option>";
+                    }
+                } ?>
+                </select>
+                <div class="error"><?php echo Format::htmlchars($errors['agent_avatar']); ?></div>
+            </td>
+        </tr>
         <tr>
             <th colspan="2">
                 <em><b><?php echo __('Authentication Settings'); ?></b></em>
diff --git a/include/staff/settings-users.inc.php b/include/staff/settings-users.inc.php
index 62f4161df6ef88142aa8605c47af8261b383ee10..61840a4da806934960218a496709b96e6205af4c 100644
--- a/include/staff/settings-users.inc.php
+++ b/include/staff/settings-users.inc.php
@@ -36,6 +36,31 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
                 <i class="help-tip icon-question-sign" href="#client_name_format"></i>
             </td>
         </tr>
+        <tr>
+            <td width="180"><?php echo __('Avatar Source'); ?>:</td>
+            <td>
+                <select name="client_avatar">
+<?php           require_once INCLUDE_DIR . 'class.avatar.php';
+                foreach (AvatarSource::allSources() as $id=>$class) {
+                    $modes = $class::getModes();
+                    if ($modes) {
+                        echo "<optgroup label=\"{$class::getName()}\">";
+                        foreach ($modes as $mid=>$mname) {
+                            $oid = "$id.$mid";
+                            $selected = ($config['client_avatar'] == $oid) ? 'selected="selected"' : '';
+                            echo "<option {$selected} value=\"{$oid}\">{$mname}</option>";
+                        }
+                        echo "</optgroup>";
+                    }
+                    else {
+                        $selected = ($config['client_avatar'] == $id) ? 'selected="selected"' : '';
+                        echo "<option {$selected} value=\"{$id}\">{$class::getName()}</option>";
+                    }
+                } ?>
+                </select>
+                <div class="error"><?php echo Format::htmlchars($errors['client_avatar']); ?></div>
+            </td>
+        </tr>
         <tr>
             <th colspan="2">
                 <em><b><?php echo __('Authentication Settings'); ?></b></em>
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index 62cd3e3362509d15f13faee81508f9b51060059d..8df932b07640c958f3762217ba50b71322904413 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -3,8 +3,8 @@ $entryTypes = array('M'=>'message', 'R'=>'response', 'N'=>'note');
 $user = $entry->getUser() ?: $entry->getStaff();
 $name = $user ? $user->getName() : $entry->poster;
 $avatar = '';
-if ($user && ($url = $user->get_gravatar(48)))
-    $avatar = "<img class=\"avatar\" src=\"{$url}\"> ";
+if ($user)
+    $avatar = $user->getAvatar();
 
 ?>
 <div class="thread-entry <?php echo $entryTypes[$entry->type]; ?> <?php if ($avatar) echo 'avatar'; ?>">
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 37b30ef848068e9dbc9b087bd3204de4ea960b27..313041d7364c96d15a80ae1522519dbf50339b89 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -884,6 +884,10 @@ h2 .reload {
 img.avatar {
     border-radius: inherit;
 }
+.avatar > img.avatar {
+    width: 100%;
+    height: 100%;
+}
 .thread-entry .header {
     padding: 8px 0.9em;
     border: 1px solid #ccc;
@@ -2523,7 +2527,7 @@ td.indented {
   top: auto;
   box-shadow: 0 -3px 10px rgba(0,0,0,0.2);
 }
-.message.bar .avatar {
+.message.bar .avatar[class*=" oscar-"] {
   display: inline-block;
   width: 36px;
   height: 36px;
@@ -2559,6 +2563,13 @@ td.indented {
   border-bottom: none;
   border-top: 3px solid red;
 }
+.message.bar .title .avatar {
+    width: auto;
+    max-height: 20px;
+    border-radius: 3px;
+    margin: -4px 0.3em 0;
+    vertical-align: middle;
+}
 
 #thread-items::before {
   border-left: 2px dotted #ddd;
@@ -2616,7 +2627,7 @@ td.indented {
     vertical-align: middle;
     border-radius: 3px;
     width: auto;
-    max-height: 24px;
+    max-height: 20px;
     margin: -3px 3px 0;
 }
 .thread-event .description {
diff --git a/scp/js/scp.js b/scp/js/scp.js
index 9d72b96378aa90bebc0560349d0f42f9133fe45b..f5a6f26abac192dc504a34cc03d9590c20437d36 100644
--- a/scp/js/scp.js
+++ b/scp/js/scp.js
@@ -772,16 +772,17 @@ $.uid = 1;
       buttonText: __('OK'),
       classes: '',
       dismissible: true,
+      html: false,
       onok: null,
-      position: 'top'
+      position: 'top',
     };
 
     this.show = function(title, message, options) {
       this.hide();
       options = $.extend({}, this.defaults, options);
       var bar = this.bar = $(options.bar).addClass(options.classes)
-        .append($('<div class="title"></div>').text(title))
-        .append($('<div class="body"></div>').text(message))
+        .append($('<div class="title"></div>').html(title))
+        .append($('<div class="body"></div>').html(message))
         .addClass(options.position);
       if (options.avatar)
         bar.prepend($('<div class="avatar pull-left" title="Oscar"></div>')