/include/class.client.php
PHP | 481 lines | 331 code | 89 blank | 61 comment | 63 complexity | 740d9c58b8797c6e45374f169203bb05 MD5 | raw file
- <?php
- /*********************************************************************
- class.client.php
- Handles everything about EndUser
- Peter Rotich <peter@osticket.com>
- Copyright (c) 2006-2013 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_once INCLUDE_DIR.'class.user.php';
- abstract class TicketUser
- implements EmailContact, ITicketUser, TemplateVariable {
- static private $token_regex = '/^(?P<type>\w{1})(?P<algo>\d+)x(?P<hash>.*)$/i';
- protected $user;
- protected $_guest = false;
- function __construct($user) {
- $this->user = $user;
- }
- function __call($name, $args) {
- global $cfg;
- $rv = null;
- if($this->user && is_callable(array($this->user, $name)))
- $rv = $args
- ? call_user_func_array(array($this->user, $name), $args)
- : call_user_func(array($this->user, $name));
- return $rv ?: false;
- }
- // Required for Internationalization::getCurrentLanguage() in templates
- function getLanguage() {
- return $this->user->getLanguage();
- }
- static function getVarScope() {
- return array(
- 'email' => __('Email Address'),
- 'name' => array('class' => 'PersonsName', 'desc' => __('Full Name')),
- 'ticket_link' => __('Link to view the ticket'),
- );
- }
- function getVar($tag) {
- global $cfg;
- switch (strtolower($tag)) {
- case 'ticket_link':
- $qstr = array();
- if ($cfg && $cfg->isAuthTokenEnabled()
- && ($ticket=$this->getTicket()))
- $qstr['auth'] = $ticket->getAuthToken($this);
- return sprintf('%s/view.php?%s',
- $cfg->getBaseUrl(),
- Http::build_query($qstr, false)
- );
- break;
- }
- }
- function getId() { return ($this->user) ? $this->user->getId() : null; }
- function getEmail() { return ($this->user) ? $this->user->getEmail() : null; }
- static function lookupByToken($token) {
- //Expecting well formatted token see getAuthToken routine for details.
- $matches = array();
- if (!preg_match(static::$token_regex, $token, $matches))
- return null;
- //Unpack the user and ticket ids
- $matches +=unpack('Vuid/Vtid',
- Base32::decode(strtolower(substr($matches['hash'], 0, 13))));
- $user = null;
- if (!($ticket = Ticket::lookup($matches['tid'])))
- // Require a ticket for now
- return null;
- switch ($matches['type']) {
- case 'c': //Collaborator c
- if (($user = Collaborator::lookup($matches['uid']))
- && $user->getTicketId() != $matches['tid'])
- $user = null;
- break;
- case 'o': //Ticket owner
- if (($user = $ticket->getOwner())
- && $user->getId() != $matches['uid']) {
- $user = null;
- }
- break;
- }
- if (!$user
- || !$user instanceof ITicketUser
- || strcasecmp($ticket->getAuthToken($user, $matches['algo']), $token))
- return false;
- return $user;
- }
- static function lookupByEmail($email) {
- if (!($user=User::lookup(array('emails__address' => $email))))
- return null;
- return new EndUser($user);
- }
- function isOwner() {
- return $this instanceof TicketOwner;
- }
- function flagGuest() {
- $this->_guest = true;
- }
- function isGuest() {
- return $this->_guest;
- }
- function getUserId() {
- return $this->user->getId();
- }
- abstract function getTicketId();
- abstract function getTicket();
- }
- class TicketOwner extends TicketUser {
- protected $ticket;
- function __construct($user, $ticket) {
- parent::__construct($user);
- $this->ticket = $ticket;
- }
- function getTicket() {
- return $this->ticket;
- }
- function getTicketId() {
- return $this->ticket->getId();
- }
- }
- /*
- * Decorator class for authenticated user
- *
- */
- class EndUser extends BaseAuthenticatedUser {
- protected $user;
- protected $_account = false;
- protected $_stats;
- protected $topic_stats;
- function __construct($user) {
- $this->user = $user;
- }
- /*
- * Delegate calls to the user
- */
- function __call($name, $args) {
- if(!$this->user
- || !is_callable(array($this->user, $name)))
- return $this->getVar(substr($name, 3));
- return $args
- ? call_user_func_array(array($this->user, $name), $args)
- : call_user_func(array($this->user, $name));
- }
- function getVar($tag) {
- $u = $this;
- // Traverse the $user properties of all nested user objects to get
- // to the User instance with the custom data
- while (isset($u->user)) {
- $u = $u->user;
- if (method_exists($u, 'getVar')) {
- if ($rv = $u->getVar($tag))
- return $rv;
- }
- }
- }
- function getId() {
- //We ONLY care about user ID at the ticket level
- if ($this->user instanceof Collaborator)
- return $this->user->getUserId();
- elseif ($this->user)
- return $this->user->getId();
- return false;
- }
- function getUserName() {
- //XXX: Revisit when real usernames are introduced or when email
- // requirement is removed.
- return $this->user->getEmail();
- }
- function getUserType() {
- return $this->isOwner() ? 'owner' : 'collaborator';
- }
- function getAuthBackend() {
- list($authkey,) = explode(':', $this->getAuthKey());
- return UserAuthenticationBackend::getBackend($authkey);
- }
- function getTicketStats() {
- if (!isset($this->_stats))
- $this->_stats = $this->getStats();
- return $this->_stats;
- }
- function getNumTickets($forMyOrg=false, $state=false) {
- $stats = $this->getTicketStats();
- $count = 0;
- $section = $forMyOrg ? 'myorg' : 'mine';
- foreach ($stats[$section] as $row) {
- if ($state && $row['status__state'] != $state)
- continue;
- $count += $row['count'];
- }
- return $count;
- }
- function getNumOpenTickets($forMyOrg=false) {
- return $this->getNumTickets($forMyOrg, 'open') ?: 0;
- }
- function getNumClosedTickets($forMyOrg=false) {
- return $this->getNumTickets($forMyOrg, 'closed') ?: 0;
- }
- function getNumTopicTickets($topic_id, $forMyOrg=false) {
- $stats = $this->getTicketStats();
- $section = $forMyOrg ? 'myorg' : 'mine';
- if (!isset($this->topic_stats)) {
- $this->topic_stats = array();
- foreach ($stats[$section] as $row) {
- $this->topic_stats[$row['topic_id']] += $row['count'];
- }
- }
- return $this->topic_stats[$topic_id];
- }
- function getNumTopicTicketsInState($topic_id, $state=false, $forMyOrg=false) {
- $stats = $this->getTicketStats();
- $count = 0;
- $section = $forMyOrg ? 'myorg' : 'mine';
- foreach ($stats[$section] as $row) {
- if ($topic_id != $row['topic_id'])
- continue;
- if ($state && $state != $row['status__state'])
- continue;
- $count += $row['count'];
- }
- return $count;
- }
- function getNumOrganizationTickets() {
- return $this->getNumTickets(true);
- }
- function getNumOpenOrganizationTickets() {
- return $this->getNumTickets(true, 'open');
- }
- function getNumClosedOrganizationTickets() {
- return $this->getNumTickets(true, 'closed');
- }
- function getAccount() {
- if ($this->_account === false)
- $this->_account =
- ClientAccount::lookup(array('user_id'=>$this->getId()));
- return $this->_account;
- }
- function getLanguage($flags=false) {
- if ($acct = $this->getAccount())
- return $acct->getLanguage($flags);
- }
- private function getStats() {
- $basic = Ticket::objects()
- ->annotate(array('count' => SqlAggregate::COUNT('ticket_id')))
- ->values('status__state', 'topic_id')
- ->distinct('status_id', 'topic_id');
- // Share tickets among the organization for owners only
- $mine = clone $basic;
- $collab = clone $basic;
- $mine->filter(array(
- 'user_id' => $this->getId(),
- ));
- // Also add collaborator tickets to the list. This may seem ugly;
- // but the general rule for SQL is that a single query can only use
- // one index. Therefore, to scan two indexes (by user_id and
- // thread.collaborators.user_id), we need two queries. A union will
- // help out with that.
- $mine->union($collab->filter(array(
- 'thread__collaborators__user_id' => $this->getId(),
- Q::not(array('user_id' => $this->getId()))
- )));
- if ($orgid = $this->getOrgId()) {
- // Also generate a separate query for all the tickets owned by
- // either my organization or ones that I'm collaborating on
- // which are not part of the organization.
- $myorg = clone $basic;
- $myorg->values('user__org_id');
- $collab = clone $myorg;
- $myorg->filter(array('user__org_id' => $orgid));
- $myorg->union($collab->filter(array(
- 'thread__collaborators__user_id' => $this->getId(),
- Q::not(array('user__org_id' => $orgid))
- )));
- }
- return array('mine' => $mine, 'myorg' => $myorg);
- }
- function onLogin($bk) {
- if ($account = $this->getAccount())
- $account->onLogin($bk);
- }
- }
- class ClientAccount extends UserAccount {
- function checkPassword($password, $autoupdate=true) {
- /*bcrypt based password match*/
- if(Passwd::cmp($password, $this->get('passwd')))
- return true;
- //Fall back to MD5
- if(!$password || strcmp($this->get('passwd'), MD5($password)))
- return false;
- //Password is a MD5 hash: rehash it (if enabled) otherwise force passwd change.
- if ($autoupdate)
- $this->set('passwd', Passwd::hash($password));
- if (!$autoupdate || !$this->save())
- $this->forcePasswdReset();
- return true;
- }
- function hasCurrentPassword($password) {
- return $this->checkPassword($password, false);
- }
- function cancelResetTokens() {
- // TODO: Drop password-reset tokens from the config table for
- // this user id
- $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset"
- AND `value`='.db_input('c'.$this->getUserId());
- if (!db_query($sql, false))
- return false;
- unset($_SESSION['_client']['reset-token']);
- }
- function onLogin($bk) {
- $this->setExtraAttr('browser_lang',
- Internationalization::getCurrentLanguage());
- $this->save();
- }
- function update($vars, &$errors) {
- global $cfg;
- // FIXME: Updates by agents should go through UserAccount::update()
- global $thisstaff;
- if ($thisstaff)
- return parent::update($vars, $errors);
- $rtoken = $_SESSION['_client']['reset-token'];
- if ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd'] || $rtoken) {
- if (!$vars['passwd1'])
- $errors['passwd1']=__('New password is required');
- elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
- $errors['passwd1']=__('Password must be at least 6 characters');
- elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
- $errors['passwd2']=__('Passwords do not match');
- if ($rtoken) {
- $_config = new Config('pwreset');
- if ($_config->get($rtoken) != 'c'.$this->getUserId())
- $errors['err'] =
- __('Invalid reset token. Logout and try again');
- elseif (!($ts = $_config->lastModified($rtoken))
- && ($cfg->getPwResetWindow() < (time() - strtotime($ts))))
- $errors['err'] =
- __('Invalid reset token. Logout and try again');
- }
- elseif ($this->get('passwd')) {
- if (!$vars['cpasswd'])
- $errors['cpasswd']=__('Current password is required');
- elseif (!$this->hasCurrentPassword($vars['cpasswd']))
- $errors['cpasswd']=__('Invalid current password!');
- elseif (!strcasecmp($vars['passwd1'], $vars['cpasswd']))
- $errors['passwd1']=__('New password MUST be different from the current password!');
- }
- }
- // Timezone selection is not required. System default is a valid
- // fallback
- if ($errors) return false;
- $this->set('timezone', $vars['timezone']);
- $this->set('dst', isset($vars['dst']) ? 1 : 0);
- // Change language
- $this->set('lang', $vars['lang'] ?: null);
- Internationalization::setCurrentLanguage(null);
- TextDomain::configureForUser($this);
- if ($vars['backend']) {
- $this->set('backend', $vars['backend']);
- if ($vars['username'])
- $this->set('username', $vars['username']);
- }
- if ($vars['passwd1']) {
- $this->set('passwd', Passwd::hash($vars['passwd1']));
- $info = array('password' => $vars['passwd1']);
- Signal::send('auth.pwchange', $this->getUser(), $info);
- $this->cancelResetTokens();
- $this->clearStatus(UserAccountStatus::REQUIRE_PASSWD_RESET);
- }
- return $this->save();
- }
- }
- // Used by the email system
- interface EmailContact {
- // function getId()
- // function getName()
- // function getEmail()
- }
- interface ITicketUser {
- /* PHP 5.3 < 5.3.8 will crash with some abstract inheritance issue
- * ------------------------------------------------------------
- function isOwner();
- function flagGuest();
- function isGuest();
- function getUserId();
- function getTicketId();
- function getTicket();
- */
- }
- ?>