diff --git a/include/class.client.php b/include/class.client.php
index 486cf6e74576d3d6581d2b729eb3bb8939b74955..2e8f6e32ccb26a0c2cf931bf01a04ea6bec2052b 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -2,10 +2,7 @@
 /*********************************************************************
     class.client.php
 
-    Handles everything about client
-
-    XXX: Please note that osTicket uses email address and ticket ID to authenticate the user*!
-          Client is modeled on the info of the ticket used to login .
+    Handles everything about EndUser
 
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
@@ -16,229 +13,117 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
+abstract class TicketUser {
 
-class Client {
-
-    var $id;
-    var $fullname;
-    var $username;
-    var $email;
-
-    var $_answers;
-
-    var $ticket_id;
-    var $ticketID;
-
-    var $ht;
-
-    var $user;
-
-
-    function Client($id, $email=null) {
-        $this->id =0;
-        $this->load($id,$email);
-    }
-
-    function load($id=0, $email=null) {
-
-        if(!$id && !($id=$this->getId()))
-            return false;
-
-        $sql='SELECT ticket.ticket_id, ticketID, email.address as email '
-            .' FROM '.TICKET_TABLE.' ticket '
-            .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
-            .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
-            .' WHERE ticketID='.db_input($id);
-
-        if($email)
-            $sql.=' AND email.address = '.db_input($email);
-
-        if(!($res=db_query($sql)) || !db_num_rows($res))
-            return NULL;
-
-        $this->ht = db_fetch_array($res);
-        $this->id         = $this->ht['ticketID']; //placeholder
-        $this->ticket_id  = $this->ht['ticket_id'];
-        $this->ticketID   = $this->ht['ticketID'];
+    static private $token_regex = '/^(?P<type>\w{1})(?P<id>\d+)t(?P<tid>\d+)x(?P<algo>\d+)h(?P<hash>.*)$/i';
 
-        $this->user = User::lookup(array('emails__address'=>$this->ht['email']));
-        $this->fullname   = $this->user->getFullName();
-        $this->username   = $this->ht['email'];
-        $this->email      = $this->ht['email'];
-
-        $this->stats = array();
-
-        return($this->id);
-    }
-
-    function loadDynamicData() {
-        $this->_answers = array();
-        foreach (DynamicFormEntry::forClient($this->getId()) as $form)
-            foreach ($form->getAnswers() as $answer)
-                $this->_answers[$answer->getField()->get('name')] =
-                    $answer->getValue();
-    }
+    protected  $user;
 
-    function reload() {
-        return $this->load();
+    function __construct($user) {
+        $this->user = $user;
     }
 
-    function isClient() {
-        return TRUE;
-    }
+    function __call($name, $args) {
+        global $cfg;
+
+        if($this->user && is_callable(array($this->user, $name)))
+            return  $args
+                ? call_user_func_array(array($this->user, $name), $args)
+                : call_user_func(array($this->user, $name));
+
+        $tag =  substr($name, 3);
+        switch (strtolower($tag)) {
+            case 'ticket_link':
+                return sprintf('%s/view.php?auth=%s',
+                        $cfg->getBaseUrl(),
+                        urlencode($this->getAuthToken()));
+                break;
+        }
 
-    function getId() {
-        return $this->id;
-    }
+        return false;
 
-    function getEmail() {
-        return $this->email;
     }
 
-    function getUserName() {
-        return $this->username;
-    }
+    protected function getAuthToken($type=1) {
 
-    function getName() {
-        return $this->fullname;
-    }
+        //Format: // c<id>x<algo id used>h<hash for algo>
+        $authtoken = '';
+        switch($type) {
+            case 1:
+                $authtoken = sprintf('%s%dt%dx%dh%s',
+                        ($this->isOwner() ? 'o' : 'c'),
+                        $this->getId(),
+                        $this->getTicketId(),
+                        $type,
+                        substr(base64_encode(md5($this->getId().$this->getTicket()->getCreateDate().$this->getTicketId().SECRET_SALT,
+                                    true)), 16));
+                break;
+        }
 
-    function getPhone() {
-        return $this->_answers['phone'];
-    }
+        //TODO: Throw an exception
+        if (!$authtoken)
+            return false;
 
-    function getTicketID() {
-        return $this->ticketID;
-    }
+        return $authtoken;
+    }
+
+    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;
+
+        $user = null;
+        switch ($matches['type']) {
+            case 'c': //Collaborator c
+                if (($user = Collaborator::lookup($matches['id']))
+                        && $user->getTicketId() != $matches['tid'])
+                    $user = null;
+                break;
+            case 'o': //Ticket owner
+                if (($ticket = Ticket::lookup($matches['tid']))) {
+                    if (($user = $ticket->getOwner())
+                            && $user->getId() != $matches['id'])
+                        $user = null;
+                }
+                break;
+        }
 
-    function getTicketStats() {
+        if (!$user
+                || !$user instanceof TicketUser
+                || strcasecmp($user->getAuthToken($matches['algo']), $token))
+            return false;
 
-        if(!$this->stats['tickets'])
-            $this->stats['tickets'] = Ticket::getClientStats($this->getEmail());
+        var_dump($user);
 
-        return $this->stats['tickets'];
+        return $user;
     }
 
-    function getNumTickets() {
-        return ($stats=$this->getTicketStats())?($stats['open']+$stats['closed']):0;
+    function isOwner() {
+        return  ($this->user
+                    && $this->user->getId() == $this->getTicket()->getOwnerId());
     }
 
-    function getNumOpenTickets() {
-        return ($stats=$this->getTicketStats())?$stats['open']:0;
-    }
+    abstract function getTicketId();
+    abstract function getTicket();
+}
 
-    function getNumClosedTickets() {
-        return ($stats=$this->getTicketStats())?$stats['closed']:0;
-    }
+class TicketOwner extends  TicketUser {
 
-    /* ------------- Static ---------------*/
-    function getLastTicketIdByEmail($email) {
-        $sql='SELECT ticket.ticketID FROM '.TICKET_TABLE.' ticket '
-            .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id'
-            .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id'
-            .' WHERE email.address = '.db_input($email)
-            .' ORDER BY ticket.created '
-            .' LIMIT 1';
-        if(($res=db_query($sql)) && db_num_rows($res))
-            list($tid) = db_fetch_row($res);
-
-        return $tid;
-    }
+    protected $ticket;
 
-    function lookup($id, $email=null) {
-        return ($id && is_numeric($id) && ($c=new Client($id,$email)) && $c->getId()==$id)?$c:null;
+    function __construct($user, $ticket) {
+        parent::__construct($user);
+        $this->ticket = $ticket;
     }
 
-    function lookupByEmail($email) {
-        return (($id=self::getLastTicketIdByEmail($email)))?self::lookup($id, $email):null;
+    function getTicket() {
+        return $this->ticket;
     }
 
-    /* static */ function login($ticketID, $email, $auth=null, &$errors=array()) {
-        global $ost;
-
-
-        $cfg = $ost->getConfig();
-        $auth = trim($auth);
-        $email = trim($email);
-        $ticketID = trim($ticketID);
-
-        # Only consider auth token for GET requests, and for GET requests,
-        # REQUIRE the auth token
-        $auto_login = ($_SERVER['REQUEST_METHOD'] == 'GET');
-
-        //Check time for last max failed login attempt strike.
-        if($_SESSION['_client']['laststrike']) {
-            if((time()-$_SESSION['_client']['laststrike'])<$cfg->getClientLoginTimeout()) {
-                $errors['login'] = 'Excessive failed login attempts';
-                $errors['err'] = 'You\'ve reached maximum failed login attempts allowed. Try again later or <a href="open.php">open a new ticket</a>';
-                $_SESSION['_client']['laststrike'] = time(); //renew the strike.
-            } else { //Timeout is over.
-                //Reset the counter for next round of attempts after the timeout.
-                $_SESSION['_client']['laststrike'] = null;
-                $_SESSION['_client']['strikes'] = 0;
-            }
-        }
-
-        if($auto_login && !$auth)
-            $errors['login'] = 'Invalid method';
-        elseif(!$ticketID || !Validator::is_email($email))
-            $errors['login'] = 'Valid email and ticket number required';
-
-        //Bail out on error.
-        if($errors) return false;
-
-        //See if we can fetch local ticket id associated with the ID given
-        if(($ticket=Ticket::lookupByExtId($ticketID, $email)) && $ticket->getId()) {
-            //At this point we know the ticket ID is valid.
-            //TODO: 1) Check how old the ticket is...3 months max?? 2) Must be the latest 5 tickets??
-            //Check the email given.
-
-            # Require auth token for automatic logins (GET METHOD).
-            if (!strcasecmp($ticket->getEmail(), $email) && (!$auto_login || $auth === $ticket->getAuthToken())) {
-
-                //valid match...create session goodies for the client.
-                $user = new ClientSession($email,$ticket->getExtId());
-                $_SESSION['_client'] = array(); //clear.
-                $_SESSION['_client']['userID'] = $ticket->getEmail(); //Email
-                $_SESSION['_client']['key'] = $ticket->getExtId(); //Ticket ID --acts as password when used with email. See above.
-                $_SESSION['_client']['token'] = $user->getSessionToken();
-                $_SESSION['TZ_OFFSET'] = $cfg->getTZoffset();
-                $_SESSION['TZ_DST'] = $cfg->observeDaylightSaving();
-                $user->refreshSession(); //set the hash.
-                //Log login info...
-                $msg=sprintf('%s/%s logged in [%s]', $ticket->getEmail(), $ticket->getExtId(), $_SERVER['REMOTE_ADDR']);
-                $ost->logDebug('User login', $msg);
-
-                //Regenerate session ID.
-                $sid=session_id(); //Current session id.
-                session_regenerate_id(TRUE); //get new ID.
-                if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id())
-                    $session->destroy($sid);
-
-                return $user;
-
-            }
-        }
-
-        //If we get to this point we know the login failed.
-        $errors['login'] = 'Invalid login';
-        $_SESSION['_client']['strikes']+=1;
-        if(!$errors && $_SESSION['_client']['strikes']>$cfg->getClientMaxLogins()) {
-            $errors['login'] = 'Access Denied';
-            $errors['err'] = 'Forgot your login info? Please <a href="open.php">open a new ticket</a>.';
-            $_SESSION['_client']['laststrike'] = time();
-            $alert='Excessive login attempts by a user.'."\n".
-                    'Email: '.$email."\n".'Ticket#: '.$ticketID."\n".
-                    'IP: '.$_SERVER['REMOTE_ADDR']."\n".'Time:'.date('M j, Y, g:i a T')."\n\n".
-                    'Attempts #'.$_SESSION['_client']['strikes'];
-            $ost->logError('Excessive login attempts (user)', $alert, ($cfg->alertONLoginError()));
-        } elseif($_SESSION['_client']['strikes']%2==0) { //Log every other failed login attempt as a warning.
-            $alert='Email: '.$email."\n".'Ticket #: '.$ticketID."\n".'IP: '.$_SERVER['REMOTE_ADDR'].
-                   "\n".'TIME: '.date('M j, Y, g:i a T')."\n\n".'Attempts #'.$_SESSION['_client']['strikes'];
-            $ost->logWarning('Failed login attempt (user)', $alert);
-        }
-
-        return false;
+    function getTicketId() {
+        return $this->ticket->getId();
     }
 }
 
@@ -260,7 +145,6 @@ class  EndUser extends AuthenticatedUser {
      */
     function __call($name, $args) {
 
-
         if(!$this->user
                 || !is_callable(array($this->user, $name)))
             return false;
@@ -278,10 +162,6 @@ class  EndUser extends AuthenticatedUser {
         return $this->user->getId();
     }
 
-    function isOwner() {
-        return  ($this->user && $this->user instanceof Client);
-    }
-
     function getUserName() {
         //XXX: Revisit when real usernames are introduced  or when email
         // requirement is removed.
@@ -296,6 +176,43 @@ class  EndUser extends AuthenticatedUser {
         return UserAuthenticationBackend::signOut($this);
     }
 
-}
+    function getTicketStats() {
+
+        if (!isset($this->ht['stats']))
+            $this->ht['stats'] = $this->getStats();
 
+        return $this->ht['stats'];
+    }
+
+    function getNumTickets() {
+        return ($stats=$this->getTicketStats())?($stats['open']+$stats['closed']):0;
+    }
+
+    function getNumOpenTickets() {
+        return ($stats=$this->getTicketStats())?$stats['open']:0;
+    }
+
+    function getNumClosedTickets() {
+        return ($stats=$this->getTicketStats())?$stats['closed']:0;
+    }
+
+    private function getStats() {
+
+        $sql='SELECT count(open.ticket_id) as open, count(closed.ticket_id) as closed '
+            .' FROM '.TICKET_TABLE.' ticket '
+            .' LEFT JOIN '.TICKET_TABLE.' open
+                ON (open.ticket_id=ticket.ticket_id AND open.status=\'open\') '
+            .' LEFT JOIN '.TICKET_TABLE.' closed
+                ON (closed.ticket_id=ticket.ticket_id AND closed.status=\'closed\')'
+            .' LEFT JOIN '.TICKET_COLLABORATOR_TABLE.' collab
+                ON (collab.ticket_id=ticket.ticket_id
+                    AND collab.user_id = '.db_input($this->getId()).' )'
+            .' WHERE ticket.user_id = '.db_input($this->getId())
+            .' OR collab.user_id = '.db_input($this->getId());
+
+        return db_fetch_array(db_query($sql));
+    }
+
+
+}
 ?>