diff --git a/account.php b/account.php
new file mode 100644
index 0000000000000000000000000000000000000000..66de95ecae8e048b0291b248b7c109fdf38daa9b
--- /dev/null
+++ b/account.php
@@ -0,0 +1,117 @@
+<?php
+/*********************************************************************
+    profile.php
+
+    Manage client profile. This will allow a logged-in user to manage
+    his/her own public (non-internal) information
+
+    Peter Rotich <peter@osticket.com>
+    Jared Hancock <jared@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:
+    $Id: $
+**********************************************************************/
+require 'client.inc.php';
+
+$inc = 'register.inc.php';
+
+$errors = array();
+
+if (!$cfg || !$cfg->isClientRegistrationEnabled()) {
+    Http::redirect('index.php');
+}
+
+elseif ($thisclient) {
+    // Guest registering for an account
+    if ($thisclient->isGuest()) {
+        foreach ($thisclient->getForms() as $f)
+            if ($f->get('type') == 'U')
+                $user_form = $f;
+        $user_form->getField('email')->configure('disabled', true);
+    }
+    // Existing client (with an account) updating profile
+    else {
+        $user = User::lookup($thisclient->getId());
+        $content = Page::lookup(Page::getIdByType('registration-thanks'));
+        $inc = isset($_GET['confirmed'])
+            ? 'register.confirmed.inc.php' : 'profile.inc.php';
+    }
+}
+
+if ($user && $_POST) {
+    if ($acct = $thisclient->getAccount()) {
+       $acct->update($_POST, $errors);
+    }
+    if (!$errors && $user->updateInfo($_POST, $errors))
+        Http::redirect('tickets.php');
+}
+
+elseif ($_POST) {
+    $user_form = UserForm::getUserForm()->getForm($_POST);
+    $user_form->getField('email')->configure('disabled', true);
+    if ($thisclient)
+        $user_form->getField('email')->value = $thisclient->getEmail();
+
+    if (!$user_form->isValid(function($f) { return !$f->get('internal'); }))
+        $errors['err'] = 'Incomplete client information';
+    elseif (!$_POST['backend'] && !$_POST['passwd1'])
+        $errors['passwd1'] = 'New password required';
+    elseif (!$_POST['backend'] && $_POST['passwd2'] != $_POST['passwd1'])
+        $errors['passwd1'] = 'Passwords do not match';
+
+    // XXX: The email will always be in use already if a guest is logged in
+    // and is registering for an account. Instead,
+    elseif (($addr = $user_form->getField('email')->getClean())
+            && ClientAccount::lookupByUsername($addr)) {
+        $user_form->getField('email')->addError(
+            'Email already registered. Would you like to <a href="login.php?e='
+            .urlencode($addr).'" style="color:inherit"><strong>sign in</strong></a>?');
+        $errors['err'] = 'Unable to register account. See messages below';
+    }
+    // Users created from ClientCreateRequest
+    elseif (isset($_POST['backend']) && !($user = User::fromVars($user_form->getClean())))
+        $errors['err'] = 'Unable to create local account. See messages below';
+    // New users and users registering from a ticket access link
+    elseif (!$user && !($user = $thisclient ?: User::fromForm($user_form)))
+        $errors['err'] = 'Unable to register account. See messages below';
+    else {
+        if (!($acct = ClientAccount::createForUser($user)))
+            $errors['err'] = 'Internal error. Unable to create new account';
+        elseif (!$acct->update($_POST, $errors))
+            $errors['err'] = 'Errors configuring your profile. See messages below';
+    }
+
+    if (!$errors) {
+        switch ($_POST['do']) {
+        case 'create':
+            $content = Page::lookup(Page::getIdByType('registration-confirm'));
+            $inc = 'register.confirm.inc.php';
+            $acct->sendResetEmail('registration-client');
+            break;
+        case 'import':
+            foreach (UserAuthenticationBackend::allRegistered() as $bk) {
+                if ($bk::$id == $_POST['backend']) {
+                    $cl = new ClientSession(new EndUser($user));
+                    $acct->confirm();
+                    if ($user = $bk->login($cl, $bk))
+                        Http::redirect('tickets.php');
+                    break;
+                }
+            }
+            break;
+        }
+    }
+
+    if ($errors && $user && $user != $thisclient)
+        $user->delete();
+}
+
+include(CLIENTINC_DIR.'header.inc.php');
+include(CLIENTINC_DIR.$inc);
+include(CLIENTINC_DIR.'footer.inc.php');
+
diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css
index c4bd447e8ce924d267453b60b4b0faa764ce5a5f..3c558720ba239b9e2ef28648102d5f5aaa6737b1 100644
--- a/assets/default/css/theme.css
+++ b/assets/default/css/theme.css
@@ -219,6 +219,7 @@ h2 {
   border: 1px solid #a00;
   background: url('../images/icons/error.png') 10px 50% no-repeat #fff0f0;
 }
+#msg_info { margin: 0; padding: 5px; margin-bottom: 10px; color: #3a87ad; border: 1px solid #bce8f1;  background-color: #d9edf7; }
 .warning {
   background: #ffc;
   font-style: italic;
@@ -626,21 +627,21 @@ label.required {
   text-align: left;
 }
 #clientLogin {
-  width: 400px;
+    display: block;
   margin-top: 20px;
-  padding: 10px 100px 10px 10px;
+  padding: 20px;
   border: 1px solid #ccc;
-  background: url('../images/lock.png?1319655200') 440px 50% no-repeat #f6f6f6;
+  border-radius: 5px;
+  box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
+  background: url('../images/lock.png?1319655200') 95% 50% no-repeat #f6f6f6;
 }
 #clientLogin p {
   clear: both;
-  text-align: center;
 }
 #clientLogin strong {
   font-size: 11px;
   color: #d00;
   display: block;
-  padding-left: 140px;
 }
 #clientLogin #email {
   width: 250px;
@@ -870,3 +871,8 @@ a.refresh {
   color: #777;
   text-decoration: none;
 }
+table.padded tr > td,
+table.padded tr > th {
+  height: 20px;
+  padding-bottom: 5px;
+}
diff --git a/bootstrap.php b/bootstrap.php
index 0a50c4a2635b693e575d87a3a13dda9f66e3a997..832fe96e50a6b89f9c7d952c5fa355295fcd8ca5 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -63,13 +63,14 @@ class Bootstrap {
         define('CONFIG_TABLE',$prefix.'config');
 
         define('CANNED_TABLE',$prefix.'canned_response');
-        define('PAGE_TABLE', $prefix.'page');
+        define('PAGE_TABLE', $prefix.'content');
         define('FILE_TABLE',$prefix.'file');
         define('FILE_CHUNK_TABLE',$prefix.'file_chunk');
 
         define('ATTACHMENT_TABLE',$prefix.'attachment');
         define('USER_TABLE',$prefix.'user');
         define('USER_EMAIL_TABLE',$prefix.'user_email');
+        define('USER_ACCOUNT_TABLE',$prefix.'user_account');
 
         define('STAFF_TABLE',$prefix.'staff');
         define('TEAM_TABLE',$prefix.'team');
diff --git a/client.inc.php b/client.inc.php
index ee9a9fcae443df2d4c0ade4636270d102b9955e8..ad4c7ee4e18c12841a46ac239fd0a7b90bd3e597 100644
--- a/client.inc.php
+++ b/client.inc.php
@@ -56,7 +56,7 @@ if($thisclient && $thisclient->getId() && $thisclient->isValid()){
 /******* CSRF Protectin *************/
 // Enforce CSRF protection for POSTS
 if ($_POST  && !$ost->checkCSRFToken()) {
-    @header('Location: index.php');
+    Http::redirect('index.php');
     //just incase redirect fails
     die('Action denied (400)!');
 }
@@ -68,4 +68,13 @@ $ost->addExtraHeader('<meta name="csrf_token" content="'.$ost->getCSRFToken().'"
 define('PAGE_LIMIT', DEFAULT_PAGE_LIMIT);
 
 $nav = new UserNav($thisclient, 'home');
+
+$exempt = in_array(basename($_SERVER['SCRIPT_NAME']), array('logout.php', 'ajax.php', 'logs.php', 'upgrade.php'));
+
+if (!$exempt && $thisclient && ($acct = $thisclient->getAccount())
+        && $acct->isPasswdResetForced()) {
+    $warn = 'Password change required to continue';
+    require('profile.php'); //profile.php must request this file as require_once to avoid problems.
+    exit;
+}
 ?>
diff --git a/include/ajax.content.php b/include/ajax.content.php
index acc238c34e828c414d3be133c836bb82d3f311b7..463248382493cbc58fd1bc302c03ac54c2d4efab 100644
--- a/include/ajax.content.php
+++ b/include/ajax.content.php
@@ -125,5 +125,44 @@ class ContentAjaxAPI extends AjaxController {
             break;
         }
     }
+
+    function manageContent($id, $lang=false) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login Required');
+
+        $content = Page::lookup($id, $lang);
+        include STAFFINC_DIR . 'templates/content-manage.tmpl.php';
+    }
+
+    function manageNamedContent($type, $lang=false) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login Required');
+
+        $content = Page::lookup(Page::getIdByType($type, $lang));
+        include STAFFINC_DIR . 'templates/content-manage.tmpl.php';
+    }
+
+    function updateContent($id) {
+        global $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Login Required');
+        elseif (!$_POST['name'] || !$_POST['body'])
+            Http::response(422, 'Please submit name and body');
+        elseif (!($content = Page::lookup($id)))
+            Http::response(404, 'No such content');
+
+        $vars = array_merge($content->getHashtable(), $_POST);
+        if (!$content->save($id, $vars, $errors)) {
+            if ($errors['err'])
+                Http::response(422, $errors['err']);
+            else
+                Http::response(500, 'Unable to update content: '.print_r($errors, true));
+        }
+    }
 }
 ?>
diff --git a/include/class.auth.php b/include/class.auth.php
index 660acf9ba2aba5946acd40fd2ec07bedea154237..dcde257c8ed86d3a83983fbfb4e7ff5f8bc71164 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -50,6 +50,44 @@ interface AuthDirectorySearch {
     function search($query);
 }
 
+/**
+ * Class: ClientCreateRequest
+ *
+ * Simple container to represent a remote authentication success for a
+ * client which should be imported into the local database. The class will
+ * provide access to the backend that authenticated the user, the username
+ * that the user entered when logging in, and any other information about
+ * the user that the backend was able to lookup. Generally, this extra
+ * information would be the same information retrieved from calling the
+ * AuthDirectorySearch::lookup() method.
+ */
+class ClientCreateRequest {
+
+    var $backend;
+    var $username;
+    var $info;
+
+    function __construct($backend, $username, $info=array()) {
+        $this->backend = $backend;
+        $this->username = $username;
+        $this->info = $info;
+    }
+
+    function getBackend() {
+        return $this->backend;
+    }
+    function setBackend($what) {
+        $this->backend = $what;
+    }
+
+    function getUsername() {
+        return $this->username;
+    }
+    function getInfo() {
+        return $this->info;
+    }
+}
+
 /**
  * Authentication backend
  *
@@ -128,17 +166,26 @@ abstract class AuthenticationBackend {
             // All backends are queried here, even if they don't support
             // authentication so that extensions like lockouts and audits
             // can be supported.
-            $result = $bk->authenticate($username, $password);
-            if ($result instanceof AuthenticatedUser
-                    && ($bk->login($result, $bk)))
-                return $result;
-            elseif ($result instanceof AccessDenied) {
+            try {
+                $result = $bk->authenticate($username, $password);
+                if ($result instanceof AuthenticatedUser
+                        && ($bk->login($result, $bk)))
+                    return $result;
+                elseif ($result instanceof ClientCreateRequest
+                        && $bk instanceof UserAuthenticationBackend)
+                    return $result;
+                elseif ($result instanceof AccessDenied) {
+                    break;
+                }
+            }
+            catch (AccessDenied $e) {
+                $result = $e;
                 break;
             }
         }
 
         if (!$result)
-            $result = new  AccessDenied('Access denied');
+            $result = new AccessDenied('Access denied');
 
         if ($result && $result instanceof AccessDenied)
             $errors['err'] = $result->reason;
@@ -322,6 +369,9 @@ abstract class StaffAuthenticationBackend  extends AuthenticationBackend {
 
         Signal::send('auth.login.succeeded', $staff);
 
+        if ($bk->supportsAuthentication())
+            $staff->cancelResetTokens();
+
         return true;
     }
 
@@ -398,8 +448,20 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
     }
 
     function getAllowedBackends($userid) {
-        // White listing backends for specific user not supported.
-        return array();
+        $backends = array('authtoken');
+        $sql = 'SELECT A1.backend FROM '.USER_ACCOUNT_TABLE
+              .' A1 INNER JOIN '.USER_EMAIL_TABLE.' A2 ON (A2.user_id = A1.user_id)'
+              .' WHERE backend IS NOT NULL '
+              .' AND (A1.username='.db_input($userid)
+                  .' OR A2.`address`='.db_input($userid).')';
+
+        if (!($res=db_query($sql, false)))
+            return $backends;
+
+        while (list($bk) = db_fetch_row($res))
+            $backends[] = $bk;
+
+        return array_filter($backends);
     }
 
     function login($user, $bk) {
@@ -410,6 +472,15 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
                 || !($authkey = $bk->getAuthKey($user)))
             return false;
 
+        $acct = $user->getAccount();
+
+        if ($acct) {
+            if (!$acct->isConfirmed())
+                throw new AccessDenied('Account confirmation required');
+            elseif ($acct->isLocked())
+                throw new AccessDenied('Account is administratively locked');
+        }
+
         //Tag the authkey.
         $authkey = $bk::$id.':'.$authkey;
 
@@ -419,8 +490,6 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
         $authsession = array(); //clear.
         $authsession['id'] = $user->getId();
         $authsession['key'] = $authkey;
-        $_SESSION['TZ_OFFSET'] = $ost->getConfig()->getTZoffset();
-        $_SESSION['TZ_DST'] = $ost->getConfig()->observeDaylightSaving();
 
         //The backend used decides the format of the auth key.
         // XXX: encrypt to hide the bk??
@@ -433,6 +502,9 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
                 $user->getUserName(), $user->getId(), $_SERVER['REMOTE_ADDR']);
         $ost->logDebug('User login', $msg);
 
+        if ($bk->supportsAuthentication() && ($acct=$user->getAccount()))
+            $acct->cancelResetTokens();
+
         return true;
     }
 
@@ -451,7 +523,7 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
     }
 
     protected function getAuthKey($user) {
-        return  $user->getUsername();
+        return  $user->getId();
     }
 
     static function getUser() {
@@ -463,7 +535,6 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
         list($id, $auth) = explode(':', $_SESSION['_auth']['user']['key']);
 
         if (!($bk=static::getBackend($id)) //get the backend
-                || !$bk->supportsAuthentication() //Make sure it can authenticate
                 || !($user=$bk->validate($auth)) //Get AuthicatedUser
                 || !($user instanceof AuthenticatedUser) // Make sure it user
                 || $user->getId() != $_SESSION['_auth']['user']['id'] // check ID
@@ -474,14 +545,24 @@ abstract class UserAuthenticationBackend  extends AuthenticationBackend {
 
         return $user;
     }
+
+    protected function validate($userid) {
+        if (!($user = User::lookup($userid)))
+            return false;
+        elseif (!$user->getAccount())
+            return false;
+
+        return new ClientSession(new EndUser($user));
+    }
 }
 
 /**
  * This will be an exception in later versions of PHP
  */
-class AccessDenied {
+class AccessDenied extends Exception {
     function __construct($reason) {
         $this->reason = $reason;
+        parent::__construct($reason);
     }
 }
 
@@ -745,7 +826,6 @@ class AuthTokenAuthentication extends UserAuthenticationBackend {
         return $user;
     }
 
-
     protected function getAuthKey($user) {
 
         if (!$this->supportsAuthentication() || !$user)
@@ -790,6 +870,7 @@ class AuthTokenAuthentication extends UserAuthenticationBackend {
         if (!$user || strcmp($this->getAuthKey($user), $authkey))
             return null;
 
+        $user->flagGuest();
 
         return $user;
     }
@@ -807,20 +888,18 @@ class AccessLinkAuthentication extends UserAuthenticationBackend {
     function authenticate($email, $number) {
 
         if (!($ticket = Ticket::lookupByNumber($number))
-                || !($user=User::lookup(array('emails__address' =>
-                            $email))))
+                || !($user=User::lookup(array('emails__address' => $email))))
             return false;
 
-        //Ticket owner?
+        // Ticket owner?
         if ($ticket->getUserId() == $user->getId())
             $user = $ticket->getOwner();
-        //Collaborator?
-        elseif (!($user = Collaborator::lookup(array('userId' =>
-                            $user->getId(), 'ticketId' =>
-                            $ticket->getId()))))
+        // Collaborator?
+        elseif (!($user = Collaborator::lookup(array(
+                'userId' => $user->getId(),
+                'ticketId' => $ticket->getId()))))
             return false; //Bro, we don't know you!
 
-
         return new ClientSession($user);
     }
 
@@ -831,4 +910,88 @@ class AccessLinkAuthentication extends UserAuthenticationBackend {
 
 }
 UserAuthenticationBackend::register('AccessLinkAuthentication');
+
+class osTicketClientAuthentication extends UserAuthenticationBackend {
+    static $name = "Local Client Authentication";
+    static $id = "client";
+
+    function authenticate($username, $password) {
+        if (!($acct = ClientAccount::lookupByUsername($username)))
+            return;
+
+        if (($client = new ClientSession(new EndUser($acct->getUser())))
+                && !$client->getId())
+            return false;
+        elseif (!$acct->checkPassword($password))
+            return false;
+        else
+            return $client;
+    }
+}
+UserAuthenticationBackend::register('osTicketClientAuthentication');
+
+class ClientPasswordResetTokenBackend extends UserAuthenticationBackend {
+    static $id = "pwreset.client";
+
+    function supportsAuthentication() {
+        return false;
+    }
+
+    function signOn($errors=array()) {
+        global $ost;
+
+        if (!isset($_POST['userid']) || !isset($_POST['token']))
+            return false;
+        elseif (!($_config = new Config('pwreset')))
+            return false;
+        elseif (!($acct = ClientAccount::lookupByUsername($_POST['userid']))
+                || !$acct->getId()
+                || !($client = new ClientSession(new EndUser($acct->getUser()))))
+            $errors['msg'] = 'Invalid user-id given';
+        elseif (!($id = $_config->get($_POST['token']))
+                || $id != $client->getId())
+            $errors['msg'] = 'Invalid reset token';
+        elseif (!($ts = $_config->lastModified($_POST['token']))
+                && ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts))))
+            $errors['msg'] = 'Invalid reset token';
+        elseif (!$acct->forcePasswdReset())
+            $errors['msg'] = 'Unable to reset password';
+        else
+            return $client;
+    }
+
+    function login($client, $bk) {
+        $_SESSION['_client']['reset-token'] = $_POST['token'];
+        Signal::send('auth.pwreset.login', $client);
+        return parent::login($client, $bk);
+    }
+}
+UserAuthenticationBackend::register('ClientPasswordResetTokenBackend');
+
+class ClientAcctConfirmationTokenBackend extends UserAuthenticationBackend {
+    static $id = "confirm.client";
+
+    function supportsAuthentication() {
+        return false;
+    }
+
+    function signOn($errors=array()) {
+        global $ost;
+
+        if (!isset($_GET['token']))
+            return false;
+        elseif (!($_config = new Config('pwreset')))
+            return false;
+        elseif (!($id = $_config->get($_GET['token'])))
+            return false;
+        elseif (!($acct = ClientAccount::lookup(array('user_id'=>$id)))
+                || !$acct->getId()
+                || $id != $acct->getUserId()
+                || !($client = new ClientSession(new EndUser($acct->getUser()))))
+            return false;
+        else
+            return $client;
+    }
+}
+UserAuthenticationBackend::register('ClientAcctConfirmationTokenBackend');
 ?>
diff --git a/include/class.client.php b/include/class.client.php
index c9f6fc394ec8fa53619d2d1b2867dfdccbb3db3d..7d1dfbcb7c93e6852070098c10774cee7f891dab 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -18,6 +18,7 @@ abstract class TicketUser {
     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;
@@ -51,18 +52,23 @@ abstract class TicketUser {
         global $ost;
 
         if (!($ticket = $this->getTicket())
-                || !($dept = $ticket->getDept())
-                || !($email = $dept->getAutoRespEmail())
-                || !($tpl = $dept->getTemplate()->getMsgTemplate('user.accesslink')))
+                || !($email = $ost->getConfig()->getDefaultEmail())
+                || !($content = Page::lookup(Page::getIdByType('access-link'))))
             return;
 
         $vars = array(
             'url' => $ost->getConfig()->getBaseUrl(),
             'ticket' => $this->getTicket(),
+            'user' => $this,
             'recipient' => $this);
 
-        $msg = $ost->replaceTemplateVariables($tpl->asArray(), $vars);
-        $email->send($this->getEmail(), $msg['subj'], $msg['body']);
+        $msg = $ost->replaceTemplateVariables(array(
+            'subj' => $content->getName(),
+            'body' => $content->getBody(),
+        ), $vars);
+
+        $email->send($this->getEmail(), Format::striptags($msg['subj']),
+            $msg['body']);
     }
 
     protected function getAuthToken($algo=1) {
@@ -133,6 +139,14 @@ abstract class TicketUser {
                     && $this->user->getId() == $this->getTicket()->getOwnerId());
     }
 
+    function flagGuest() {
+        $this->_guest = true;
+    }
+
+    function isGuest() {
+        return $this->_guest;
+    }
+
     abstract function getTicketId();
     abstract function getTicket();
 }
@@ -163,6 +177,7 @@ class TicketOwner extends  TicketUser {
 class  EndUser extends AuthenticatedUser {
 
     protected $user;
+    protected $_account = false;
 
     function __construct($user) {
         $this->user = $user;
@@ -187,7 +202,10 @@ class  EndUser extends AuthenticatedUser {
         if ($this->user instanceof Collaborator)
             return $this->user->getUserId();
 
-        return $this->user->getId();
+        elseif ($this->user)
+            return $this->user->getId();
+
+        return false;
     }
 
     function getUserName() {
@@ -225,6 +243,14 @@ class  EndUser extends AuthenticatedUser {
         return ($stats=$this->getTicketStats())?$stats['closed']:0;
     }
 
+    function getAccount() {
+        if ($this->_account === false)
+            $this->_account =
+                ClientAccount::lookup(array('user_id'=>$this->getId()));
+
+        return $this->_account;
+    }
+
     private function getStats() {
 
         $sql='SELECT count(open.ticket_id) as open, count(closed.ticket_id) as closed '
@@ -242,4 +268,242 @@ class  EndUser extends AuthenticatedUser {
         return db_fetch_array(db_query($sql));
     }
 }
+
+require_once INCLUDE_DIR.'class.orm.php';
+class ClientAccountModel extends VerySimpleModel {
+    static $meta = array(
+        'table' => USER_ACCOUNT_TABLE,
+        'pk' => array('id'),
+        'joins' => array(
+            'user' => array(
+                'null' => false,
+                'constraint' => array('user_id' => 'UserModel.id')
+            ),
+        ),
+    );
+}
+
+class ClientAccount extends ClientAccountModel {
+    var $_options = null;
+    var $timezone;
+
+    const CONFIRMED             = 0x0001;
+    const LOCKED                = 0x0002;
+    const PASSWD_RESET_REQUIRED = 0x0004;
+
+    function __onload() {
+        if ($this->get('timezone_id')) {
+            $this->timezone = Timezone::getOffsetById($this->ht['timezone_id']);
+            $_SESSION['TZ_OFFSET'] = $this->timezone;
+            $_SESSION['TZ_DST'] = $this->get('dst');
+        }
+    }
+
+    function getId() {
+        return $this->get('id');
+    }
+
+    function getUserId() {
+        return $this->get('user_id');
+    }
+
+    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 hasPassword() {
+        return (bool) $this->get('passwd');
+    }
+
+    function sendResetEmail($template='pwreset-client') {
+        global $ost, $cfg;
+
+        $token = Misc::randCode(48); // 290-bits
+
+        $email = $cfg->getDefaultEmail();
+        $content = Page::lookup(Page::getIdByType($template));
+
+        if (!$email ||  !$content)
+            return new Error('Unable to retrieve password reset email template');
+
+        $vars = array(
+            'url' => $ost->getConfig()->getBaseUrl(),
+            'token' => $token,
+            'user' => $this->getUser(),
+            'recipient' => $this->getUser(),
+            'link' => sprintf(
+                "%s/pwreset.php?token=%s",
+                $ost->getConfig()->getBaseUrl(),
+                $token),
+        );
+        $vars['reset_link'] = &$vars['link'];
+
+        $info = array('email' => $email, 'vars' => &$vars, 'log'=>true);
+        Signal::send('auth.pwreset.email', $this, $info);
+
+        $msg = $ost->replaceTemplateVariables(array(
+            'subj' => $content->getName(),
+            'body' => $content->getBody(),
+        ), $vars);
+
+        $_config = new Config('pwreset');
+        $_config->set($vars['token'], $this->user->getId());
+
+        $email->send($this->user->default_email->get('address'),
+            Format::striptags($msg['subj']), $msg['body']);
+    }
+
+    function confirm() {
+        $this->_setStatus(self::CONFIRMED);
+        return $this->save();
+    }
+
+    function isConfirmed() {
+        return $this->_getStatus(self::CONFIRMED);
+    }
+
+    function lock() {
+        $this->_setStatus(self::LOCKED);
+        $this->save();
+    }
+
+    function isLocked() {
+        return $this->_getStatus(self::LOCKED);
+    }
+
+    function forcePasswdReset() {
+        $this->_setStatus(self::PASSWD_RESET_REQUIRED);
+        return $this->save();
+    }
+
+    function isPasswdResetForced() {
+        return $this->_getStatus(self::PASSWD_RESET_REQUIRED);
+    }
+
+    function _getStatus($flag) {
+        return 0 !== ($this->get('status') & $flag);
+    }
+
+    function _clearStatus($flag) {
+        return $this->set('status', $this->get('status') & ~$flag);
+    }
+
+    function _setStatus($flag) {
+        return $this->set('status', $this->get('status') | $flag);
+    }
+
+    function cancelResetTokens() {
+        // TODO: Drop password-reset tokens from the config table for
+        //       this user id
+        $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset"
+            AND `key`='.db_input($this->getUserId());
+        if (!db_query($sql, false))
+            return false;
+
+        unset($_SESSION['_client']['reset-token']);
+    }
+
+    function getInfo() {
+        $base = $this->ht;
+        $base['tz_offset'] = $this->timezone;
+        return $base;
+    }
+
+    function getUser() {
+        $user = User::lookup($this->get('user_id'));
+        $user->set('account', $this);
+        return $user;
+    }
+
+    function update($vars, &$errors) {
+        $rtoken = $_SESSION['_client']['reset-token'];
+        if ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd'] || $rtoken) {
+
+            if (!$vars['passwd1'])
+                $errors['passwd1']='New password required';
+            elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
+                $errors['passwd1']='Must be at least 6 characters';
+            elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
+                $errors['passwd2']='Password(s) do not match';
+
+            if ($rtoken) {
+                $_config = new Config('pwreset');
+                if ($_config->get($rtoken) != $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 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!';
+            }
+        }
+
+        if (!$vars['timezone_id'])
+            $errors['timezone_id']='Time zone required';
+
+        if ($errors) return false;
+
+        $this->set('timezone_id', $vars['timezone_id']);
+        $this->set('dst', isset($vars['dst']) ? 1 : 0);
+
+        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, $info);
+            $this->cancelResetTokens();
+            $this->_clearStatus(self::PASSWD_RESET_REQUIRED);
+        }
+
+        return $this->save();
+    }
+
+    static function createForUser($user) {
+        return static::create(array('user_id'=>$user->getId()));
+    }
+
+    static function lookupByUsername($username) {
+        if (strpos($username, '@') !== false)
+            $user = self::lookup(array('user__emails__address'=>$username));
+        else
+            $user = self::lookup(array('username'=>$username));
+
+        return $user;
+    }
+}
+ClientAccount::_inspect();
+
 ?>
diff --git a/include/class.config.php b/include/class.config.php
index 68704cd7b65523acb6f5610746b4e9741847c962..03fe8891c0acfbabe2ea41c750f926a606225277 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -153,6 +153,9 @@ class OsticketConfig extends Config {
         'allow_client_updates' => false,
         'message_autoresponder_collabs' => true,
         'add_email_collabs' => true,
+        'clients_only' => false,
+        'client_registration' => 'closed',
+        'accept_unregistered_email' => true,
     );
 
     function OsticketConfig($section=null) {
@@ -239,6 +242,10 @@ class OsticketConfig extends Config {
         return $this->get('db_tz_offset');
     }
 
+    function getDefaultTimezoneId() {
+        return $this->get('default_timezone_id');
+    }
+
     /* Date & Time Formats */
     function observeDaylightSaving() {
         return ($this->get('enable_daylight_saving'));
@@ -290,10 +297,6 @@ class OsticketConfig extends Config {
         return $this->get('passwd_reset_period');
     }
 
-    function showRelatedTickets() {
-        return $this->get('show_related_tickets');
-    }
-
     function isHtmlThreadEnabled() {
         return $this->get('enable_html_thread');
     }
@@ -517,6 +520,19 @@ class OsticketConfig extends Config {
         return $this->get('pw_reset_window') * 60;
     }
 
+    function isClientLoginRequired() {
+        return $this->get('clients_only');
+    }
+
+    function isClientRegistrationEnabled() {
+        return in_array($this->getClientRegistrationMode(),
+            array('public', 'auto'));
+    }
+
+    function getClientRegistrationMode() {
+        return $this->get('client_registration');
+    }
+
     function isCaptchaEnabled() {
         return (extension_loaded('gd') && function_exists('gd_info') && $this->get('enable_captcha'));
     }
@@ -533,6 +549,10 @@ class OsticketConfig extends Config {
         return ($this->get('use_email_priority'));
     }
 
+    function acceptUnregisteredEmail() {
+        return $this->get('accept_unregistered_email');
+    }
+
     function addCollabsViaEmail() {
         return ($this->get('add_email_collabs'));
     }
@@ -767,7 +787,10 @@ class OsticketConfig extends Config {
             case 'pages':
                 return $this->updatePagesSettings($vars, $errors);
                 break;
-           case 'autoresp':
+            case 'access':
+                return $this->updateAccessSettings($vars, $errors);
+                break;
+            case 'autoresp':
                 return $this->updateAutoresponderSettings($vars, $errors);
                 break;
             case 'alerts':
@@ -789,17 +812,12 @@ class OsticketConfig extends Config {
         $f['helpdesk_url']=array('type'=>'string',   'required'=>1, 'error'=>'Helpdesk URl required');
         $f['helpdesk_title']=array('type'=>'string',   'required'=>1, 'error'=>'Helpdesk title required');
         $f['default_dept_id']=array('type'=>'int',   'required'=>1, 'error'=>'Default Dept. required');
-        $f['staff_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
-        $f['client_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
         //Date & Time Options
         $f['time_format']=array('type'=>'string',   'required'=>1, 'error'=>'Time format required');
         $f['date_format']=array('type'=>'string',   'required'=>1, 'error'=>'Date format required');
         $f['datetime_format']=array('type'=>'string',   'required'=>1, 'error'=>'Datetime format required');
         $f['daydatetime_format']=array('type'=>'string',   'required'=>1, 'error'=>'Day, Datetime format required');
         $f['default_timezone_id']=array('type'=>'int',   'required'=>1, 'error'=>'Default Timezone required');
-        $f['pw_reset_window']=array('type'=>'int', 'required'=>1, 'min'=>1,
-            'error'=>'Valid password reset window required');
-
 
         if(!Validator::process($f, $vars, $errors) || $errors)
             return false;
@@ -813,6 +831,27 @@ class OsticketConfig extends Config {
             'log_level'=>$vars['log_level'],
             'log_graceperiod'=>$vars['log_graceperiod'],
             'name_format'=>$vars['name_format'],
+            'time_format'=>$vars['time_format'],
+            'date_format'=>$vars['date_format'],
+            'datetime_format'=>$vars['datetime_format'],
+            'daydatetime_format'=>$vars['daydatetime_format'],
+            'default_timezone_id'=>$vars['default_timezone_id'],
+            'enable_daylight_saving'=>isset($vars['enable_daylight_saving'])?1:0,
+        ));
+    }
+
+    function updateAccessSettings($vars, &$errors) {
+        $f=array();
+        $f['staff_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
+        $f['client_session_timeout']=array('type'=>'int',   'required'=>1, 'error'=>'Enter idle time in minutes');
+        $f['pw_reset_window']=array('type'=>'int', 'required'=>1, 'min'=>1,
+            'error'=>'Valid password reset window required');
+
+
+        if(!Validator::process($f, $vars, $errors) || $errors)
+            return false;
+
+        return $this->updateAll(array(
             'passwd_reset_period'=>$vars['passwd_reset_period'],
             'staff_max_logins'=>$vars['staff_max_logins'],
             'staff_login_timeout'=>$vars['staff_login_timeout'],
@@ -823,18 +862,12 @@ class OsticketConfig extends Config {
             'client_session_timeout'=>$vars['client_session_timeout'],
             'allow_pw_reset'=>isset($vars['allow_pw_reset'])?1:0,
             'pw_reset_window'=>$vars['pw_reset_window'],
-            'time_format'=>$vars['time_format'],
-            'date_format'=>$vars['date_format'],
-            'datetime_format'=>$vars['datetime_format'],
-            'daydatetime_format'=>$vars['daydatetime_format'],
-            'default_timezone_id'=>$vars['default_timezone_id'],
-            'enable_daylight_saving'=>isset($vars['enable_daylight_saving'])?1:0,
+            'clients_only'=>isset($vars['clients_only'])?1:0,
+            'client_registration'=>$vars['client_registration'],
         ));
     }
 
     function updateTicketsSettings($vars, &$errors) {
-
-
         $f=array();
         $f['default_sla_id']=array('type'=>'int',   'required'=>1, 'error'=>'Selection required');
         $f['default_priority_id']=array('type'=>'int',   'required'=>1, 'error'=>'Selection required');
@@ -932,6 +965,7 @@ class OsticketConfig extends Config {
             'enable_mail_polling'=>isset($vars['enable_mail_polling'])?1:0,
             'strip_quoted_reply'=>isset($vars['strip_quoted_reply'])?1:0,
             'use_email_priority'=>isset($vars['use_email_priority'])?1:0,
+            'accept_unregistered_email'=>isset($vars['accept_unregistered_email'])?1:0,
             'add_email_collabs'=>isset($vars['add_email_collabs'])?1:0,
             'reply_separator'=>$vars['reply_separator'],
          ));
diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php
index 0045b6a430e3d864d4b8792df143a187214451fe..01682d00cf20c9f71976f7821a73076a98881f60 100644
--- a/include/class.dynamic_forms.php
+++ b/include/class.dynamic_forms.php
@@ -73,9 +73,9 @@ class DynamicForm extends VerySimpleModel {
     }
 
     function getField($name) {
-        foreach ($this->getDynamicFields() as $f)
+        foreach ($this->getFields() as $f)
             if (!strcasecmp($f->get('name'), $name))
-                return $f->getImpl();
+                return $f;
     }
 
     function hasField($name) {
@@ -494,7 +494,7 @@ class DynamicFormEntry extends VerySimpleModel {
     function getForm() {
         if (!isset($this->_form)) {
             $this->_form = DynamicForm::lookup($this->get('form_id'));
-            if ($this->id)
+            if (isset($this->id))
                 $this->_form->data($this);
         }
         return $this->_form;
diff --git a/include/class.forms.php b/include/class.forms.php
index 2bbb04a66559d7264816968e2ce023b9b271e083..42f4651a1debd6fd0234244ef6f9f1f6aec10ea7 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -478,6 +478,11 @@ class FormField {
         return $this->_cform;
     }
 
+    function configure($prop, $value) {
+        $this->getConfiguration();
+        $this->_config[$prop] = $value;
+    }
+
     function getWidget() {
         if (!static::$widget)
             throw new Exception('Widget not defined for this field');
@@ -932,7 +937,7 @@ class Widget {
         $this->value = $this->getValue();
         if (!isset($this->value) && is_object($this->field->getAnswer()))
             $this->value = $this->field->getAnswer()->getValue();
-        if (!isset($this->value) && $this->field->value)
+        if (!isset($this->value) && isset($this->field->value))
             $this->value = $this->field->value;
     }
 
@@ -960,12 +965,14 @@ class TextboxWidget extends Widget {
             $classes = 'class="'.$config['classes'].'"';
         if (isset($config['autocomplete']))
             $autocomplete = 'autocomplete="'.($config['autocomplete']?'on':'off').'"';
+        if (isset($config['disabled']))
+            $disabled = 'disabled="disabled"';
         ?>
         <span style="display:inline-block">
         <input type="<?php echo static::$input_type; ?>"
             id="<?php echo $this->name; ?>"
-            <?php echo $size . " " . $maxlength; ?>
-            <?php echo $classes.' '.$autocomplete
+            <?php echo implode(' ', array_filter(array(
+                $size, $maxlength, $classes, $autocomplete, $disabled)))
                 .' placeholder="'.$config['placeholder'].'"'; ?>
             name="<?php echo $this->name; ?>"
             value="<?php echo Format::htmlchars($this->value); ?>"/>
@@ -1090,6 +1097,8 @@ class CheckboxWidget extends Widget {
 
     function render() {
         $config = $this->field->getConfiguration();
+        if (!isset($this->value))
+            $this->value = $this->field->get('default');
         ?>
         <input type="checkbox" name="<?php echo $this->name; ?>[]" <?php
             if ($this->value) echo 'checked="checked"'; ?> value="<?php
diff --git a/include/class.i18n.php b/include/class.i18n.php
index 7335495d5bd75ecdc9547a27d5af35f8f5de5076..b7e7289430bf993618579a5b2f5726d7c682e100 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -24,6 +24,11 @@ class Internationalization {
     var $langs = array('en_US');
 
     function Internationalization($language=false) {
+        global $cfg;
+
+        if ($cfg && ($lang = $cfg->getSystemLanguage()))
+            array_unshift($this->langs, $language);
+
         if ($language)
             array_unshift($this->langs, $language);
     }
@@ -85,9 +90,13 @@ class Internationalization {
             }
         }
 
-        // Pages
+        // Pages and content
         $_config = new OsticketConfig();
-        foreach (array('landing','thank-you','offline') as $type) {
+        foreach (array('landing','thank-you','offline',
+                'registration-staff', 'pwreset-staff', 'banner-staff',
+                'registration-client', 'pwreset-client', 'banner-client',
+                'registration-confirm', 'registration-thanks',
+                'access-link') as $type) {
             $tpl = $this->getTemplate("templates/page/{$type}.yaml");
             if (!($page = $tpl->getData()))
                 continue;
@@ -97,12 +106,16 @@ class Internationalization {
                 .', lang='.db_input($tpl->getLang())
                 .', notes='.db_input($page['notes'])
                 .', created=NOW(), updated=NOW(), isactive=1';
-            if (db_query($sql) && ($id = db_insert_id()))
+            if (db_query($sql) && ($id = db_insert_id())
+                    && in_array($type, array('landing', 'thank-you', 'offline')))
                 $_config->set("{$type}_page_id", $id);
         }
         // Default Language
         $_config->set('system_language', $this->langs[0]);
 
+        // content_id defaults to the `id` field value
+        db_query('UPDATE '.PAGE_TABLE.' SET content_id=id');
+
         // Canned response examples
         if (($tpl = $this->getTemplate('templates/premade.yaml'))
                 && ($canned = $tpl->getData())) {
diff --git a/include/class.nav.php b/include/class.nav.php
index 1648332948ec1a32e5ac9a12aba75827d3087a15..6e0179890938e6f7a933f7b089b2368dd8e0afa2 100644
--- a/include/class.nav.php
+++ b/include/class.nav.php
@@ -200,6 +200,7 @@ class AdminNav extends StaffNav{
                     $subnav[]=array('desc'=>'System','href'=>'settings.php?t=system','iconclass'=>'preferences');
                     $subnav[]=array('desc'=>'Tickets','href'=>'settings.php?t=tickets','iconclass'=>'ticket-settings');
                     $subnav[]=array('desc'=>'Emails','href'=>'settings.php?t=emails','iconclass'=>'email-settings');
+                    $subnav[]=array('desc'=>'Access','href'=>'settings.php?t=access','iconclass'=>'users');
                     $subnav[]=array('desc'=>'Knowledgebase','href'=>'settings.php?t=kb','iconclass'=>'kb-settings');
                     $subnav[]=array('desc'=>'Autoresponder','href'=>'settings.php?t=autoresp','iconclass'=>'email-autoresponders');
                     $subnav[]=array('desc'=>'Alerts&nbsp;&amp;&nbsp;Notices','href'=>'settings.php?t=alerts','iconclass'=>'alert-settings');
@@ -279,9 +280,15 @@ class UserNav {
             if($cfg && $cfg->isKnowledgebaseEnabled())
                 $navs['kb']=array('desc'=>'Knowledgebase','href'=>'kb/index.php','title'=>'');
 
-            $navs['new']=array('desc'=>'Open&nbsp;New&nbsp;Ticket','href'=>'open.php','title'=>'');
+            // Show the "Open New Ticket" link unless BOTH client
+            // registration is disabled and client login is required for new
+            // tickets. In such a case, creating a ticket would not be
+            // possible for web clients.
+            if ($cfg->getClientRegistrationMode() != 'disabled'
+                    || !$cfg->isClientLoginRequired())
+                $navs['new']=array('desc'=>'Open&nbsp;New&nbsp;Ticket','href'=>'open.php','title'=>'');
             if($user && $user->isValid()) {
-                if($cfg && $cfg->showRelatedTickets()) {
+                if(!$user->isGuest()) {
                     $navs['tickets']=array('desc'=>sprintf('Tickets&nbsp;(%d)',$user->getNumTickets()),
                                            'href'=>'tickets.php',
                                             'title'=>'Show all tickets');
diff --git a/include/class.orm.php b/include/class.orm.php
index b9bda6a1176ac4beea1aee9301360e50328a315b..cdf25599501b82e474893d2ccca18823804d38f1 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -16,6 +16,8 @@
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
 
+class OrmException extends Exception {}
+
 class VerySimpleModel {
     static $meta = array(
         'table' => false,
@@ -46,6 +48,13 @@ class VerySimpleModel {
             $v = $this->ht[$field] = $class::lookup($this->ht[$j['local']]);
             return $v;
         }
+        throw new OrmException(sprintf('%s: %s: Field not defined',
+            get_class($this), $field));
+    }
+
+    function __isset($field) {
+        return array_key_exists($field, $this->ht)
+            || isset(static::$meta['joins'][$field]);
     }
 
     function set($field, $value) {
@@ -86,7 +95,8 @@ class VerySimpleModel {
         // Construct related lists
         if (isset(static::$meta['joins'])) {
             foreach (static::$meta['joins'] as $name => $j) {
-                if (isset($j['list']) && $j['list']) {
+                if (isset($this->{$j['local']})
+                        && isset($j['list']) && $j['list']) {
                     $fkey = $j['fkey'];
                     $this->{$name} = new InstrumentedList(
                         // Send Model, Foriegn-Field, Local-Id
@@ -97,6 +107,8 @@ class VerySimpleModel {
         }
     }
 
+    function __onload() {}
+
     static function _inspect() {
         if (!static::$meta['table'])
             throw new OrmConfigurationError(
@@ -113,7 +125,8 @@ class VerySimpleModel {
                     $constraint[$field] = "$model.$foreign";
                 }
                 $j['constraint'] = $constraint;
-                $j['list'] = true;
+                if (!isset($j['list']))
+                    $j['list'] = true;
             }
             // XXX: Make this better (ie. composite keys)
             $keys = array_keys($j['constraint']);
@@ -365,7 +378,9 @@ class ModelInstanceIterator implements Iterator, ArrayAccess {
 
     function buildModel($row) {
         // TODO: Traverse to foreign keys
-        return new $this->model($row); # nolint
+        $model = new $this->model($row); # nolint
+        $model->__onload();
+        return $model;
     }
 
     function fillTo($index) {
diff --git a/include/class.page.php b/include/class.page.php
index f0ada9a6c077dfa1099a55d64867a82cb294c501..553e342a7e2f657aa6d2df307479b65f7dffa764 100644
--- a/include/class.page.php
+++ b/include/class.page.php
@@ -19,13 +19,13 @@ class Page {
     var $ht;
     var $attachments;
 
-    function Page($id) {
+    function Page($id, $lang=false) {
         $this->id=0;
         $this->ht = array();
-        $this->load($id);
+        $this->load($id, $lang);
     }
 
-    function load($id=0) {
+    function load($id=0, $lang=false) {
 
         if(!$id && !($id=$this->getId()))
             return false;
@@ -33,7 +33,8 @@ class Page {
         $sql='SELECT page.*, count(topic.page_id) as topics '
             .' FROM '.PAGE_TABLE.' page '
             .' LEFT JOIN '.TOPIC_TABLE. ' topic ON(topic.page_id=page.id) '
-            .' WHERE page.id='.db_input($id)
+            . ' WHERE page.content_id='.db_input($id)
+            . ($lang ? ' AND lang='.db_input($lang) : '')
             .' GROUP By page.id';
 
         if (!($res=db_query($sql)) || !db_num_rows($res))
@@ -195,10 +196,25 @@ class Page {
         return self::getActivePages(array('type' => 'thank-you'));
     }
 
-    function getIdByName($name) {
+    function getIdByName($name, $lang=false) {
 
         $id = 0;
         $sql = ' SELECT id FROM '.PAGE_TABLE.' WHERE name='.db_input($name);
+        if ($lang)
+            $sql .= ' AND lang='.db_input($lang);
+
+        if(($res=db_query($sql)) && db_num_rows($res))
+            list($id) = db_fetch_row($res);
+
+        return $id;
+    }
+
+    function getIdByType($type, $lang=false) {
+        $id = 0;
+        $sql = ' SELECT id FROM '.PAGE_TABLE.' WHERE `type`='.db_input($type);
+        if ($lang)
+            $sql .= ' AND lang='.db_input($lang);
+
         if(($res=db_query($sql)) && db_num_rows($res))
             list($id) = db_fetch_row($res);
 
@@ -224,8 +240,6 @@ class Page {
 
         if(!$vars['type'])
             $errors['type'] = 'Type required';
-        elseif(!in_array($vars['type'], array('landing', 'offline', 'thank-you', 'other')))
-            $errors['type'] = 'Invalid selection';
 
         if(!$vars['name'])
             $errors['name'] = 'Name required';
@@ -254,10 +268,13 @@ class Page {
 
         } else {
             $sql='INSERT INTO '.PAGE_TABLE.' SET '.$sql.', created=NOW()';
-            if(db_query($sql) && ($id=db_insert_id()))
-                return $id;
+            if (!db_query($sql) || !($id=db_insert_id())) {
+                $errors['err']='Unable to create page. Internal error';
+                return false;
+            }
 
-            $errors['err']='Unable to create page. Internal error';
+            // TODO: Update `content_id`
+            return $id;
         }
 
         return false;
diff --git a/include/class.plugin.php b/include/class.plugin.php
index 6a8ac830b8d372b3dd9974af60ca555635e77898..f571ad75d5c135c1dfcc4258ab93394ac3af8d72 100644
--- a/include/class.plugin.php
+++ b/include/class.plugin.php
@@ -9,6 +9,8 @@ class PluginConfig extends Config {
         // Use parent constructor to place configurable information into the
         // central config table in a namespace of "plugin.<id>"
         parent::Config("plugin.$name");
+        foreach ($this->getOptions() as $name => $field)
+            $this->config[$name]['value'] = $field->to_php($this->get($name));
     }
 
     /* abstract */
@@ -56,8 +58,14 @@ class PluginConfig extends Config {
             $commit = $this->pre_save($config, $errors);
         }
         $errors += $f->errors();
-        if ($commit && count($errors) === 0)
-            return $this->updateAll($config);
+        if ($commit && count($errors) === 0) {
+            $dbready = array();
+            foreach ($config as $name => $val) {
+                $field = $f->getField($name);
+                $dbready[$name] = $field->to_database($val);
+            }
+            return $this->updateAll($dbready);
+        }
         return false;
     }
 
diff --git a/include/class.staff.php b/include/class.staff.php
index 819c68d2814dc4c834255d891dd765d361ddbffd..9ab47f6b4e4bbdfb14f939535ebbb8cf7d852420 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -721,8 +721,11 @@ class Staff extends AuthenticatedUser {
     }
 
     function create($vars, &$errors) {
-        if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id))) {
-            $staff->updateTeams($vars['teams']);
+        if(($id=self::save(0, $vars, $errors)) && ($staff=Staff::lookup($id))) {
+            if ($vars['teams'])
+                $staff->updateTeams($vars['teams']);
+            if ($vars['welcome_email'])
+                $staff->sendResetEmail('registration-staff');
             Signal::send('model.created', $staff);
         }
 
@@ -738,27 +741,28 @@ class Staff extends AuthenticatedUser {
         unset($_SESSION['_staff']['reset-token']);
     }
 
-    function sendResetEmail() {
+    function sendResetEmail($template='pwreset-staff') {
         global $ost, $cfg;
 
-        if(!($tpl = $this->getDept()->getTemplate()))
-            $tpl= $ost->getConfig()->getDefaultTemplate();
-
+        $content = Page::lookup(Page::getIdByType($template));
         $token = Misc::randCode(48); // 290-bits
-        if (!($template = $tpl->getMsgTemplate('staff.pwreset')))
+
+        if (!$content)
             return new Error('Unable to retrieve password reset email template');
 
         $vars = array(
             'url' => $ost->getConfig()->getBaseUrl(),
             'token' => $token,
             'staff' => $this,
+            'recipient' => $this,
             'reset_link' => sprintf(
                 "%s/scp/pwreset.php?token=%s",
                 $ost->getConfig()->getBaseUrl(),
                 $token),
         );
+        $vars['link'] = &$vars['reset_link'];
 
-        if(!($email=$cfg->getAlertEmail()))
+        if (!($email = $cfg->getAlertEmail()))
             $email = $cfg->getDefaultEmail();
 
         $info = array('email' => $email, 'vars' => &$vars, 'log'=>true);
@@ -778,12 +782,16 @@ class Staff extends AuthenticatedUser {
                 $email->getEmail()
             ), false);
 
-        $msg = $ost->replaceTemplateVariables($template->asArray(), $vars);
+        $msg = $ost->replaceTemplateVariables(array(
+            'subj' => $content->getName(),
+            'body' => $content->getBody(),
+        ), $vars);
 
         $_config = new Config('pwreset');
         $_config->set($vars['token'], $this->getId());
 
-        $email->send($this->getEmail(), $msg['subj'], $msg['body']);
+        $email->send($this->getEmail(), Format::stripTags($msg['subj']),
+            $msg['body']);
     }
 
     function save($id, $vars, &$errors) {
@@ -823,7 +831,7 @@ class Staff extends AuthenticatedUser {
             if($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) {
                 $errors['passwd2']='Password(s) do not match';
             }
-            elseif ($vars['backend'] != 'local') {
+            elseif ($vars['backend'] != 'local' || $vars['welcome_email']) {
                 // Password can be omitted
             }
             elseif(!$vars['passwd1'] && !$id) {
diff --git a/include/class.template.php b/include/class.template.php
index 88f3e5c5701383cca67b68f55764d0f30eb92262..4394c0fc1af09a7fbeeb2d3a06c7de1f550afea2 100644
--- a/include/class.template.php
+++ b/include/class.template.php
@@ -79,14 +79,6 @@ class EmailTemplateGroup {
             'group'=>'ticket.staff',
             'name'=>'Overdue Ticket Alert',
             'desc'=>'Alert sent to staff on stale or overdue tickets.'),
-        'staff.pwreset' => array(
-            'group'=>'sys',
-            'name' => 'Staff Password Reset',
-            'desc' => 'Notice sent to staff with the password reset link.'),
-        'user.accesslink' => array(
-            'group'=>'sys',
-            'name' => 'User Access Link Recovery',
-            'desc' => 'Notice sent to user on request with ticket access link.'),
         );
 
     function EmailTemplateGroup($id){
diff --git a/include/class.ticket.php b/include/class.ticket.php
index bd05210c3611429f2170cf4636edabda418bd7c3..a69109204c85f16a88ab60fa01f2c0b87b1a2d8f 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -2157,6 +2157,14 @@ class Ticket {
             };
         };
 
+        $reject_ticket = function($message) use (&$errors) {
+            $errors = array(
+                'errno' => 403,
+                'err' => 'This help desk is for use by authorized users only');
+            $ost->logWarning('Ticket Denied', $message);
+            return 0;
+        };
+
         // Create and verify the dynamic form entry for the new ticket
         $form = TicketForm::getNewInstance();
         // If submitting via email, ensure we have a subject and such
@@ -2193,12 +2201,7 @@ class Ticket {
 
             //Make sure the email address is not banned
             if (TicketFilter::isBanned($vars['email'])) {
-                $errors = array(
-                        'errno' => 403,
-                        'err' => 'This help desk is for use by authorized
-                        users only');
-                $ost->logWarning('Ticket denied', 'Banned email - '.$vars['email']);
-                return 0;
+                return $reject_ticket('Banned email - '.$vars['email']);
             }
 
             //Make sure the open ticket limit hasn't been reached. (LOOP CONTROL)
@@ -2220,17 +2223,11 @@ class Ticket {
         //Init ticket filters...
         $ticket_filter = new TicketFilter($origin, $vars);
         // Make sure email contents should not be rejected
-        if($ticket_filter
+        if ($ticket_filter
                 && ($filter=$ticket_filter->shouldReject())) {
-            $errors = array(
-                    'errno' => 403,
-                    'err' => "This help desk is for use by authorized users
-                    only");
-            $ost->logWarning('Ticket denied',
-                    sprintf('Ticket rejected ( %s) by filter "%s"',
-                        $vars['email'], $filter->getName()));
-
-            return 0;
+            return $reject_ticket(
+                sprintf('Ticket rejected ( %s) by filter "%s"',
+                    $vars['email'], $filter->getName()));
         }
 
         $id=0;
@@ -2281,10 +2278,18 @@ class Ticket {
                         || !($user=User::fromVars($user_form->getClean())))
                     $errors['user'] = 'Incomplete client information';
             }
+
+            // Reject emails if not from registered clients (if configured)
+            if (!$cfg->acceptUnregisteredEmail() && !$user->getAccount()) {
+                return $reject_ticket(
+                    sprintf('Ticket rejected (%s) (unregistered client)',
+                        $vars['email']));
+            }
         }
 
-        //Any error above is fatal.
-        if($errors)  return 0;
+        // Any error above is fatal.
+        if ($errors)
+            return 0;
 
         # Some things will need to be unpacked back into the scope of this
         # function
diff --git a/include/class.user.php b/include/class.user.php
index 0a945f68b57c1b37ed93c59fa40fbd7b5ba45969..d23d1e9f1b857bd343f74c42bc7348580703df56 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -36,6 +36,10 @@ class UserModel extends VerySimpleModel {
             'emails' => array(
                 'reverse' => 'UserEmailModel.user',
             ),
+            'account' => array(
+                'list' => false,
+                'reverse' => 'ClientAccount.user',
+            ),
             'default_email' => array(
                 'null' => true,
                 'constraint' => array('default_email_id' => 'UserEmailModel.id')
@@ -143,6 +147,10 @@ class User extends UserModel {
         return $this->created;
     }
 
+    function getAccount() {
+        return $this->account;
+    }
+
     function to_json() {
 
         $info = array(
@@ -284,6 +292,10 @@ class User extends UserModel {
             $this->set('updated', new SqlFunction('NOW'));
         return parent::save($refetch);
     }
+
+    function delete() {
+        return parent::delete() && $this->default_email->delete();
+    }
 }
 User::_inspect();
 
diff --git a/include/class.usersession.php b/include/class.usersession.php
index 31c4ce058d50939e10dabda80fe736ab3095c83f..9e7fd277baf58d59b39ade1255bd29a1308493a4 100644
--- a/include/class.usersession.php
+++ b/include/class.usersession.php
@@ -120,7 +120,7 @@ class ClientSession extends EndUser {
         parent::__construct($user);
         $this->token = &$_SESSION[':token']['client'];
         // XXX: Change the key to user-id
-        $this->session= new UserSession($user->getUserName());
+        $this->session= new UserSession($user->getId());
     }
 
     function isValid(){
diff --git a/include/client/accesslink.inc.php b/include/client/accesslink.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..0f1ef8c319afb1b388bc0710dd6e4fc30809ceae
--- /dev/null
+++ b/include/client/accesslink.inc.php
@@ -0,0 +1,43 @@
+<?php
+if(!defined('OSTCLIENTINC')) die('Access Denied');
+
+$email=Format::input($_POST['lemail']?$_POST['lemail']:$_GET['e']);
+$ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']);
+?>
+<h1>Check Ticket Status</h1>
+<p>Please provide us with your email address and a ticket number, and an access
+link will be emailed to you.</p>
+<form action="login.php" method="post" id="clientLogin">
+    <?php csrf_token(); ?>
+<div style="display:table-row">
+    <div style="width:40%;display:table-cell;box-shadow: 12px 0 15px -15px rgba(0,0,0,0.4);padding-right: 2em;">
+    <strong><?php echo Format::htmlchars($errors['login']); ?></strong>
+    <br>
+    <div>
+        <label for="email">E-Mail Address:</label><br/>
+        <input id="email" type="text" name="lemail" size="30" value="<?php echo $email; ?>">
+    </div>
+    <div>
+        <label for="ticketno">Ticket Number:</label><br/>
+        <input id="ticketno" type="text" name="lticket" size="16" value="<?php echo $ticketid; ?>"></td>
+    </div>
+    <p>
+        <input class="btn" type="submit" value="Email Access Link">
+    </p>
+    </div>
+    <div style="display:table-cell;padding-left: 2em;padding-right:90px;">
+<?php if ($cfg && $cfg->getClientRegistrationMode() !== 'disabled') { ?>
+        Have an account with us?
+        <a href="account.php?do=create">Sign In</a> <?php
+    if ($cfg->isClientRegistrationEnabled()) { ?>
+        or <a href="login.php?do=create">register for an account</a> <?php
+    } ?> to access all your tickets.
+<?php
+} ?>
+    </div>
+</div>
+</form>
+<br>
+<p>
+If this is your first time contacting us or you've lost the ticket number, please <a href="open.php">open a new ticket</a>.
+</p>
diff --git a/include/client/header.inc.php b/include/client/header.inc.php
index 107cf479c43884bf362b57f72378c40bc1a5be78..e6ea209f4048ee21b92244bfc0e504e82a89c8fa 100644
--- a/include/client/header.inc.php
+++ b/include/client/header.inc.php
@@ -43,21 +43,23 @@ header("Content-Type: text/html; charset=UTF-8\r\n");
                 style="height: 5em"></a>
             <p>
              <?php
-             if($thisclient && is_object($thisclient) && $thisclient->isValid()) {
+                if ($thisclient && is_object($thisclient) && $thisclient->isValid()
+                    && !$thisclient->isGuest()) {
                  echo Format::htmlchars($thisclient->getName()).'&nbsp;|';
                  ?>
-                <a href="<?php echo ROOT_PATH; ?>profile.php">Profile</a> |
-                <?php
-                if($cfg->showRelatedTickets()) {?>
+                <a href="<?php echo ROOT_PATH; ?>account.php">Profile</a> |
                 <a href="<?php echo ROOT_PATH; ?>tickets.php">Tickets <b>(<?php echo $thisclient->getNumTickets(); ?>)</b></a> -
-                <?php
-                } ?>
                 <a href="<?php echo ROOT_PATH; ?>logout.php?auth=<?php echo $ost->getLinkToken(); ?>">Log Out</a>
-             <?php
-             }elseif($nav){ ?>
-                 Guest User | <a href="<?php echo ROOT_PATH; ?>login.php">Log In</a>
-              <?php
-             } ?>
+            <?php
+            } elseif($nav) {
+                if ($cfg->getClientRegistrationMode() == 'public') { ?>
+                    Guest User | <?php
+                }
+                if ($cfg->getClientRegistrationMode() != 'disabled') { ?>
+                    <a href="<?php echo ROOT_PATH; ?>login.php">Sign In</a>
+<?php
+                }
+            } ?>
             </p>
         </div>
         <?php
diff --git a/include/client/login.inc.php b/include/client/login.inc.php
index 586ef9808b75144e37dafc775debb9cc8f4ca1c4..fd1d7bc80abac91077654ae8a58d2cfc285b0444 100644
--- a/include/client/login.inc.php
+++ b/include/client/login.inc.php
@@ -1,29 +1,54 @@
 <?php
 if(!defined('OSTCLIENTINC')) die('Access Denied');
 
-$email=Format::input($_POST['lemail']?$_POST['lemail']:$_GET['e']);
-$ticketid=Format::input($_POST['lticket']?$_POST['lticket']:$_GET['t']);
+$email=Format::input($_POST['luser']?:$_GET['e']);
+$passwd=Format::input($_POST['lpasswd']?:$_GET['t']);
+
+$content = Page::lookup(Page::getIdByType('banner-client'));
+
+if ($content) {
+    list($title, $body) = $ost->replaceTemplateVariables(
+        array($content->getName(), $content->getBody()));
+} else {
+    $title = 'Sign In';
+    $body = 'To better serve you, we encourage our clients to register for
+        an account and verify the email address we have on record.';
+}
+
 ?>
-<h1>Check Ticket Status</h1>
-<p>Please provide us with your email address and a ticket number, and an access
-link will be emailed to you.</p>
+<h1><?php echo Format::display($title); ?></h1>
+<p><?php echo Format::display($body); ?></p>
 <form action="login.php" method="post" id="clientLogin">
     <?php csrf_token(); ?>
+<div style="display:table-row">
+    <div style="width:40%;display:table-cell;box-shadow: 12px 0 15px -15px rgba(0,0,0,0.4);padding-left: 2em;">
     <strong><?php echo Format::htmlchars($errors['login']); ?></strong>
     <br>
     <div>
-        <label for="email">E-Mail Address:</label>
-        <input id="email" type="text" name="lemail" size="30" value="<?php echo $email; ?>">
+        <label for="username">Username:</label>
+        <input id="username" type="text" name="luser" size="30" value="<?php echo $email; ?>">
     </div>
     <div>
-        <label for="ticketno">Ticket Number:</label>
-        <input id="ticketno" type="text" name="lticket" size="16" value="<?php echo $ticketid; ?>"></td>
+        <label for="passwd">Password:</label>
+        <input id="passwd" type="password" name="lpasswd" size="30" value="<?php echo $passwd; ?>"></td>
     </div>
     <p>
-        <input class="btn" type="submit" value="Email Access Link">
+        <input class="btn" type="submit" value="Sign In">
+<?php if ($suggest_pwreset) { ?>
+        <a style="padding-top:4px;display:inline-block;" href="pwreset.php">Forgot My Password</a>
+<?php } ?>
     </p>
+    </div>
+    <div style="display:table-cell;padding-left: 2em;">
+<?php if ($cfg && $cfg->isClientRegistrationEnabled()) { ?>
+        Not yet registered? <a href="account.php?do=create">Create an account</a>
+<?php } ?>
+    </div>
+</div>
 </form>
 <br>
 <p>
+<?php if ($cfg && !$cfg->isClientLoginRequired()) { ?>
 If this is your first time contacting us or you've lost the ticket number, please <a href="open.php">open a new ticket</a>.
+<?php } ?>
 </p>
diff --git a/include/client/profile.inc.php b/include/client/profile.inc.php
index a103ba1f7ec5f00ec1ec182b520ccfb2eaa8077b..d5651031cdaac3a0a5975edd862384a18d69bfe6 100644
--- a/include/client/profile.inc.php
+++ b/include/client/profile.inc.php
@@ -8,12 +8,83 @@ account
 </p>
 <form action="profile.php" method="post">
   <?php csrf_token(); ?>
-<table width="800">
+<table width="800" class="padded">
 <?php
 foreach ($user->getForms() as $f) {
     $f->render(false);
 }
+if ($acct = $thisclient->getAccount()) {
+    $info=$acct->getInfo();
+    $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 ?>
+<tr>
+    <td colspan="2">
+        <div><hr><h3>Preferences</h3>
+        </div>
+    </td>
+</tr>
+    <td>Time Zone:</td>
+    <td>
+        <select name="timezone_id" id="timezone_id">
+            <option value="0">&mdash; Select Time Zone &mdash;</option>
+            <?php
+            $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id';
+            if(($res=db_query($sql)) && db_num_rows($res)){
+                while(list($id,$offset, $tz)=db_fetch_row($res)){
+                    $sel=($info['timezone_id']==$id)?'selected="selected"':'';
+                    echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz);
+                }
+            }
+            ?>
+        </select>
+        &nbsp;<span class="error"><?php echo $errors['timezone_id']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+       Daylight Saving:
+    </td>
+    <td>
+        <input type="checkbox" name="dst" value="1" <?php echo $info['dst']?'checked="checked"':''; ?>>
+        Observe daylight saving
+        <em>(Current Time: <strong><?php echo Format::date($cfg->getDateTimeFormat(),Misc::gmtime(),$info['tz_offset'],$info['dst']); ?></strong>)</em>
+    </td>
+</tr>
+<tr>
+    <td colspan=2">
+        <div><hr><h3>Access Credentials</h3></div>
+    </td>
+</tr>
+<?php if (!isset($_SESSION['_client']['reset-token'])) { ?>
+<tr>
+    <td width="180">
+        Current Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="cpasswd" value="<?php echo $info['cpasswd']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['cpasswd']; ?></span>
+    </td>
+</tr>
+<?php } ?>
+<tr>
+    <td width="180">
+        New Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd1']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+        Confirm New Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd2']; ?></span>
+    </td>
+</tr>
+<?php } ?>
 </table>
 <hr>
 <p style="text-align: center;">
diff --git a/include/client/pwreset.login.php b/include/client/pwreset.login.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1c1ed4e75f656b8372d95052aa1201ba2e28545
--- /dev/null
+++ b/include/client/pwreset.login.php
@@ -0,0 +1,26 @@
+<?php
+if(!defined('OSTCLIENTINC')) die('Access Denied');
+
+$userid=Format::input($_POST['userid']);
+?>
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address again in the form below and press the
+<strong>Login</strong> to access your account and reset your password.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="reset"/>
+    <input type="hidden" name="token" value="<?php echo $_REQUEST['token']; ?>"/>
+    <strong><?php echo Format::htmlchars($banner); ?></strong>
+    <br>
+    <div>
+        <label for="username">Username:</label>
+        <input id="username" type="text" name="userid" size="30" value="<?php echo $userid; ?>">
+    </div>
+    <p>
+        <input class="btn" type="submit" value="Login">
+    </p>
+    </div>
+</form>
diff --git a/include/client/pwreset.request.php b/include/client/pwreset.request.php
new file mode 100644
index 0000000000000000000000000000000000000000..28922c8d2a9d6028ab18ec533ea7be9873c847ec
--- /dev/null
+++ b/include/client/pwreset.request.php
@@ -0,0 +1,26 @@
+<?php
+if(!defined('OSTCLIENTINC')) die('Access Denied');
+
+$userid=Format::input($_POST['userid']);
+?>
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address in the form below and press the
+<strong>Send Email</strong> button to have a password reset link sent to
+your email account on file.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="sendmail"/>
+    <strong><?php echo Format::htmlchars($banner); ?></strong>
+    <br>
+    <div>
+        <label for="username">Username:</label>
+        <input id="username" type="text" name="userid" size="30" value="<?php echo $userid; ?>">
+    </div>
+    <p>
+        <input class="btn" type="submit" value="Send Email">
+    </p>
+    </div>
+</form>
diff --git a/include/client/pwreset.sent.php b/include/client/pwreset.sent.php
new file mode 100644
index 0000000000000000000000000000000000000000..c76f50957ecc56ec8a2946a26d729e364d332968
--- /dev/null
+++ b/include/client/pwreset.sent.php
@@ -0,0 +1,13 @@
+<h1>Forgot My Password</h1>
+<p>
+Enter your username or email address in the form below and press the
+<strong>Send Email</strong> button to have a password reset link sent to
+your email account on file.
+
+<form action="pwreset.php" method="post" id="clientLogin">
+    <div style="width:50%;display:inline-block">
+    We have sent you a reset email to the email address you have on file for
+    your account. If you do not receive the email or cannot reset your
+    password, please ...
+    </div>
+</form>
diff --git a/include/client/register.confirm.inc.php b/include/client/register.confirm.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..a0628c0bcd287ed760cc95d48b44f8b1dae380bd
--- /dev/null
+++ b/include/client/register.confirm.inc.php
@@ -0,0 +1,17 @@
+<?php if ($content) {
+    list($title, $body) = $ost->replaceTemplateVariables(
+        array($content->getName(), $content->getBody())); ?>
+<h1><?php echo Format::display($title); ?></h1>
+<p><?php
+echo Format::display($body); ?>
+</p>
+<?php } else { ?>
+<h1>Account Registration</h1>
+<p>
+<strong>Thanks for registering for an account.</strong>
+</p>
+<p>
+We've just sent you an email to the address you entered. Please follow the
+link in the email to confirm your account and gain access to your tickets.
+</p>
+<?php } ?>
diff --git a/include/client/register.confirmed.inc.php b/include/client/register.confirmed.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..5bba31e8c539699fa2834be37c03a86a3cca30e6
--- /dev/null
+++ b/include/client/register.confirmed.inc.php
@@ -0,0 +1,18 @@
+<?php if ($content) {
+    list($title, $body) = $ost->replaceTemplateVariables(
+        array($content->getName(), $content->getBody())); ?>
+<h1><?php echo Format::display($title); ?></h1>
+<p><?php
+echo Format::display($body); ?>
+</p>
+<?php } else { ?>
+<h1>Account Registration</h1>
+<p>
+<strong>Thanks for registering for an account.</strong>
+</p>
+<p>
+You've confirmed your email address and successfully activated your account.
+You may proceed to check on previously opened tickets or open a new ticket.
+</p>
+<p><em>Your friendly support center</em></p>
+<?php } ?>
diff --git a/include/client/register.inc.php b/include/client/register.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..0a5676781c0de4625ac2e033279ed3c67ec7bbf5
--- /dev/null
+++ b/include/client/register.inc.php
@@ -0,0 +1,115 @@
+<?php
+$info = $_POST;
+if (!isset($info['timezone_id']))
+    $info += array(
+        'timezone_id' => $cfg->getDefaultTimezoneId(),
+        'dst' => $cfg->observeDaylightSaving(),
+        'backend' => null,
+    );
+if (isset($user) && $user instanceof ClientCreateRequest) {
+    $bk = $user->getBackend();
+    $info = array_merge($info, array(
+        'backend' => $bk::$id,
+        'username' => $user->getUsername(),
+    ));
+}
+
+?>
+<h1>Account Registration</h1>
+<p>
+Use the forms below to create or update the information we have on file for
+your account
+</p>
+<form action="account.php" method="post">
+  <?php csrf_token(); ?>
+  <input type="hidden" name="do" value="<?php echo $_REQUEST['do']
+    ?: ($info['backend'] ? 'import' :'create'); ?>" />
+<table width="800" class="padded">
+<tbody>
+<?php
+    $cf = $user_form ?: UserForm::getInstance();
+    $cf->render(false);
+?>
+<tr>
+    <td colspan="2">
+        <div><hr><h3>Preferences</h3>
+        </div>
+    </td>
+</tr>
+    <td>Time Zone:</td>
+    <td>
+        <select name="timezone_id" id="timezone_id">
+            <?php
+            $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id';
+            if(($res=db_query($sql)) && db_num_rows($res)){
+                while(list($id,$offset, $tz)=db_fetch_row($res)){
+                    $sel=($info['timezone_id']==$id)?'selected="selected"':'';
+                    echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz);
+                }
+            }
+            ?>
+        </select>
+        &nbsp;<span class="error"><?php echo $errors['timezone_id']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+       Daylight Saving:
+    </td>
+    <td>
+        <input type="checkbox" name="dst" value="1" <?php echo $info['dst']?'checked="checked"':''; ?>>
+        Observe daylight saving
+        <em>(Current Time: <strong><?php echo Format::date($cfg->getDateTimeFormat(),Misc::gmtime(),$info['tz_offset'],$info['dst']); ?></strong>)</em>
+    </td>
+</tr>
+<tr>
+    <td colspan=2">
+        <div><hr><h3>Access Credentials</h3></div>
+    </td>
+</tr>
+<?php if ($info['backend']) { ?>
+<tr>
+    <td width="180">
+        Login With:
+    </td>
+    <td>
+        <input type="hidden" name="backend" value="<?php echo $info['backend']; ?>"/>
+        <input type="hidden" name="username" value="<?php echo $info['username']; ?>"/>
+<?php foreach (UserAuthenticationBackend::allRegistered() as $bk) {
+    if ($bk::$id == $info['backend']) {
+        echo $bk::$name;
+        break;
+    }
+} ?>
+    </td>
+</tr>
+<?php } else { ?>
+<tr>
+    <td width="180">
+        Create a Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd1']; ?></span>
+    </td>
+</tr>
+<tr>
+    <td width="180">
+        Confirm New Password:
+    </td>
+    <td>
+        <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>">
+        &nbsp;<span class="error">&nbsp;<?php echo $errors['passwd2']; ?></span>
+    </td>
+</tr>
+<?php } ?>
+</tbody>
+</table>
+<hr>
+<p style="text-align: center;">
+    <input type="submit" value="Register"/>
+    <input type="button" value="Cancel" onclick="javascript:
+        window.location.href='index.php';"/>
+</p>
+</form>
+
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 1c227ba4cdec255b7e75607abfeab143e549d305..b355e2265d69e7c6121474edc41f414c4c3cee37 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -1,5 +1,5 @@
 <?php
-if(!defined('OSTCLIENTINC') || !is_object($thisclient) || !$thisclient->isValid() || !$cfg->showRelatedTickets()) die('Access Denied');
+if(!defined('OSTCLIENTINC') || !is_object($thisclient) || !$thisclient->isValid()) die('Access Denied');
 
 $qstr='&'; //Query string collector
 $status=null;
diff --git a/include/client/view.inc.php b/include/client/view.inc.php
index 469740f6c7e60d1c434d8b1d9709c4c27fcfe89e..27f2bd0845f90a09050801e8bb6763a1ef0840e8 100644
--- a/include/client/view.inc.php
+++ b/include/client/view.inc.php
@@ -8,7 +8,18 @@ $dept = $ticket->getDept();
 if(!$dept || !$dept->isPublic())
     $dept = $cfg->getDefaultDept();
 
-?>
+if ($thisclient && $thisclient->isGuest()
+    && $cfg->isClientRegistrationEnabled()) { ?>
+
+<div id="msg_info">
+    <i class="icon-compass icon-2x pull-left"></i>
+    <strong>Looking for your other tickets?</strong></br>
+    <a href="login.php" style="text-decoration:underline">Sign in</a> or
+    <a href="account.php?do=create" style="text-decoration:underline">register for an account</a>
+    for the best experience on our help desk.</div>
+
+<?php } ?>
+
 <table width="800" cellpadding="1" cellspacing="0" border="0" id="ticketInfo">
     <tr>
         <td colspan="2" width="100%">
diff --git a/include/i18n/en_US/config.yaml b/include/i18n/en_US/config.yaml
index 17c7083c31afd188c833e97ce2727fd01c641b54..9ec6537a20eaf27afa0a7568a7c7048a1237ccd0 100644
--- a/include/i18n/en_US/config.yaml
+++ b/include/i18n/en_US/config.yaml
@@ -81,3 +81,4 @@ core:
     random_ticket_ids: 1
     log_level: 2
     log_graceperiod: 12
+    client_registration: 'public'
diff --git a/include/i18n/en_US/help/tips/settings.access.yaml b/include/i18n/en_US/help/tips/settings.access.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..911a4b34c3b665cdf1be6d3930344fc28ac5c520
--- /dev/null
+++ b/include/i18n/en_US/help/tips/settings.access.yaml
@@ -0,0 +1,22 @@
+#
+# This is popup help messages for the Admin Panel -> Settings -> System page
+#
+# Fields:
+# title - Shown in bold at the top of the popover window
+# content - The body of the help popover
+# links - List of links shows below the content
+#   title - Link title
+#   href - href of link (links starting with / are translated to the
+#       helpdesk installation path)
+#
+# The key names such as 'helpdesk_name' should not be translated as they
+# must match the HTML #ids put into the page template.
+#
+---
+# Authentication settings
+password_reset:
+    title: Password Expiration Policy
+    content: >
+        Sets how often (in months) staff members will be required to change
+        their password. If disabled (set to "No expiration"), passwords will
+        not expire.
diff --git a/include/i18n/en_US/help/tips/settings.system.yaml b/include/i18n/en_US/help/tips/settings.system.yaml
index 1d08d55b0d9f77bd2ee8122a971e5fcd3e7ca1b8..60e336b73470394cc7f7821317dd883e80d2006d 100644
--- a/include/i18n/en_US/help/tips/settings.system.yaml
+++ b/include/i18n/en_US/help/tips/settings.system.yaml
@@ -68,14 +68,6 @@ name_format:
         Email templates will use the format for names if no other format is
         specified in the place holder.
 
-# Authentication settings
-password_reset:
-    title: Password Expiration Policy
-    content: >
-        Sets how often (in months) staff members will be required to change
-        their password. If disabled (set to "No expiration"), passwords will
-        not expire.
-
 # Date and time options
 date_and_time:
     title: Localized Date Formats
diff --git a/include/i18n/en_US/templates/email/staff.pwreset.yaml b/include/i18n/en_US/templates/email/staff.pwreset.yaml
deleted file mode 100644
index 12f6fd0f3d1268fc29a30281d6019b6c1ab0b3bf..0000000000000000000000000000000000000000
--- a/include/i18n/en_US/templates/email/staff.pwreset.yaml
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Email template: staff.pwreset
-#
-# Sent when a staff member requests a password reset via the <a>Forgot my
-# password</a> link in the staff control login screen
-#
----
-notes: |
-    Sent when a staff member requests a password reset via the <a>Forgot my
-    password</a> link in the staff control login screen
-
-subject: |
-    osTicket Staff Password Reset
-body: |
-    <h3><strong>Hi %{staff.name.first},</strong></h3>
-    A password reset request has been submitted on your behalf for the
-    helpdesk at %{url}.
-    <br>
-    <br>
-    If you feel that this has been done in error, delete and disregard this
-    email. Your account is still secure and no one has been given access to
-    it. It is not locked and your password has not been reset. Someone could
-    have mistakenly entered your email address.
-    <br>
-    <br>
-    Follow the link below to login to the help desk and change your
-    password.
-    <br>
-    <br>
-    <a href="%{reset_link}">%{reset_link}</a>
-    <br>
-    <br>
-    <em style="font-size: small">Your friendly Customer Support System</em>
-    <br>
-    <img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" alt="Powered by osTicket"
-    width="126" height="19" style="width: 126px;">
diff --git a/include/i18n/en_US/templates/email/user.accesslink.yaml b/include/i18n/en_US/templates/email/user.accesslink.yaml
deleted file mode 100644
index acd50fe0fd5b9c89b9efffffa72e271b85491f0e..0000000000000000000000000000000000000000
--- a/include/i18n/en_US/templates/email/user.accesslink.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Email template: user.accesslink
-#
-# Sent when a user requests an access link to check the status of a ticket
-#
-#
-#
----
-notes: |
-    Sent when a user requests an access link to check the status of a ticket
-
-subject: |
-    Ticket [#%{ticket.number}] Access Link
-body: |
-    <h3><strong>Hi %{recipient.name.first},</strong></h3>
-    An access link request for ticket #%{ticket.number} has been submitted on your behalf for the
-    helpdesk at %{url}.
-    <br>
-    <br>
-    Follow the link below to check the status of the ticket #%{ticket.number}.
-    <br>
-    <br>
-    <a href="%{recipient.ticket_link}">%{recipient.ticket_link}</a>
-    <br>
-    <br>If you <strong>did not</strong> make the request, please delete
-    and disregard this email. Your account is still secure and no one has
-    been given access to the ticket. Someone could have mistakenly entered
-    your email address.
-    <br>
-    <br>
-    --<br>
-    %{company.name}
diff --git a/include/i18n/en_US/templates/page/access-link.yaml b/include/i18n/en_US/templates/page/access-link.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cd445d171e1e9b243ffc5aa8a969e4245f18eda3
--- /dev/null
+++ b/include/i18n/en_US/templates/page/access-link.yaml
@@ -0,0 +1,28 @@
+#
+# access-link.yaml
+#
+# Ticket access link sent to clients for guest-only systems where the ticket
+# number and email address will trigger an access link sent via email
+#
+---
+notes: >
+    Ticket access link sent to clients for guest-only systems where the ticket
+    number and email address will trigger an access link sent via email
+name: "Ticket [#%{ticket.number}] Access Link"
+body: >
+    <h3><strong>Hi %{recipient.name.first},</strong></h3>
+    An access link request for ticket #%{ticket.number} has been submitted
+    on your behalf for the helpdesk at %{url}.<br />
+    <br />
+    Follow the link below to check the status of the ticket
+    #%{ticket.number}.<br />
+    <br />
+    <a href="%{recipient.ticket_link}">%{recipient.ticket_link}</a><br />
+    <br />
+    If you <strong>did not</strong> make the request, please delete and
+    disregard this email. Your account is still secure and no one has been
+    given access to the ticket. Someone could have mistakenly entered your
+    email address.<br />
+    <br />
+    --<br />
+    %{company.name}
diff --git a/include/i18n/en_US/templates/page/banner-client.yaml b/include/i18n/en_US/templates/page/banner-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5bab4cd11c11027a3ca93740d481cb6f040a2a35
--- /dev/null
+++ b/include/i18n/en_US/templates/page/banner-client.yaml
@@ -0,0 +1,13 @@
+#
+# banner-client.yaml
+#
+# This forms the header on the staff login page. It can be useful to inform
+# your clients about your login and registration policies.
+---
+notes: >
+    This forms the header on the staff login page. It can be useful to
+    inform your clients about your login and registration policies.
+name: "Sign in to %{company.name}"
+body: >
+    To better serve you, we encourage our clients to register for an account
+    and verify the email address we have on record.
diff --git a/include/i18n/en_US/templates/page/banner-staff.yaml b/include/i18n/en_US/templates/page/banner-staff.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..cde341856b768d108f378bf7f2d5086e0fd7714e
--- /dev/null
+++ b/include/i18n/en_US/templates/page/banner-staff.yaml
@@ -0,0 +1,9 @@
+#
+# banner-staff.yaml
+#
+# This is the initial message and banner shown on the staff login page
+---
+notes: >
+    This is the initial message and banner shown on the staff login page
+name: "Authentication Required"
+body: ""
diff --git a/include/i18n/en_US/templates/page/pwreset-client.yaml b/include/i18n/en_US/templates/page/pwreset-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..d3ea3a3917f4754e3c1e0eab805e8a60bde72bbb
--- /dev/null
+++ b/include/i18n/en_US/templates/page/pwreset-client.yaml
@@ -0,0 +1,28 @@
+#
+# pwreset-client.yaml
+#
+# Template of the email sent to clients when using the Forgot My Password
+# link on the login page
+---
+notes: >
+    Template of the email sent to clients when using the Forgot My Password
+    link on the login page
+name: "%{company.name} Help Desk Access"
+body: >
+    <h3><strong>Hi %{user.name.first},</strong></h3>
+    A password reset request has been submitted on your behalf for the
+    helpdesk at %{url}.<br />
+    <br />
+    If you feel that this has been done in error, delete and disregard this
+    email. Your account is still secure and no one has been given access to
+    it. It is not locked and your password has not been reset. Someone could
+    have mistakenly entered your email address.<br />
+    <br />
+    Follow the link below to login to the help desk and change your
+    password.<br />
+    <br />
+    <a href="%{link}">%{link}</a><br />
+    <br />
+    <em style="font-size: small">Your friendly Customer Support System
+    <br />
+    %{company.name}</em>
diff --git a/include/i18n/en_US/templates/page/pwreset-staff.yaml b/include/i18n/en_US/templates/page/pwreset-staff.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ff8c8c157fc1143848281f13c2fa4e0630ad7eba
--- /dev/null
+++ b/include/i18n/en_US/templates/page/pwreset-staff.yaml
@@ -0,0 +1,29 @@
+#
+# pwreset-staff.yaml
+#
+# Template of the email sent to staff members when using the Forgot My
+# Password link
+---
+notes: >
+    Template of the email sent to staff members when using the Forgot My
+    Password link
+name: "osTicket Staff Password Reset"
+body: >
+    <h3><strong>Hi %{staff.name.first},</strong></h3>
+    A password reset request has been submitted on your behalf for the
+    helpdesk at %{url}.<br />
+    <br />
+    If you feel that this has been done in error, delete and disregard this
+    email. Your account is still secure and no one has been given access to
+    it. It is not locked and your password has not been reset. Someone could
+    have mistakenly entered your email address.<br />
+    <br />
+    Follow the link below to login to the help desk and change your
+    password.<br />
+    <br />
+    <a href="%{link}">%{link}</a><br />
+    <br />
+    <em style="font-size: small">Your friendly Customer Support System</em>
+    <br />
+    <img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" alt="Powered by
+    osTicket" width="126" height="19" style="width: 126px" />
diff --git a/include/i18n/en_US/templates/page/registration-client.yaml b/include/i18n/en_US/templates/page/registration-client.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..ea820a31fb9a6a945a2156a570fb369258225699
--- /dev/null
+++ b/include/i18n/en_US/templates/page/registration-client.yaml
@@ -0,0 +1,24 @@
+#
+# registration-staff.yaml
+#
+# Confirmation email sent to clients when accounts are created for them by
+# staff or via the client portal. This email serves as an email address
+# verification.
+#
+---
+notes: >
+    Confirmation email sent to clients when accounts are created for them by
+    staff or via the client portal. This email serves as an email address
+    verification. Please use %{link} somewhere in the body.
+name: "Welcome to %{company.name}"
+body: >
+    <h3><strong>Hi %{recipient.name.first},</strong></h3> We've created an
+    account for you at our help desk at %{url}.<br />
+    <br />
+    Please follow the link below to confirm your account and gain access to
+    your tickets.<br />
+    <br />
+    <a href="%{link}">%{link}</a><br />
+    <br />
+    <em style="font-size: small">Your friendly Customer Support System<br
+    />%{company.name}</em>
diff --git a/include/i18n/en_US/templates/page/registration-confirm.yaml b/include/i18n/en_US/templates/page/registration-confirm.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..00aa6880940fd5a32fda2362fee360bde992e126
--- /dev/null
+++ b/include/i18n/en_US/templates/page/registration-confirm.yaml
@@ -0,0 +1,22 @@
+#
+# registration-confirm.yaml
+#
+# Template of the page shown to the user after registering for an account.
+# The system will send the user an email with a link they should follow to
+# confirm the account. This page should inform them of the next step in
+# the process.
+#
+---
+notes: >
+    Template of the page shown to the user after registering for an account.
+    The system will send the user an email with a link they should follow to
+    confirm the account. This page should inform them of the next step in
+    the process.
+name: "Account registration"
+body: >
+    <strong>Thanks for registering for an account.</strong>
+    <p>
+    We've just sent you an email to the address you entered. Please follow
+    the link in the email to confirm your account and gain access to your
+    tickets.
+    </p>
diff --git a/include/i18n/en_US/templates/page/registration-staff.yaml b/include/i18n/en_US/templates/page/registration-staff.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..c544a06f0e537ddcabffba8fafc4907b07820784
--- /dev/null
+++ b/include/i18n/en_US/templates/page/registration-staff.yaml
@@ -0,0 +1,22 @@
+#
+# registration-staff.yaml
+#
+# Initial (optional) email sent to staff members when accounts are created
+# for them in the staff control panel
+#
+---
+notes: >
+    Initial (optional) email sent to staff members when accounts are created
+    for them in the staff control panel
+name: "Welcome to osTicket"
+body: >
+    <h3><strong>Hi %{user.name.first},</strong></h3>
+    We've created an account for you at our help desk at %{url}.<br />
+    <br />
+    Please follow the link below to confirm your account and gain access to
+    your tickets.<br />
+    <br />
+    <a href="%{link}">%{link}</a><br />
+    <br />
+    <em style="font-size: small">Your friendly Customer Support System
+    <br />%{company.name}</em>
diff --git a/include/i18n/en_US/templates/page/registration-thanks.yaml b/include/i18n/en_US/templates/page/registration-thanks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..adfb922a76d2d0eb5d90121e38103e4b3961a381
--- /dev/null
+++ b/include/i18n/en_US/templates/page/registration-thanks.yaml
@@ -0,0 +1,22 @@
+#
+# registration-thanks.yaml
+#
+# Page shown to the user after successfully registring and confirming their
+# account. This page should inform the user that the process is complete and
+# that the user can now submit a ticket or access existing tickets
+#
+---
+notes: >
+    Page shown to the user after successfully registring and confirming their
+    account. This page should inform the user that the process is complete and
+    that the user can now submit a ticket or access existing tickets
+name: "Account Confirmed!"
+body: >
+    <strong>Thanks for registering for an account.</strong><br />
+    <br />
+    You've confirmed your email address and successfully activated your
+    account. You may proceed to check on previously opened tickets or open a
+    new ticket.<br />
+    <br />
+    <em>Your friendly support center</em><br />
+    %{company.name}
diff --git a/include/staff/login.tpl.php b/include/staff/login.tpl.php
index 35fffcc98ebb9374656b9f4fee90945e15c89d3b..d65aeea471a03ae27b7748e8fa82468ce5a73e35 100644
--- a/include/staff/login.tpl.php
+++ b/include/staff/login.tpl.php
@@ -5,6 +5,7 @@ $info = ($_POST && $errors)?Format::htmlchars($_POST):array();
 <div id="loginBox">
     <h1 id="logo"><a href="index.php">osTicket Staff Control Panel</a></h1>
     <h3><?php echo Format::htmlchars($msg); ?></h3>
+    <div><small><?php echo ($content) ? Format::display($content->getBody()) : ''; ?></small></div>
     <form action="login.php" method="post">
         <?php csrf_token(); ?>
         <input type="hidden" name="do" value="scplogin">
diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php
index 34a3fa2f9d2ca062b285400e398a2ffc756598a4..5dff4eced87e1d141ff292cff029cd46b6a94119 100644
--- a/include/staff/pages.inc.php
+++ b/include/staff/pages.inc.php
@@ -6,7 +6,7 @@ $sql='SELECT page.id, page.isactive, page.name, page.created, page.updated, '
      .'page.type, count(topic.topic_id) as topics '
      .' FROM '.PAGE_TABLE.' page '
      .' LEFT JOIN '.TOPIC_TABLE.' topic ON(topic.page_id=page.id) '
-     .' WHERE 1 ';
+     .' WHERE type in ("other","landing","thank-you","offline") ';
 $sortOptions=array(
         'name'=>'page.name', 'status'=>'page.isactive',
         'created'=>'page.created', 'updated'=>'page.updated',
diff --git a/include/staff/settings-access.inc.php b/include/staff/settings-access.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..35c8ef8a53915b2220e4d9a14c79544673b8009b
--- /dev/null
+++ b/include/staff/settings-access.inc.php
@@ -0,0 +1,201 @@
+<?php
+if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied');
+
+?>
+<h2>Access Control Settings</h2>
+<form action="settings.php?t=access" method="post" id="save">
+<?php csrf_token(); ?>
+<input type="hidden" name="t" value="access" >
+<table class="form_table settings_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th colspan="2">
+                <h4>Configure Access to this Help Desk</h4>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <th colspan="2">
+                <em><b>Staff Authentication Settings</b></em>
+            </th>
+        </tr>
+        <tr><td>Password Expiration Policy:</th>
+            <td>
+                <select name="passwd_reset_period">
+                   <option value="0"> &mdash; No expiration &mdash;</option>
+                  <?php
+                    for ($i = 1; $i <= 12; $i++) {
+                        echo sprintf('<option value="%d" %s>%s%s</option>',
+                                $i,(($config['passwd_reset_period']==$i)?'selected="selected"':''), $i>1?"Every $i ":'', $i>1?' Months':'Monthly');
+                    }
+                    ?>
+                </select>
+                <font class="error"><?php echo $errors['passwd_reset_period']; ?></font>
+                <i class="help-tip icon-question-sign" href="#password_reset"></i>
+            </td>
+        </tr>
+        <tr><td>Allow Password Resets:</th>
+            <td>
+              <input type="checkbox" name="allow_pw_reset" <?php echo $config['allow_pw_reset']?'checked="checked"':''; ?>>
+              <em>Enables the <u>Forgot my password</u> link on the staff
+              control panel</em>
+            </td>
+        </tr>
+        <tr><td>Password Reset Window:</th>
+            <td>
+              <input type="text" name="pw_reset_window" size="6" value="<?php
+                    echo $config['pw_reset_window']; ?>">
+                Maximum time <em>in minutes</em> a password reset token can
+                be valid.
+                &nbsp;<font class="error">&nbsp;<?php echo $errors['pw_reset_window']; ?></font>
+            </td>
+        </tr>
+        <tr><td>Staff Excessive Logins:</td>
+            <td>
+                <select name="staff_max_logins">
+                  <?php
+                    for ($i = 1; $i <= 10; $i++) {
+                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_max_logins']==$i)?'selected="selected"':''), $i);
+                    }
+                    ?>
+                </select> failed login attempt(s) allowed before a
+                <select name="staff_login_timeout">
+                  <?php
+                    for ($i = 1; $i <= 10; $i++) {
+                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_login_timeout']==$i)?'selected="selected"':''), $i);
+                    }
+                    ?>
+                </select> minute lock-out is enforced.
+            </td>
+        </tr>
+        <tr><td>Staff Session Timeout:</td>
+            <td>
+              <input type="text" name="staff_session_timeout" size=6 value="<?php echo $config['staff_session_timeout']; ?>">
+                Maximum idle time in minutes before a staff member must log in again (enter 0 to disable).
+            </td>
+        </tr>
+        <tr><td>Bind Staff Session to IP:</td>
+            <td>
+              <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>>
+              <em>(binds staff session to originating IP address upon login)</em>
+            </td>
+        </tr>
+        <tr>
+            <th colspan="2">
+                <em><b>Client Authentication Settings</b></em>
+            </th>
+        </tr>
+        <tr><td>Registration Required:</td>
+            <td><input type="checkbox" name="clients_only" <?php
+                if ($config['clients_only'])
+                    echo 'checked="checked"'; ?>/>
+                Require registration and login to create tickets
+            </td>
+        <tr><td>Registration Method:</td>
+            <td><select name="client_registration">
+<?php foreach (array(
+    'disabled' => 'Disabled — All users are guests',
+    'public' => 'Public — Anyone can register',
+    'closed' => 'Private — Only staff can register clients',)
+    as $key=>$val) { ?>
+        <option value="<?php echo $key; ?>" <?php
+        if ($config['client_registration'] == $key)
+            echo 'selected="selected"'; ?>><?php echo $val;
+        ?></option><?php
+    } ?>
+            </select></td>
+        </tr>
+        <tr><td>Client Excessive Logins:</td>
+            <td>
+                <select name="client_max_logins">
+                  <?php
+                    for ($i = 1; $i <= 10; $i++) {
+                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_max_logins']==$i)?'selected="selected"':''), $i);
+                    }
+
+                    ?>
+                </select> failed login attempt(s) allowed before a
+                <select name="client_login_timeout">
+                  <?php
+                    for ($i = 1; $i <= 10; $i++) {
+                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_login_timeout']==$i)?'selected="selected"':''), $i);
+                    }
+                    ?>
+                </select> minute lock-out is enforced.
+            </td>
+        </tr>
+        <tr><td>Client Session Timeout:</td>
+            <td>
+              <input type="text" name="client_session_timeout" size=6 value="<?php echo $config['client_session_timeout']; ?>">
+                &nbsp;Maximum idle time in minutes before a client must log in again (enter 0 to disable).
+            </td>
+        </tr>
+    </tbody>
+    <thead>
+        <tr>
+            <th colspan="2">
+                <h4>Authentication and Registration Templates</h4>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+<?php
+$res = db_query('select distinct(`type`), content_id, notes, name, updated from '
+    .PAGE_TABLE
+    .' where isactive=1 group by `type`');
+$contents = array();
+while (list($type, $id, $notes, $name, $u) = db_fetch_row($res))
+    $contents[$type] = array($id, $name, $notes, $u);
+
+$manage_content = function($title, $content) use ($contents) {
+    list($id, $name, $notes, $upd) = $contents[$content];
+    $notes = explode('. ', $notes);
+    $notes = $notes[0];
+    ?><tr><td colspan="2">
+    <a href="#ajax.php/content/<?php echo $id; ?>/manage"
+    onclick="javascript:
+        $.dialog($(this).attr('href').substr(1), 200);
+    return false;"><i class="icon-file-text pull-left icon-2x"
+        style="color:#bbb;"></i> <?php
+    echo Format::htmlchars($title); ?></a><br/>
+        <span class="faded" style="display:inline-block;width:90%"><?php
+        echo Format::display($notes); ?>
+    <em>(Last Updated <?php echo Format::db_datetime($upd); ?>)</em></span></td></tr><?php
+}; ?>
+        <tr>
+            <th colspan="2">
+                <em><b>Authentication and Registration Templates</b></em>
+            </th>
+        </tr>
+        <?php $manage_content('Staff Members', 'pwreset-staff'); ?>
+        <?php $manage_content('Clients', 'pwreset-client'); ?>
+        <?php $manage_content('Guess Ticket Access', 'access-link'); ?>
+        <tr>
+            <th colspan="2">
+                <em><b>Sign-In Pages</b></em>
+            </th>
+        </tr>
+        <?php $manage_content('Staff Login Banner', 'banner-staff'); ?>
+        <?php $manage_content('Client Sign-In Page', 'banner-client'); ?>
+        <tr>
+            <th colspan="2">
+                <em><b>Client Account Registration</b></em>
+            </th>
+        </tr>
+        <?php $manage_content('Please Confirm Email Address Page', 'registration-confirm'); ?>
+        <?php $manage_content('Confirmation Email', 'registration-client'); ?>
+        <?php $manage_content('Account Confirmed Page', 'registration-thanks'); ?>
+        <tr>
+            <th colspan="2">
+                <em><b>Staff Account Registration</b></em>
+            </th>
+        </tr>
+        <?php $manage_content('Staff Welcome Email', 'registration-staff'); ?>
+</tbody>
+</table>
+<p style="text-align:center">
+    <input class="button" type="submit" name="submit" value="Save Changes">
+    <input class="button" type="reset" name="reset" value="Reset Changes">
+</p>
+</form>
diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php
index b4c95d0247d77b0199e60ca796d21aa433a26835..1d12a8e142e2bb94ae98cb0c02e24c613592299c 100644
--- a/include/staff/settings-emails.inc.php
+++ b/include/staff/settings-emails.inc.php
@@ -117,6 +117,12 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
                 <i class="help-tip icon-question-sign" href="#use_email_priority"></i>
             </td>
         </tr>
+        <tr>
+            <td width="180">Accept Unregistered Email:</td>
+            <td><input type="checkbox" name="accept_unregistered_email" <?php
+    echo $config['accept_unregistered_email'] ? 'checked="checked"' : ''; ?>/>
+            Allow emailed tickets from clients without an account
+        </tr>
         <tr>
             <td width="180">Accept Email Collaborators:</td>
             <td><input type="checkbox" name="add_email_collabs" <?php
diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php
index 3ffd9b5af9cf7474c398b5b6bb6a22631afcdfcd..3156492fb8fe6de114d4e0290a83a7736026e8a1 100644
--- a/include/staff/settings-system.inc.php
+++ b/include/staff/settings-system.inc.php
@@ -118,98 +118,6 @@ $gmtime = Misc::gmtime();
                 <i class="help-tip icon-question-sign" href="#name_format"></i>
             </td>
         </tr>
-        <tr>
-            <th colspan="2">
-                <em><b>Authentication Settings</b></em>
-            </th>
-        </tr>
-        <tr><td>Password Expiration Policy:</th>
-            <td>
-                <select name="passwd_reset_period">
-                   <option value="0"> &mdash; No expiration &mdash;</option>
-                  <?php
-                    for ($i = 1; $i <= 12; $i++) {
-                        echo sprintf('<option value="%d" %s>%s%s</option>',
-                                $i,(($config['passwd_reset_period']==$i)?'selected="selected"':''), $i>1?"Every $i ":'', $i>1?' Months':'Monthly');
-                    }
-                    ?>
-                </select>
-                <font class="error"><?php echo $errors['passwd_reset_period']; ?></font>
-                <i class="help-tip icon-question-sign" href="#password_reset"></i>
-            </td>
-        </tr>
-        <tr><td>Allow Password Resets:</th>
-            <td>
-              <input type="checkbox" name="allow_pw_reset" <?php echo $config['allow_pw_reset']?'checked="checked"':''; ?>>
-              <em>Enables the <u>Forgot my password</u> link on the staff
-              control panel</em>
-            </td>
-        </tr>
-        <tr><td>Password Reset Window:</th>
-            <td>
-              <input type="text" name="pw_reset_window" size="6" value="<?php
-                    echo $config['pw_reset_window']; ?>">
-                Maximum time <em>in minutes</em> a password reset token can
-                be valid.
-                &nbsp;<font class="error">&nbsp;<?php echo $errors['pw_reset_window']; ?></font>
-            </td>
-        </tr>
-        <tr><td>Staff Excessive Logins:</td>
-            <td>
-                <select name="staff_max_logins">
-                  <?php
-                    for ($i = 1; $i <= 10; $i++) {
-                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_max_logins']==$i)?'selected="selected"':''), $i);
-                    }
-                    ?>
-                </select> failed login attempt(s) allowed before a
-                <select name="staff_login_timeout">
-                  <?php
-                    for ($i = 1; $i <= 10; $i++) {
-                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['staff_login_timeout']==$i)?'selected="selected"':''), $i);
-                    }
-                    ?>
-                </select> minute lock-out is enforced.
-            </td>
-        </tr>
-        <tr><td>Staff Session Timeout:</td>
-            <td>
-              <input type="text" name="staff_session_timeout" size=6 value="<?php echo $config['staff_session_timeout']; ?>">
-                Maximum idle time in minutes before a staff member must log in again (enter 0 to disable).
-            </td>
-        </tr>
-        <tr><td>Client Excessive Logins:</td>
-            <td>
-                <select name="client_max_logins">
-                  <?php
-                    for ($i = 1; $i <= 10; $i++) {
-                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_max_logins']==$i)?'selected="selected"':''), $i);
-                    }
-
-                    ?>
-                </select> failed login attempt(s) allowed before a
-                <select name="client_login_timeout">
-                  <?php
-                    for ($i = 1; $i <= 10; $i++) {
-                        echo sprintf('<option value="%d" %s>%d</option>', $i,(($config['client_login_timeout']==$i)?'selected="selected"':''), $i);
-                    }
-                    ?>
-                </select> minute lock-out is enforced.
-            </td>
-        </tr>
-
-        <tr><td>Client Session Timeout:</td>
-            <td>
-              <input type="text" name="client_session_timeout" size=6 value="<?php echo $config['client_session_timeout']; ?>">
-                &nbsp;Maximum idle time in minutes before a client must log in again (enter 0 to disable).
-            </td>
-        </tr>
-        <tr><td>Bind Staff Session to IP:</td>
-            <td>
-              <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>>
-              <em>(binds staff session to originating IP address upon login)</em>
-            </td>
-        </tr>
         <tr>
             <th colspan="2">
                 <em><b>Date and Time Options</b>&nbsp;
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index c3d204328c300e9f8d3235883cf52b1227a14499..796ffcdd1bca64af46cac9282d991aa2066dbbd5 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -76,13 +76,6 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
                 <em>(Minutes to lock a ticket on activity - enter 0 to disable locking)</em>
             </td>
         </tr>
-        <tr>
-            <td width="180">Show Related Tickets:</td>
-            <td>
-                <input type="checkbox" name="show_related_tickets" value="1" <?php echo $config['show_related_tickets'] ?'checked="checked"':''; ?> >
-                <em>(Show all related tickets on user login - otherwise access is restricted to one ticket view per login)</em>
-            </td>
-        </tr>
         <tr>
             <td>Human Verification:</td>
             <td>
diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php
index 55351fbee7864581e1294a72e56d758d2d068c48..954aaa8f106eda30b116f8c8ef84d3af9c0793c4 100644
--- a/include/staff/staff.inc.php
+++ b/include/staff/staff.inc.php
@@ -12,6 +12,7 @@ if($staff && $_REQUEST['a']!='add'){
     $info=$staff->getInfo();
     $info['id']=$staff->getId();
     $info['teams'] = $staff->getTeams();
+    $info['signature'] = Format::viewableImages($info['signature']);
     $qstr.='&id='.$staff->getId();
 }else {
     $title='Add New Staff';
@@ -20,9 +21,12 @@ if($staff && $_REQUEST['a']!='add'){
     $passwd_text='Temporary password required only for "Local" authenication';
     //Some defaults for new staff.
     $info['change_passwd']=1;
+    $info['welcome_email']=1;
     $info['isactive']=1;
     $info['isvisible']=1;
     $info['isadmin']=0;
+    $info['timezone_id'] = $cfg->getDefaultTimezoneId();
+    $info['daylight_saving'] = $cfg->observeDaylightSaving();
     $qstr.='&a=add';
 }
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
@@ -106,6 +110,22 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 &nbsp;<span class="error">&nbsp;<?php echo $errors['mobile']; ?></span>
             </td>
         </tr>
+<?php if (!$staff) { ?>
+        <tr>
+            <td width="180">Welcome Email</td>
+            <td><input type="checkbox" name="welcome_email" id="welcome-email" <?php
+                if ($info['welcome_email']) echo 'checked="checked"';
+                ?> onchange="javascript:
+                var sbk = $('#backend-selection');
+                if ($(this).is(':checked'))
+                    $('#password-fields').hide();
+                else if (sbk.val() == '' || sbk.val() == 'local')
+                    $('#password-fields').show();
+                " />
+                Send staff welcome email with account access link
+            </td>
+        </tr>
+<?php } ?>
         <tr>
             <th colspan="2">
                 <em><strong>Authentication</strong>: <?php echo $passwd_text; ?> &nbsp;<span class="error">&nbsp;<?php echo $errors['temppasswd']; ?></span></em>
@@ -114,10 +134,10 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         <tr>
             <td>Authentication Backend</td>
             <td>
-            <select name="backend" onchange="javascript:
+            <select name="backend" id="backend-selection" onchange="javascript:
                 if (this.value != '' && this.value != 'local')
                     $('#password-fields').hide();
-                else
+                else if (!$('#welcome-email').is(':checked'))
                     $('#password-fields').show();
                 ">
                 <option value="">&mdash; Use any available backend &mdash;</option>
@@ -131,8 +151,9 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
             </select>
             </td>
         </tr>
-        </tbody>
-        <tbody id="password-fields" style="<?php if ($info['backend'] && $info['backend'] != 'local')
+    </tbody>
+    <tbody id="password-fields" style="<?php
+        if ($info['welcome_email'] || ($info['backend'] && $info['backend'] != 'local'))
             echo 'display:none;'; ?>">
         <tr>
             <td width="180">
diff --git a/include/staff/templates/content-manage.tmpl.php b/include/staff/templates/content-manage.tmpl.php
new file mode 100644
index 0000000000000000000000000000000000000000..d2a546cd0367b02b9c4defb3f63c8531200972c8
--- /dev/null
+++ b/include/staff/templates/content-manage.tmpl.php
@@ -0,0 +1,26 @@
+<h3>Manage Content &mdash; <?php echo Format::htmlchars($content->getName()); ?></h3>
+<a class="close" href=""><i class="icon-remove-circle"></i></a>
+<hr/>
+<form method="post" action="#content/<?php echo $content->getId(); ?>">
+    <input type="text" style="width: 100%; font-size: 14pt" name="name" value="<?php
+        echo Format::htmlchars($content->getName()); ?>" />
+    <div style="margin-top: 5px">
+    <textarea class="richtext no-bar" name="body"><?php
+    echo Format::viewableImages($content->getBody());
+?></textarea>
+    </div>
+    <div id="msg_info" style="margin-top:7px"><?php
+echo $content->getNotes(); ?></div>
+    <hr/>
+    <p class="full-width">
+        <span class="buttons" style="float:left">
+            <input type="reset" value="Reset">
+            <input type="button" name="cancel" class="<?php echo $user ? 'cancel' : 'close' ?>"  value="Cancel">
+        </span>
+        <span class="buttons" style="float:right">
+            <input type="submit" value="Save Changes">
+        </span>
+     </p>
+</form>
+</div>
+<div class="clear"></div>
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 3ac67bf82846c663c880c6fc441e5e108e310989..19fc2a074f6659a91cc9a83bc9ce83334660c472 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-f5692e24c7afba7ab6168dde0b3bb3c8
+4323a6a81c35efbf7722b7fc4e475440
diff --git a/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..c3bc8ccc3a34347bd98745a7cd3eab026d9996a0
--- /dev/null
+++ b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql
@@ -0,0 +1,122 @@
+/**
+ * @version v1.8.2
+ * @signature 4323a6a81c35efbf7722b7fc4e475440
+ * @title Add client login feature
+ *
+ */
+
+ALTER TABLE `%TABLE_PREFIX%session`
+  CHANGE `user_id` `user_id` varchar(16) NOT NULL default '0' COMMENT 'osTicket staff/client ID';
+
+ALTER TABLE `%TABLE_PREFIX%staff`
+  CHANGE `signature` `signature` text NOT NULL;
+
+ALTER TABLE `%TABLE_PREFIX%department`
+  CHANGE `dept_signature` `dept_signature` text NOT NULL;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%email_account`;
+CREATE TABLE `%TABLE_PREFIX%email_account` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) NOT NULL,
+  `active` tinyint(1) NOT NULL DEFAULT '1',
+  `protocol` varchar(64) NOT NULL DEFAULT '',
+  `host` varchar(128) NOT NULL DEFAULT '',
+  `port` int(11) NOT NULL,
+  `username` varchar(128) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `options` varchar(512) DEFAULT NULL,
+  `errors` int(11) unsigned DEFAULT NULL,
+  `created` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
+  `lastconnect` timestamp NULL DEFAULT NULL,
+  `lasterror` timestamp NULL DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
+
+ALTER TABLE `%TABLE_PREFIX%ticket_thread`
+  ADD `format` varchar(16) NOT NULL default 'html' AFTER `body`;
+
+ALTER TABLE `%TABLE_PREFIX%faq_category`
+  CHANGE `created` `created` datetime NOT NULL,
+  CHANGE `updated` `updated` datetime NOT NULL;
+
+ALTER TABLE `%TABLE_PREFIX%filter`
+  ADD `topic_id` int(11) unsigned NOT NULL default '0' AFTER `form_id`;
+
+ALTER TABLE `%TABLE_PREFIX%email`
+  ADD `topic_id` int(11) unsigned NOT NULL default '0' AFTER `dept_id`;
+
+ALTER TABLE `%TABLE_PREFIX%help_topic`
+  ADD `sort` int(10) unsigned NOT NULL default '0' AFTER `form_id`;
+
+-- Add `content_id` to the content table to allow for translations
+RENAME TABLE `%TABLE_PREFIX%page` TO `%TABLE_PREFIX%content`;
+ALTER TABLE `%TABLE_PREFIX%content`
+  CHANGE `type` `type` varchar(32) NOT NULL default 'other',
+  ADD `content_id` int(10) unsigned NOT NULL default 0 AFTER `id`;
+
+UPDATE `%TABLE_PREFIX%content`
+  SET `content_id` = `id`;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%user_account`;
+CREATE TABLE `%TABLE_PREFIX%user_account` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(10) unsigned NOT NULL,
+  `org_id` int(11) unsigned NOT NULL,
+  `status` int(11) unsigned NOT NULL DEFAULT '0',
+  `timezone_id` int(11) NOT NULL DEFAULT '0',
+  `dst` tinyint(1) NOT NULL DEFAULT '1',
+  `lang` varchar(16) DEFAULT NULL,
+  `username` varchar(64) DEFAULT NULL,
+  `passwd` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
+  `backend` varchar(32) DEFAULT NULL,
+  `registered` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username` (`username`)
+) DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%organization`;
+CREATE TABLE `%TABLE_PREFIX%organization` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) NOT NULL DEFAULT '',
+  `staff_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `created` timestamp NULL DEFAULT NULL,
+  `updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
+
+DELETE FROM `%TABLE_PREFIX%config` where `namespace`='core'
+    AND `key` = 'show_related_tickets';
+
+-- Transfer access link template
+INSERT INTO `%TABLE_PREFIX%content`
+    (`name`, `body`, `type`, `isactive`, `created`, `updated`)
+    SELECT A1.`subject`, A1.`body`, 'access-link', 1, A1.`created`, A1.`updated`
+    FROM `%TABLE_PREFIX%email_template` A1
+    WHERE A1.`tpl_id` = (SELECT `value` FROM `%TABLE_PREFIX%config` A3
+        WHERE A3.`key` = 'default_template_id' and `namespace` = 'core')
+    AND A1.`code_name` = 'user.accesslink';
+
+UPDATE `%TABLE_PREFIX%content` SET `content_id` = LAST_INSERT_ID()
+    WHERE `id` = LAST_INSERT_ID();
+
+-- Transfer staff password reset link
+INSERT INTO `%TABLE_PREFIX%content`
+    (`name`, `body`, `type`, `isactive`, `created`, `updated`)
+    SELECT A1.`subject`, A1.`body`, 'pwreset-staff', 1, A1.`created`, A1.`updated`
+    FROM `%TABLE_PREFIX%email_template` A1
+    WHERE A1.`tpl_id` = (SELECT `value` FROM `%TABLE_PREFIX%config` A3
+        WHERE A3.`key` = 'default_template_id' and `namespace` = 'core')
+    AND A1.`code_name` = 'staff.pwreset';
+
+UPDATE `%TABLE_PREFIX%content` SET `content_id` = LAST_INSERT_ID()
+    WHERE `id` = LAST_INSERT_ID();
+
+-- No longer saved in the email_template table
+DELETE FROM `%TABLE_PREFIX%email_template`
+    WHERE `code_name` IN ('staff.pwreset', 'user.accesslink');
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '4323a6a81c35efbf7722b7fc4e475440'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/f5692e24-4323a6a8.task.php b/include/upgrader/streams/core/f5692e24-4323a6a8.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..566fab7a20ad09887f6e59cd30ff73c630a09f17
--- /dev/null
+++ b/include/upgrader/streams/core/f5692e24-4323a6a8.task.php
@@ -0,0 +1,37 @@
+<?php
+
+
+class TemplateContentLoader extends MigrationTask {
+    var $description = "Loading initial system templates";
+
+    function run($max_time) {
+        foreach (array(
+                'registration-staff', 'pwreset-staff', 'banner-staff',
+                'registration-client', 'pwreset-client', 'banner-client',
+                'registration-confirm', 'registration-thanks',
+                'access-link') as $type) {
+            $i18n = new Internationalization();
+            $tpl = $i18n->getTemplate("templates/page/{$type}.yaml");
+            if (!($page = $tpl->getData()))
+                // No such template on disk
+                continue;
+
+            if ($id = db_result(db_query('select id from '.PAGE_TABLE
+                    .' where `type`='.db_input($type))))
+                // Already have a template for the content type
+                continue;
+
+            $sql = 'INSERT INTO '.PAGE_TABLE.' SET type='.db_input($type)
+                .', name='.db_input($page['name'])
+                .', body='.db_input($page['body'])
+                .', lang='.db_input($tpl->getLang())
+                .', notes='.db_input($page['notes'])
+                .', created=NOW(), updated=NOW(), isactive=1';
+            db_query($sql);
+        }
+        // Set the content_id for all the new items
+        db_query('UPDATE '.PAGE_TABLE
+            .' SET `content_id` = `id` WHERE `content_id` = 0');
+    }
+}
+return 'TemplateContentLoader';
diff --git a/login.php b/login.php
index 9e6d5f36f18d64384f96818ae82498a774553344..5d1faa1ecd24944e76a4b453731ffb164a425301 100644
--- a/login.php
+++ b/login.php
@@ -24,13 +24,44 @@ define('OSTCLIENTINC',TRUE); //make includes happy
 require_once(INCLUDE_DIR.'class.client.php');
 require_once(INCLUDE_DIR.'class.ticket.php');
 
-$inc = 'login.inc.php';
-if ($_POST) {
-    if (!$_POST['lticket'] || !Validator::is_email($_POST['lemail']))
+if ($cfg->getClientRegistrationMode() == 'disabled'
+        || isset($_POST['lticket']))
+    $inc = 'accesslink.inc.php';
+else
+    $inc = 'login.inc.php';
+
+$suggest_pwreset = false;
+if ($_POST && isset($_POST['luser'])) {
+    if (!$_POST['luser'])
+        $errors['err'] = 'Valid username or email address is required';
+    elseif (($user = UserAuthenticationBackend::process($_POST['luser'],
+            $_POST['lpasswd'], $errors))) {
+        if ($user instanceof ClientCreateRequest) {
+            if ($cfg && $cfg->isClientRegistrationEnabled()) {
+                $inc = 'register.inc.php';
+                $user_form = UserForm::getUserForm()->getForm($user->getInfo());
+            }
+            else {
+                $errors['err'] = 'Access Denied. Contact your help desk
+                    administrator to have an account registered for you';
+                // fall through to show login page again
+            }
+        }
+        else {
+            Http::redirect($_SESSION['_client']['auth']['dest']
+                ?: 'tickets.php');
+        }
+    } elseif(!$errors['err']) {
+        $errors['err'] = 'Invalid username or password - try again!';
+    }
+    $suggest_pwreset = true;
+}
+elseif ($_POST && isset($_POST['lticket'])) {
+    if (!Validator::is_email($_POST['lemail']))
         $errors['err'] = 'Valid email address and ticket number required';
     elseif (($user = UserAuthenticationBackend::process($_POST['lemail'],
-                    $_POST['lticket'], $errors))) {
-        //We're using authentication backend so we can guard aganist brute
+            $_POST['lticket'], $errors))) {
+        // We're using authentication backend so we can guard aganist brute
         // force attempts (which doesn't buy much since the link is emailed)
         $user->sendAccessLink();
         $msg = sprintf("%s - access link sent to your email!",
@@ -41,8 +72,10 @@ if ($_POST) {
     }
 }
 
-$nav = new UserNav();
-$nav->setActiveNav('status');
+if (!$nav) {
+    $nav = new UserNav();
+    $nav->setActiveNav('status');
+}
 require CLIENTINC_DIR.'header.inc.php';
 require CLIENTINC_DIR.$inc;
 require CLIENTINC_DIR.'footer.inc.php';
diff --git a/open.php b/open.php
index 4cb7684a8167b7cd5a3c07f67d876b83c313cb4d..acb25fd70a9b94261f80ab1908eff404359d8f45 100644
--- a/open.php
+++ b/open.php
@@ -17,7 +17,7 @@ require('client.inc.php');
 define('SOURCE','Web'); //Ticket source.
 $ticket = null;
 $errors=array();
-if($_POST):
+if ($_POST) {
     $vars = $_POST;
     $vars['deptId']=$vars['emailId']=0; //Just Making sure we don't accept crap...only topicId is expected.
     if ($thisclient) {
@@ -60,23 +60,30 @@ if($_POST):
     }else{
         $errors['err']=$errors['err']?$errors['err']:'Unable to create a ticket. Please correct errors below and try again!';
     }
-endif;
+}
 
 //page
 $nav->setActiveNav('new');
+if ($cfg->isClientLoginRequired()) {
+    if (!$thisclient) {
+        require_once 'secure.inc.php';
+    }
+    elseif ($thisclient->isGuest()) {
+        require_once 'login.php';
+        exit();
+    }
+}
+
 require(CLIENTINC_DIR.'header.inc.php');
 if($ticket
         && (
             (($topic = $ticket->getTopic()) && ($page = $topic->getPage()))
             || ($page = $cfg->getThankYouPage())
-            )) { //Thank the user and promise speedy resolution!
-    //Hide ticket number -  it should only be delivered via email for security reasons.
-    echo Format::safe_html($ticket->replaceVars(str_replace(
-                    array('%{ticket.number}', '%{ticket.extId}', '%{ticket}'), //ticket number vars.
-                    array_fill(0, 3, 'XXXXXX'),
-                    $page->getBody()
-                    )));
-} else {
+        )) {
+    //Thank the user and promise speedy resolution!
+    echo Format::display($ticket->replaceVars($page->getBody()));
+}
+else {
     require(CLIENTINC_DIR.'open.inc.php');
 }
 require(CLIENTINC_DIR.'footer.inc.php');
diff --git a/profile.php b/profile.php
index e17bf690eb9241ff2b7bbad74efe8684bed447d2..47c100aef5fe46f44c1072e0248bc47058f9f047 100644
--- a/profile.php
+++ b/profile.php
@@ -23,7 +23,10 @@ $user = User::lookup($thisclient->getId());
 
 if ($user && $_POST) {
     $errors = array();
-    if ($user->updateInfo($_POST, $errors))
+    if ($acct = $thisclient->getAccount()) {
+       $acct->update($_POST, $errors);
+    }
+    if (!$errors && $user->updateInfo($_POST, $errors))
         Http::redirect('tickets.php');
 }
 
diff --git a/pwreset.php b/pwreset.php
new file mode 100644
index 0000000000000000000000000000000000000000..6fb51b48728df6292ca5daddc24586434bca39c6
--- /dev/null
+++ b/pwreset.php
@@ -0,0 +1,85 @@
+<?php
+
+require_once('client.inc.php');
+if(!defined('INCLUDE_DIR')) die('Fatal Error');
+define('CLIENTINC_DIR',INCLUDE_DIR.'client/');
+define('OSTCLIENTINC',TRUE); //make includes happy
+
+require_once(INCLUDE_DIR.'class.client.php');
+
+$inc = 'pwreset.request.php';
+if($_POST) {
+    if (!$ost->checkCSRFToken()) {
+        Http::response(400, 'Valid CSRF Token Required');
+        exit;
+    }
+    switch ($_POST['do']) {
+        case 'sendmail':
+            if (($acct=ClientAccount::lookupByUsername($_POST['userid']))) {
+                if (!$acct->hasPassword()) {
+                    $banner = 'Unable to reset password. Contact your administrator';
+                }
+                elseif (!$acct->sendResetEmail()) {
+                    $inc = 'pwreset.sent.php';
+                }
+            }
+            else
+                $banner = 'Unable to verify username '
+                    .Format::htmlchars($_POST['userid']);
+            break;
+        case 'reset':
+            $inc = 'pwreset.login.php';
+            $errors = array();
+            if ($client = UserAuthenticationBackend::processSignOn($errors)) {
+                Http::redirect('index.php');
+            }
+            elseif (isset($errors['msg'])) {
+                $banner = $errors['msg'];
+            }
+            break;
+    }
+}
+elseif ($_GET['token']) {
+    $banner = 'Re-enter your username or email';
+    $_config = new Config('pwreset');
+    if (($id = $_config->get($_GET['token']))
+            && ($acct = ClientAccount::lookup(array('user_id'=>$id)))) {
+        if (!$acct->isConfirmed()) {
+            $inc = 'register.confirmed.inc.php';
+            $acct->confirm();
+            // TODO: Log the user in
+            if ($client = UserAuthenticationBackend::processSignOn($errors)) {
+                if ($acct->hasPassword() && !$acct->get('backend')) {
+                    $acct->cancelResetTokens();
+                }
+                // No password setup yet -- force one to be created
+                else {
+                    $_SESSION['_client']['reset-token'] = $_GET['token'];
+                    $acct->forcePasswdReset();
+                }
+                Http::redirect('account.php?confirmed');
+            }
+        }
+        else {
+            $inc = 'pwreset.login.php';
+        }
+    }
+    elseif ($id && ($user = User::lookup($id)))
+        $inc = 'pwreset.create.php';
+    else
+        Http::redirect('index.php');
+}
+elseif ($cfg->allowPasswordReset()) {
+    $banner = 'Enter your username or email address below';
+}
+else {
+    $_SESSION['_staff']['auth']['msg']='Password resets are disabled';
+    return header('Location: index.php');
+}
+
+$nav = new UserNav();
+$nav->setActiveNav('status');
+require CLIENTINC_DIR.'header.inc.php';
+require CLIENTINC_DIR.$inc;
+require CLIENTINC_DIR.'footer.inc.php';
+?>
diff --git a/scp/ajax.php b/scp/ajax.php
index 5bc266064fda0a468c766d093371efffa411d3c5..9ef736ef7999896da99e8c889e97dde6a7b682d2 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -42,7 +42,10 @@ $dispatcher = patterns('',
     url('^/content/', patterns('ajax.content.php:ContentAjaxAPI',
         url_get('^log/(?P<id>\d+)', 'log'),
         url_get('^ticket_variables', 'ticket_variables'),
-        url_get('^signature/(?P<type>\w+)(?:/(?P<id>\d+))?$', 'getSignature')
+        url_get('^signature/(?P<type>\w+)(?:/(?P<id>\d+))?$', 'getSignature'),
+        url_get('^(?P<id>\d+)/(?:(?P<lang>\w+)/)?manage$', 'manageContent'),
+        url_get('^(?P<id>[\w-]+)/(?:(?P<lang>\w+)/)?manage$', 'manageNamedContent'),
+        url_post('^(?P<id>\d+)(?:/(?P<lang>\w+))?$', 'updateContent')
     )),
     url('^/config/', patterns('ajax.config.php:ConfigAjaxAPI',
         url_get('^scp', 'scp')
diff --git a/scp/css/scp.css b/scp/css/scp.css
index ea1a1dddeec181a5c63271437c39f00925068394..4500c2dfbeb0a04c7820a08775f148bcef77c5be 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -12,6 +12,10 @@ a {
     text-decoration:none;
 }
 
+.form_table a:hover {
+    text-decoration: underline;
+}
+
 .centered {
     text-align:center;
 }
@@ -544,6 +548,7 @@ a.print {
 
 .form_table td {
     border-bottom:1px solid #ddd;
+    height: 20px;
 }
 
 table.fixed {
diff --git a/scp/login.php b/scp/login.php
index 20f53938e163334af2db7e8f83d2d0e450ac35fa..0505a9ce8d9d1a3dd099e06daa7600c41c7aa260 100644
--- a/scp/login.php
+++ b/scp/login.php
@@ -19,9 +19,11 @@ if(!defined('INCLUDE_DIR')) die('Fatal Error. Kwaheri!');
 require_once(INCLUDE_DIR.'class.staff.php');
 require_once(INCLUDE_DIR.'class.csrf.php');
 
+$content = Page::lookup(Page::getIdByType('banner-staff'));
+
 $dest = $_SESSION['_staff']['auth']['dest'];
 $msg = $_SESSION['_staff']['auth']['msg'];
-$msg = $msg?$msg:'Authentication Required';
+$msg = $msg ?: $content->getName();
 $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php';
 $show_reset = false;
 if($_POST) {
diff --git a/scp/pwreset.php b/scp/pwreset.php
index b2826014c9b46b27572d3de6f3c6801c89183a6d..f5eed25fc0ed98e37cfa4d18dfc098c858bffe77 100644
--- a/scp/pwreset.php
+++ b/scp/pwreset.php
@@ -62,10 +62,11 @@ if($_POST) {
     }
 }
 elseif ($_GET['token']) {
-    $msg = 'Re-enter your username or email';
+    $msg = 'Please enter your username or email';
     $_config = new Config('pwreset');
     if (($id = $_config->get($_GET['token']))
             && ($staff = Staff::lookup($id)))
+        // TODO: Detect staff confirmation (for welcome email)
         $tpl = 'pwreset.login.php';
     else
         header('Location: index.php');
diff --git a/scp/settings.php b/scp/settings.php
index b72cdf2c377a436f8d188e11c8ea38f41937d708..a4243fd957fb780eea3b151fd96c8b9770ef9ccb 100644
--- a/scp/settings.php
+++ b/scp/settings.php
@@ -24,6 +24,8 @@ $settingOptions=array(
         array('Email Settings', 'settings.email'),
     'pages' =>
         array('Site Pages', 'settings.pages'),
+    'access' =>
+        array('Access Control', 'settings.access'),
     'kb' =>
         array('Knowledgebase Settings', 'settings.kb'),
     'autoresp' =>
diff --git a/scp/staff.php b/scp/staff.php
index b2d78f3cfb866d7b59596fd9e5f304292883165f..9a134e7e6376f9b0f99873b961e7ed44befabc94 100644
--- a/scp/staff.php
+++ b/scp/staff.php
@@ -31,7 +31,7 @@ if($_POST){
             break;
         case 'create':
             if(($id=Staff::create($_POST,$errors))){
-                $msg=Format::htmlchars($_POST['name']).' added successfully';
+                $msg=Format::htmlchars($_POST['firstname']).' added successfully';
                 $_REQUEST['a']=null;
             }elseif(!$errors['err']){
                 $errors['err']='Unable to add staff. Correct any error(s) below and try again.';
diff --git a/secure.inc.php b/secure.inc.php
index bf6a75b3e032590ca6ec89346e50c67393fd5514..8a0116306ae0fd6d5c50168c6a85feba6e80e110 100644
--- a/secure.inc.php
+++ b/secure.inc.php
@@ -20,7 +20,9 @@ require_once('client.inc.php');
 //Client Login page: Ajax interface can pre-declare the function to trap logins.
 if(!function_exists('clientLoginPage')) {
     function clientLoginPage($msg ='') {
-        global $ost;
+        global $ost, $cfg, $nav;
+        $_SESSION['_client']['auth']['dest'] =
+            '/' . ltrim($_SERVER['REQUEST_URI'], '/');
         require('./login.php');
         exit;
     }
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 85f06999698f21093930575429e967ff7e28ab29..c15a946a6aa97c5e965a1ebebe6667f36b46b681 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -48,8 +48,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq_category` (
   `name` varchar(125) default NULL,
   `description` TEXT NOT NULL,
   `notes` tinytext NOT NULL,
-  `created` date NOT NULL,
-  `updated` date NOT NULL,
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
   PRIMARY KEY  (`category_id`),
   KEY (`ispublic`)
 ) DEFAULT CHARSET=utf8;
@@ -181,7 +181,7 @@ CREATE TABLE `%TABLE_PREFIX%department` (
   `autoresp_email_id` int(10) unsigned NOT NULL default '0',
   `manager_id` int(10) unsigned NOT NULL default '0',
   `dept_name` varchar(128) NOT NULL default '',
-  `dept_signature` tinytext NOT NULL,
+  `dept_signature` text NOT NULL,
   `ispublic` tinyint(1) unsigned NOT NULL default '1',
   `group_membership` tinyint(1) NOT NULL default '0',
   `ticket_auto_response` tinyint(1) NOT NULL default '1',
@@ -212,6 +212,7 @@ CREATE TABLE `%TABLE_PREFIX%email` (
   `noautoresp` tinyint(1) unsigned NOT NULL default '0',
   `priority_id` tinyint(3) unsigned NOT NULL default '2',
   `dept_id` tinyint(3) unsigned NOT NULL default '0',
+  `topic_id` int(11) unsigned NOT NULL default '0',
   `email` varchar(255) NOT NULL default '',
   `name` varchar(255) NOT NULL default '',
   `userid` varchar(255) NOT NULL,
@@ -243,6 +244,25 @@ CREATE TABLE `%TABLE_PREFIX%email` (
   KEY `dept_id` (`dept_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%email_account`;
+CREATE TABLE `%TABLE_PREFIX%email_account` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) NOT NULL,
+  `active` tinyint(1) NOT NULL DEFAULT '1',
+  `protocol` varchar(64) NOT NULL DEFAULT '',
+  `host` varchar(128) NOT NULL DEFAULT '',
+  `port` int(11) NOT NULL,
+  `username` varchar(128) DEFAULT NULL,
+  `password` varchar(255) DEFAULT NULL,
+  `options` varchar(512) DEFAULT NULL,
+  `errors` int(11) unsigned DEFAULT NULL,
+  `created` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
+  `updated` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP,
+  `lastconnect` timestamp NULL DEFAULT NULL,
+  `lasterror` timestamp NULL DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%filter`;
 CREATE TABLE `%TABLE_PREFIX%filter` (
   `id` int(11) unsigned NOT NULL auto_increment,
@@ -261,6 +281,7 @@ CREATE TABLE `%TABLE_PREFIX%filter` (
   `team_id` int(10) unsigned NOT NULL default '0',
   `sla_id` int(10) unsigned NOT NULL default '0',
   `form_id` int(11) unsigned NOT NULL default '0',
+  `topic_id` int(11) unsigned NOT NULL default '0',
   `target` ENUM(  'Any',  'Web',  'Email',  'API' ) NOT NULL DEFAULT  'Any',
   `name` varchar(32) NOT NULL default '',
   `notes` text,
@@ -385,6 +406,7 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` (
   `sla_id` int(10) unsigned NOT NULL default '0',
   `page_id` int(10) unsigned NOT NULL default '0',
   `form_id` int(10) unsigned NOT NULL default '0',
+  `sort` int(10) unsigned NOT NULL default '0',
   `topic` varchar(32) NOT NULL default '',
   `notes` text,
   `created` datetime NOT NULL,
@@ -399,6 +421,16 @@ CREATE TABLE `%TABLE_PREFIX%help_topic` (
   KEY `page_id` (`page_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%organization`;
+CREATE TABLE `%TABLE_PREFIX%organization` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `name` varchar(128) NOT NULL DEFAULT '',
+  `staff_id` int(10) unsigned NOT NULL DEFAULT '0',
+  `created` timestamp NULL DEFAULT NULL,
+  `updated` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`)
+) DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%canned_response`;
 CREATE TABLE `%TABLE_PREFIX%canned_response` (
   `canned_id` int(10) unsigned NOT NULL auto_increment,
@@ -422,7 +454,7 @@ CREATE TABLE `%TABLE_PREFIX%session` (
   `session_data` blob,
   `session_expire` datetime default NULL,
   `session_updated` datetime default NULL,
-  `user_id` int(10) unsigned NOT NULL default '0' COMMENT 'osTicket staff ID',
+  `user_id` varchar(16) NOT NULL default '0' COMMENT 'osTicket staff/client ID',
   `user_ip` varchar(64) NOT NULL,
   `user_agent` varchar(255) collate utf8_unicode_ci NOT NULL,
   PRIMARY KEY  (`session_id`),
@@ -445,7 +477,7 @@ CREATE TABLE `%TABLE_PREFIX%staff` (
   `phone` varchar(24) NOT NULL default '',
   `phone_ext` varchar(6) default NULL,
   `mobile` varchar(24) NOT NULL default '',
-  `signature` tinytext NOT NULL,
+  `signature` text NOT NULL,
   `notes` text,
   `isactive` tinyint(1) NOT NULL default '1',
   `isadmin` tinyint(1) NOT NULL default '0',
@@ -621,6 +653,7 @@ CREATE TABLE `%TABLE_PREFIX%ticket_thread` (
   `source` varchar(32) NOT NULL default '',
   `title` varchar(255),
   `body` text NOT NULL,
+  `format` varchar(16) NOT NULL default 'html',
   `ip_address` varchar(64) NOT NULL default '',
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
@@ -686,10 +719,11 @@ INSERT INTO `%TABLE_PREFIX%timezone` (`id`, `offset`, `timezone`) VALUES
   (30, 12.0, 'Auckland, Wellington, Fiji, Kamchatka');
 
 -- pages
-CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%page` (
+CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%content` (
   `id` int(10) unsigned NOT NULL auto_increment,
+  `content_id` int(10) unsigned NOT NULL default '0',
   `isactive` tinyint(1) unsigned NOT NULL default '0',
-  `type` enum('landing','offline','thank-you','other') NOT NULL default 'other',
+  `type` varchar(32) NOT NULL default 'other',
   `name` varchar(255) NOT NULL,
   `body` text NOT NULL,
   `lang` varchar(16) NOT NULL default 'en_US',
@@ -708,6 +742,7 @@ CREATE TABLE `%TABLE_PREFIX%plugin` (
   `install_path` varchar(60) not null,
   `isphar` tinyint(1) not null default 0,
   `isactive` tinyint(1) not null default 0,
+  `version` varchar(64),
   `installed` datetime not null,
   primary key (`id`)
 ) DEFAULT CHARSET=utf8;
@@ -731,3 +766,20 @@ CREATE TABLE `%TABLE_PREFIX%user_email` (
   UNIQUE KEY `address` (`address`),
   KEY `user_email_lookup` (`user_id`)
 ) DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%user_account`;
+CREATE TABLE `%TABLE_PREFIX%user_account` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `user_id` int(10) unsigned NOT NULL,
+  `org_id` int(11) unsigned NOT NULL,
+  `status` int(11) unsigned NOT NULL DEFAULT '0',
+  `timezone_id` int(11) NOT NULL DEFAULT '0',
+  `dst` tinyint(1) NOT NULL DEFAULT '1',
+  `lang` varchar(16) DEFAULT NULL,
+  `username` varchar(64) DEFAULT NULL,
+  `passwd` varchar(128) CHARACTER SET ascii COLLATE ascii_bin DEFAULT NULL,
+  `backend` varchar(32) DEFAULT NULL,
+  `registered` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `username` (`username`)
+) DEFAULT CHARSET=utf8;
diff --git a/tickets.php b/tickets.php
index 01d8b799c4220aa9ffc66ad7ce3e238ef2984925..9561da31dd62bd7ecbccb4f4b83326845b279e3f 100644
--- a/tickets.php
+++ b/tickets.php
@@ -16,6 +16,10 @@
 **********************************************************************/
 require('secure.inc.php');
 if(!is_object($thisclient) || !$thisclient->isValid()) die('Access denied'); //Double check again.
+
+if ($thisclient->isGuest())
+    $_REQUEST['id'] = $thisclient->getTicketId();
+
 require_once(INCLUDE_DIR.'class.ticket.php');
 require_once(INCLUDE_DIR.'class.json.php');
 $ticket=null;
@@ -28,6 +32,9 @@ if($_REQUEST['id']) {
     }
 }
 
+if (!$ticket && $thisclient->isGuest())
+    Http::redirect('view.php');
+
 //Process post...depends on $ticket object above.
 if($_POST && is_object($ticket) && $ticket->getId()):
     $errors=array();
@@ -98,7 +105,7 @@ if($ticket && $ticket->checkUserAccess($thisclient)) {
     }
     else
         $inc='view.inc.php';
-} elseif($cfg->showRelatedTickets() && $thisclient->getNumTickets()) {
+} elseif($thisclient->getNumTickets()) {
     $inc='tickets.inc.php';
 } else {
     $nav->setActiveNav('new');
diff --git a/view.php b/view.php
index b8590aab4f5ddaa25f004123dbfcbae950965a2a..abf7d805ac5acf5f82353ab66201ac787c9357bf 100644
--- a/view.php
+++ b/view.php
@@ -19,10 +19,15 @@ require_once('client.inc.php');
 // Try autologin the user
 // Authenticated user can be of type ticket owner or collaborator
 $errors = array();
-$user =  UserAuthenticationBackend::processSignOn($errors);
+$user =  UserAuthenticationBackend::processSignOn($errors, false);
 if ($user && $user->getTicketId())
     Http::redirect('tickets.php?id='.$user->getTicketId());
 
-//Simply redirecting to tickets.php until multiview is implemented.
-require('tickets.php');
+$nav = new UserNav();
+$nav->setActiveNav('status');
+
+$inc = 'accesslink.inc.php';
+require CLIENTINC_DIR.'header.inc.php';
+require CLIENTINC_DIR.$inc;
+require CLIENTINC_DIR.'footer.inc.php';
 ?>