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>')