diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index abe2829c1eacbb7e06454311c51046987eef5109..4b880e8c3ceaf57e5adb2575e835a0a2a9ff2872 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -1067,6 +1067,10 @@ table.custom-data .headline { img.avatar { border-radius: inherit; } +.avatar > img.avatar { + width: 100%; + height: 100%; +} .thread-entry .header { padding: 8px 0.9em; border: 1px solid #ccc; diff --git a/avatar.php b/avatar.php new file mode 100644 index 0000000000000000000000000000000000000000..77c0a7fbeb566b8b3b91226a1d254a2d64b2c0d1 --- /dev/null +++ b/avatar.php @@ -0,0 +1,36 @@ +<?php +/********************************************************************* + avatar.php + + Simple download utility for internally-generated avatars + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2014 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: +**********************************************************************/ +require('client.inc.php'); + +if (!isset($_GET['uid']) || !isset($_GET['mode'])) + Http::response(400, '`uid` and `mode` parameters are required'); + +require_once INCLUDE_DIR . 'class.avatar.php'; + +try { + $ra = new RandomAvatar($_GET['mode']); + $avatar = $ra->makeAvatar($_GET['uid']); + + Http::response(200, false, 'image/png', false); + Http::cacheable($_GET['uid'], false, 86400); + imagepng($avatar, null, 1); + imagedestroy($avatar); + exit; +} +catch (InvalidArgumentException $ex) { + Http::response(422, 'No such avatar image set'); +} diff --git a/images/avatar-sprite-ateam.png b/images/avatar-sprite-ateam.png new file mode 100644 index 0000000000000000000000000000000000000000..0e16f01f7505840da760de8fcd46057f731a7aad Binary files /dev/null and b/images/avatar-sprite-ateam.png differ diff --git a/images/mystery-oscar.png b/images/mystery-oscar.png new file mode 100644 index 0000000000000000000000000000000000000000..3aa3b83b69ec8d1b18f1b0f9676cc62d818e6795 Binary files /dev/null and b/images/mystery-oscar.png differ diff --git a/include/ajax.staff.php b/include/ajax.staff.php index e3fec3a653dea884cce34c13ee19bf84881c5a3b..5356e9ccf6c52efb3f550da04c6cb3fb3ced1d75 100644 --- a/include/ajax.staff.php +++ b/include/ajax.staff.php @@ -206,4 +206,27 @@ class StaffAjaxAPI extends AjaxController { include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; } + + function setAvatar($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if ($id != $thisstaff->getId() && !$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if ($id == $thisstaff->getId()) + $staff = $thisstaff; + else + $staff = Staff::lookup((int) $id); + + if (!($avatar = $staff->getAvatar())) + Http::response(404, 'User does not have an avatar'); + + if ($code = $avatar->toggle()) + return $this->encode(array( + 'img' => (string) $avatar, + // XXX: This is very inflexible + 'code' => $code, + )); + } } diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php index 6cdf27629d9a8def65e593f2768d47c41a80a1e0..2a644269fb759f74b6e3a0077fee9a3459882234 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..c328855a1abd3a6bb2b914bbaa587dec471507f0 --- /dev/null +++ b/include/class.avatar.php @@ -0,0 +1,238 @@ +<?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); + + function getImageTag($size=null) { + return '<img class="avatar" alt="'.__('Avatar').'" src="'.$this->getUrl($size).'" />'; + } + + function __toString() { + return $this->getImageTag(); + } + + function isChangeable() { + return false; + } + function toggle() {} +} + +abstract class AvatarSource { + static $id; + static $name; + var $mode; + + function __construct($mode=null) { + if (isset($mode)) + $this->mode = $mode; + } + + 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 LocalAvatarSource +extends AvatarSource { + static $id = 'local'; + static $name = /* @trans */ 'Built-In'; + var $mode = 'ateam'; + + static function getModes() { + return array( + 'ateam' => __("Oscar's A-Team"), + ); + } + + function getAvatar($user) { + return new LocalAvatar($user, $this->mode); + } +} +AvatarSource::register('LocalAvatarSource'); + +class LocalAvatar +extends Avatar { + var $mode; + var $code; + + function __construct($user, $mode) { + parent::__construct($user); + $this->mode = $mode; + } + + function getUrl($size) { + if (false && ($file = $this->user->getAvatarFile())) + return $file->getDownloadUrl(); + + $code = $this->code; + if (!$code && method_exists($this->user, 'getExtraAttr')) + $code = $this->user->getExtraAttr('avatar'); + + if ($code) + $uid = md5($code); + else + // Generate a random string of 0-6 chars for the avatar signature + $uid = md5(strtolower($this->user->getEmail())); + + return ROOT_PATH . 'avatar.php?'.Http::build_query(array('uid'=>$uid, + 'mode' => $this->mode)); + } + + function toggle() { + $this->code = Misc::randCode(21); + return $this->code; + } + + function isChangeable() { + return true; + } +} + +class RandomAvatar { + var $mode; + + static $sprites = array( + 'ateam' => array( + 'file' => 'images/avatar-sprite-ateam.png', + 'grid' => 96, + ), + ); + + function __construct($mode) { + $this->mode = $mode; + } + + function makeAvatar($uid) { + $sprite = self::$sprites[$this->mode]; + if (!$sprite || !is_readable(ROOT_DIR . $sprite['file']) || !extension_loaded('gd')) + Http::redirect(ROOT_PATH.'images/mystery-oscar.png'); + + $source = imagecreatefrompng(ROOT_DIR . $sprite['file']); + $grid = $sprite['grid']; + $avatar = imagecreatetruecolor($grid, $grid); + $width = imagesx($source) / $grid; + $height = imagesy($source) / $grid; + + // Start with a white matte + $white = imagecolorallocate($avatar, 255, 255, 255); + imagefill($avatar, 0, 0, $white); + + for ($i=0, $k=$height; $i<$k; $i++) { + $idx = hexdec($uid[$i]) % $width; + imagecopy($avatar, $source, 0, 0, $idx*$grid, $i*$grid, $grid, $grid); + } + + return $avatar; + } +} + +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', + ); + } + + 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; + } +} 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.http.php b/include/class.http.php index 5e14ee932bb2acbd013b348083a4176a13fc42cb..2fd09a8d70f7aa37041bd8b57597216024cc5cc7 100644 --- a/include/class.http.php +++ b/include/class.http.php @@ -37,10 +37,15 @@ class Http { header('HTTP/1.1 '.Http::header_code_verbose($code)); header('Status: '.Http::header_code_verbose($code)."\r\n"); header("Connection: Close\r\n"); - header("Content-Type: $contentType; charset=$charset\r\n"); - header('Content-Length: '.strlen($content)."\r\n\r\n"); - print $content; - exit; + $ct = "Content-Type: $contentType"; + if ($charset) + $ct .= "; charset=$charset"; + header($ct); + if ($content) { + header('Content-Length: '.strlen($content)."\r\n\r\n"); + print $content; + exit; + } } function redirect($url,$delay=0,$msg='') { 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..c1ed5a32fdd50d538896b9b03401e5c3d09e2380 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; } @@ -633,6 +622,9 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { $this->lang = $vars['lang']; $this->onvacation = isset($vars['onvacation'])?1:0; + if (isset($vars['avatar_code'])) + $this->setExtraAttr('avatar', $vars['avatar_code']); + if ($errors) return false; 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/profile.inc.php b/include/staff/profile.inc.php index 280c63f2c22f5d69afaa1f6486945241553ea283..85a7a1338fe16f097c3dee80f88fe85886982819 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -17,6 +17,37 @@ if(!defined('OSTSTAFFINC') || !$staff || !$thisstaff) die('Access Denied'); <div class="tab_content" id="account"> <table class="table two-column" width="940" border="0" cellspacing="0" cellpadding="2"> <tbody> + <tr><td colspan="2"><div> + <div class="avatar pull-left" style="margin: 10px 15px; width: 100px; height: 100px;"> +<?php $avatar = $staff->getAvatar(); + echo $avatar; +if ($avatar->isChangeable()) { ?> + <div style="text-align: center"> + <a class="button no-pjax" + href="#ajax.php/staff/<?php echo $staff->getId(); ?>/avatar/change" + onclick="javascript: + event.preventDefault(); + var $a = $(this), + form = $a.closest('form'); + $.ajax({ + url: $a.attr('href').substr(1), + dataType: 'json', + success: function(json) { + if (!json || !json.code) + return; + var code = form.find('[name=avatar_code]'); + if (!code.length) + code = form.append($('<input>').attr({type: 'hidden', name: 'avatar_code'})); + code.val(json.code).trigger('change'); + $a.closest('.avatar').find('img').replaceWith($(json.img)); + } + }); + return false;"><i class="icon-retweet"></i></a> + </div> +<?php +} ?> + </div> + <table class="table two-column" border="0" cellspacing="2" cellpadding="2" style="width:760px"> <tr> <td class="required"><?php echo __('Name'); ?>:</td> <td> @@ -59,6 +90,7 @@ if(!defined('OSTSTAFFINC') || !$staff || !$thisstaff) die('Access Denied'); <div class="error"><?php echo $errors['mobile']; ?></div> </td> </tr> + </table></div></td></tr> </tbody> <!-- ================================================ --> <tbody> diff --git a/include/staff/settings-agents.inc.php b/include/staff/settings-agents.inc.php index bd51beb31f1f1c3e776c1ca8933c09cc216571de..5a7e83230a6a6aaef958b77f6b7780684b477c3c 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}\">{$class::getName()} / {$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/staff.inc.php b/include/staff/staff.inc.php index 9a5ad3d296bd20e7c7dd67da71d0958d534679d3..020e281622c359deb25df76694bc9440ea3a1bfa 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -57,6 +57,11 @@ else { <div class="tab_content" id="account"> <table class="table two-column" width="940" border="0" cellspacing="0" cellpadding="2"> <tbody> + <tr><td colspan="2"><div> + <div class="avatar pull-left" style="width: 100px; margin: 10px;"> + <?php echo $staff->getAvatar(); ?> + </div> + <table class="table two-column" border="0" cellspacing="2" cellpadding="2" style="width: 760px"> <tr> <td class="required"><?php echo __('Name'); ?>:</td> <td> @@ -99,6 +104,7 @@ else { <div class="error"><?php echo $errors['mobile']; ?></div> </td> </tr> + </table></div></td></tr> </tbody> <!-- ================================================ --> <tbody> 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/include/staff/templates/user.tmpl.php b/include/staff/templates/user.tmpl.php index 9400e4f93de8ed5ea0f96d5ffca19ba6a170f0a4..b1debfd90653b08fb1669c8a410c09460af73630 100644 --- a/include/staff/templates/user.tmpl.php +++ b/include/staff/templates/user.tmpl.php @@ -16,7 +16,9 @@ if ($info['error']) { echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); } ?> <div id="user-profile" style="display:<?php echo $forms ? 'none' : 'block'; ?>;margin:5px;"> - <i class="icon-user icon-4x pull-left icon-border"></i> + <div class="avatar pull-left" style="margin: 0 10px;"> + <?php echo $user->getAvatar(); ?> + </div> <?php if ($ticket) { ?> <a class="action-button pull-right change-user" style="overflow:inherit" diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php index 7c894ca6dc2fc6ad74657bc3c99f3c5c4cb6ae9b..4d02b3d66d166a9be7965ace9e6cdeab3ff9a72c 100644 --- a/include/staff/user-view.inc.php +++ b/include/staff/user-view.inc.php @@ -77,7 +77,10 @@ $org = $user->getOrganization(); </td> </tr> </table> -<table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> +<div class="avatar pull-left" style="margin: 10px; width: 80px;"> + <?php echo $user->getAvatar(); ?> +</div> +<table class="ticket_info" cellspacing="0" cellpadding="0" width="830" border="0"> <tr> <td width="50%"> <table border="0" cellspacing="" cellpadding="4" width="100%"> diff --git a/scp/ajax.php b/scp/ajax.php index 5df6b35b966831f028f5e6c2e59c6653e91a55b1..c20995c61baabea70fe934dd849ee0f96d0b40b5 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -245,7 +245,8 @@ $dispatcher = patterns('', url('^/(?P<id>\d+)/change-password$', 'changePassword'), url_get('^/(?P<id>\d+)/perms', 'getAgentPerms'), url('^/reset-permissions', 'resetPermissions'), - url('^/change-department', 'changeDepartment') + url('^/change-department', 'changeDepartment'), + url('^/(?P<id>\d+)/avatar/change', 'setAvatar') )) ); diff --git a/scp/css/scp.css b/scp/css/scp.css index 37b30ef848068e9dbc9b087bd3204de4ea960b27..690ebe063ee4eac9b98c19e32dbc825e258cfe95 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -875,7 +875,9 @@ h2 .reload { display:inline-block; width:48px; height:auto; - border-radius: 5px; +} +.avatar { + border-radius: 12%; } .thread-entry.message > .avatar { margin-left: initial; @@ -884,6 +886,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 +2529,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 +2565,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 +2629,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 0568c60641f4faae00b866043f304aee5e6aeb59..32c18ca7c19fc887daaa3cfdd362f1f73c931660 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -146,11 +146,11 @@ var scp_prep = function() { } }; - $("form#save :input[name]").change(function() { + $("form#save").on('change', ':input[name]', function() { if (!$(this).is('.nowarn')) warnOnLeave($(this)); }); - $("form#save :input[type=reset]").click(function() { + $("form#save").on('change', ':input[type=reset]', function() { var fObj = $(this).closest('form'); if(fObj.data('changed')){ $('input[type=submit]', fObj).removeClass('save pending'); @@ -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>')