diff --git a/include/ajax.config.php b/include/ajax.config.php
index 0bf0a4420411304e02d14ae639f450ddb6bdbda6..132da3daf9de1003d3c5625b723880430416f714 100644
--- a/include/ajax.config.php
+++ b/include/ajax.config.php
@@ -38,7 +38,7 @@ class ConfigAjaxAPI extends AjaxController {
         list($primary_sl, $primary_locale) = explode('_', $primary);
 
         $config=array(
-              'lock_time'       => ($cfg->getLockTime()*60),
+              'lock_time'       => $cfg->getTicketLockMode() == Lock::MODE_DISABLED ? 0 : ($cfg->getLockTime()*60),
               'html_thread'     => (bool) $cfg->isRichTextEnabled(),
               'date_format'     => $cfg->getDateFormat(true),
               'lang'            => $lang,
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index 30a8b74d74075d8e044f80d5a07ac733adfbc94e..31457ec4f80497849129f1a5f126ac7d8a4c21fb 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -92,7 +92,7 @@ class TicketsAjaxAPI extends AjaxController {
     function acquireLock($tid) {
         global $cfg, $thisstaff;
 
-        if(!$cfg || !$cfg->getLockTime())
+        if(!$cfg || !$cfg->getLockTime() || $cfg->getTicketLockMode() == Lock::MODE_DISABLED)
             Http::response(418, $this->encode(array('id'=>0, 'retry'=>false)));
 
         if(!$tid || !is_numeric($tid) || !$thisstaff)
diff --git a/include/class.client.php b/include/class.client.php
index 31e185c8bef7c763731c6e492214e5522414ee75..50fda6cef195f8e01e9e4277ade1a5cc31f82d57 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -303,7 +303,8 @@ class  EndUser extends BaseAuthenticatedUser {
     private function getStats() {
         $basic = Ticket::objects()
             ->annotate(array('count' => SqlAggregate::COUNT('ticket_id')))
-            ->values('status__state', 'topic_id');
+            ->values('status__state', 'topic_id')
+            ->distinct('status_id', 'topic_id');
 
         // Share tickets among the organization for owners only
         $mine = clone $basic;
diff --git a/include/class.config.php b/include/class.config.php
index 984a83c1619b23f1200622432333fedb5c898bf7..b3c75485c1ed9d8862656cb4e9c2e8ea1d1dc20f 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -176,6 +176,7 @@ class OsticketConfig extends Config {
         'verify_email_addrs' => 1,
         'client_avatar' => 'gravatar.mm',
         'agent_avatar' => 'gravatar.mm',
+        'ticket_lock' => 2, // Lock on activity
     );
 
     function OsticketConfig($section=null) {
@@ -424,6 +425,10 @@ class OsticketConfig extends Config {
         return $this->get('autolock_minutes');
     }
 
+    function getTicketLockMode() {
+        return $this->get('ticket_lock');
+    }
+
     function getAgentNameFormat() {
         return $this->get('agent_name_format');
     }
@@ -1204,6 +1209,7 @@ class OsticketConfig extends Config {
             'show_related_tickets'=>isset($vars['show_related_tickets'])?1:0,
             'hide_staff_name'=>isset($vars['hide_staff_name'])?1:0,
             'allow_client_updates'=>isset($vars['allow_client_updates'])?1:0,
+            'ticket_lock' => $vars['ticket_lock'],
         ));
     }
 
diff --git a/include/class.email.php b/include/class.email.php
index 4baa862f45c8e3d6eee798cbd20539c31c8cc7ab..92348241cc543f0041e6fa4ca69ab4a5c55d645a 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -200,10 +200,15 @@ class Email extends VerySimpleModel {
         Dept::objects()
             ->filter(array('email_id' => $this->getId()))
             ->update(array(
-                'autoresp_email_id' => 0,
                 'email_id' => $cfg->getDefaultEmailId()
             ));
 
+        Dept::objects()
+            ->filter(array('autoresp_email_id' => $this->getId()))
+            ->update(array(
+                'autoresp_email_id' => 0,
+            ));
+
         return true;
     }
 
diff --git a/include/class.file.php b/include/class.file.php
index e502ff80507e89c91040de4f099c8e46caddc327..580330b64cc8731cd1031c0e5974f1655e281e6b 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -531,11 +531,16 @@ class AttachmentFile extends VerySimpleModel {
     static function getBackendForFile($file) {
         global $cfg;
 
-        if (!$cfg)
+        $char = null;
+        if ($cfg) {
+            $char = $cfg->getDefaultStorageBackendChar();
+        }
+        try {
+            return FileStorageBackend::lookup($char ?: 'D', $file);
+        }
+        catch (Exception $x) {
             return new AttachmentChunkedData($file);
-
-        $char = $cfg->getDefaultStorageBackendChar();
-        return FileStorageBackend::lookup($char, $file);
+        }
     }
 
     static function lookupByHash($hash) {
diff --git a/include/class.forms.php b/include/class.forms.php
index 1ee044aa1d834d7ac6ef848bf86a635a8c693b2b..107cea8bf25acddfc36821ad400f700f7c002e25 100644
--- a/include/class.forms.php
+++ b/include/class.forms.php
@@ -597,6 +597,8 @@ class FormField {
     function getClean() {
         if (!isset($this->_clean)) {
             $this->_clean = (isset($this->value))
+                // XXX: The widget value may be parsed already if this is
+                //      linked to dynamic data via ::getAnswer()
                 ? $this->value : $this->parse($this->getWidget()->value);
 
             if ($vs = $this->get('cleaners')) {
@@ -2065,6 +2067,8 @@ class PriorityField extends ChoiceField {
     }
 
     function to_php($value, $id=false) {
+        if ($value instanceof Priority)
+            return $value;
         if (is_array($id)) {
             reset($id);
             $id = key($id);
@@ -2083,6 +2087,13 @@ class PriorityField extends ChoiceField {
             : $prio;
     }
 
+    function display($prio) {
+        if (!$prio instanceof Priority)
+            return parent::display($prio);
+        return sprintf('<span style="padding: 2px; background-color: %s">%s</span>',
+            $prio->getColor(), Format::htmlchars($prio->getDesc()));
+    }
+
     function toString($value) {
         return ($value instanceof Priority) ? $value->getDesc() : $value;
     }
diff --git a/include/class.lock.php b/include/class.lock.php
index d1c5bc3655748f72ff16c092657f32e74c843365..e5da2f048e894bd60733254a0604a5ae51c6fcf9 100644
--- a/include/class.lock.php
+++ b/include/class.lock.php
@@ -38,6 +38,10 @@ class Lock extends VerySimpleModel {
         ),
     );
 
+    const MODE_DISABLED = 0;
+    const MODE_ON_VIEW = 1;
+    const MODE_ON_ACTIVITY = 2;
+
     function getId() {
         return $this->lock_id;
     }
diff --git a/include/class.mailer.php b/include/class.mailer.php
index ee22db849f18ff121bad2c9d84313fc3f088de5b..b3c8c2111af544e231a564c3556d91bcfd4ffed1 100644
--- a/include/class.mailer.php
+++ b/include/class.mailer.php
@@ -278,7 +278,7 @@ class Mailer {
 
         // Round-trip detection - the first section is the local
         // system's message-id code
-        $rv['loopback'] = (0 === strcasecmp($rv['code'],
+        $rv['loopback'] = (0 === strcmp($rv['code'],
             static::getSystemMessageIdCode()));
 
         return $rv;
diff --git a/include/class.organization.php b/include/class.organization.php
index 0c08a4f30c08281e8b473747944b3c81046a1b0d..e870667b1f10e2132b9bdc51a2fa7c18e65f02aa 100644
--- a/include/class.organization.php
+++ b/include/class.organization.php
@@ -257,7 +257,7 @@ implements TemplateVariable {
         }
     }
 
-    function addForm($form, $sort=1, $data) {
+    function addForm($form, $sort=1, $data=null) {
         $entry = $form->instanciate($sort, $data);
         $entry->set('object_type', 'O');
         $entry->set('object_id', $this->getId());
diff --git a/include/class.orm.php b/include/class.orm.php
index 9798d94819db755e51664e83e739b4ec4dfde6f6..a92d476682f0ed2373adffe3c18dd1f35222ef88 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -919,6 +919,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
     var $distinct = array();
     var $lock = false;
     var $chain = array();
+    var $options = array();
 
     const LOCK_EXCLUSIVE = 1;
     const LOCK_SHARED = 2;
@@ -986,6 +987,9 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this;
     }
     function order_by($order, $direction=false) {
+        if ($order === false)
+            return $this->options(array('nosort' => true));
+
         $args = func_get_args();
         if (in_array($direction, array(self::ASC, self::DESC))) {
             $args = array($args[0]);
@@ -1184,6 +1188,11 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this;
     }
 
+    function options($options) {
+        $this->options = array_merge($this->options, $options);
+        return $this;
+    }
+
     function countSelectFields() {
         $count = count($this->values) + count($this->annotations);
         if (isset($this->extra['select']))
@@ -1259,6 +1268,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         // Load defaults from model
         $model = $this->model;
         $query = clone $this;
+        $options += $this->options;
         if ($options['nosort'])
             $query->ordering = array();
         elseif (!$query->ordering && $model::getMeta('ordering'))
@@ -2523,7 +2533,7 @@ class MySqlCompiler extends SqlCompiler {
                 }
                 // If there are annotations, add in these fields to the
                 // GROUP BY clause
-                if ($queryset->annotations)
+                if ($queryset->annotations && !$queryset->distinct)
                     $group_by[] = $unaliased;
             }
         }
@@ -2557,7 +2567,7 @@ class MySqlCompiler extends SqlCompiler {
                 }
             }
             // If no group by has been set yet, use the root model pk
-            if (!$group_by && !$queryset->aggregated) {
+            if (!$group_by && !$queryset->aggregated && !$queryset->distinct) {
                 foreach ($model::getMeta('pk') as $pk)
                     $group_by[] = $rootAlias .'.'. $pk;
             }
@@ -2580,29 +2590,31 @@ class MySqlCompiler extends SqlCompiler {
 
         $joins = $this->getJoins($queryset);
 
+        $sql = 'SELECT '.implode(', ', $fields).' FROM '
+            .$table.$joins.$where.$group_by.$having.$sort;
         // UNIONS
-        $unions='';
         if ($queryset->chain) {
+            // If the main query is sorted, it will need parentheses
+            if ($parens = (bool) $sort)
+                $sql = "($sql)";
             foreach ($queryset->chain as $qs) {
                 list($qs, $all) = $qs;
                 $q = $qs->getQuery(array('nosort' => true));
                 // Rewrite the parameter numbers so they fit the parameter numbers
                 // of the current parameters of the $compiler
                 $self = $this;
-                $sql = preg_replace_callback("/:(\d+)/",
+                $S = preg_replace_callback("/:(\d+)/",
                 function($m) use ($self, $q) {
                     $self->params[] = $q->params[$m[1]-1];
                     return ':'.count($self->params);
                 }, $q->sql);
                 // Wrap unions in parentheses if they are windowed or sorted
-                if ($qs->isWindowed() || count($qs->getSortFields()))
-                    $sql = "($sql)";
-                $unions .= ' UNION '.($all ? 'ALL ' : '').$sql;
+                if ($parens || $qs->isWindowed() || count($qs->getSortFields()))
+                    $S = "($S)";
+                $sql .= ' UNION '.($all ? 'ALL ' : '').$S;
             }
         }
 
-        $sql = 'SELECT '.implode(', ', $fields).' FROM '
-            .$table.$joins.$where.$group_by.$having.$unions.$sort;
         if ($queryset->limit)
             $sql .= ' LIMIT '.$queryset->limit;
         if ($queryset->offset)
diff --git a/include/class.thread.php b/include/class.thread.php
index 68d1d69358e1feb9db91de5ca05423fbd3dd9cbb..ffffe918d51e9229a7925578729a4756a2b92dbe 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -278,8 +278,6 @@ class Thread extends VerySimpleModel {
      *      - body - (string) email message body (decoded)
      */
     function postEmail($mailinfo) {
-        global $ost;
-
         // +==================+===================+=============+
         // | Orig Thread-Type | Reply Thread-Type | Requires    |
         // +==================+===================+=============+
@@ -303,27 +301,6 @@ class Thread extends VerySimpleModel {
             return false;
         }
 
-        // Mail sent by this system will have a message-id format of
-        // <code-random-mailbox@domain.tld>
-        // where code is a predictable string based on the SECRET_SALT of
-        // this osTicket installation. If this incoming mail matches the
-        // code, then it very likely originated from this system and looped
-        @list($code) = explode('-', $mailinfo['mid'], 2);
-        if (0 === strcasecmp(ltrim($code, '<'), substr(md5('mail'.SECRET_SALT), -9))) {
-            // This mail was sent by this system. It was received due to
-            // some kind of mail delivery loop. It should not be considered
-            // a response to an existing thread entry
-            if ($ost) $ost->log(LOG_ERR, _S('Email loop detected'), sprintf(
-                _S('It appears as though &lt;%s&gt; is being used as a forwarded or fetched email account and is also being used as a user / system account. Please correct the loop or seek technical assistance.'),
-                $mailinfo['email']),
-
-                // This is quite intentional -- don't continue the loop
-                false,
-                // Force the message, even if logging is disabled
-                true);
-            return true;
-        }
-
         $vars = array(
             'mid' =>    $mailinfo['mid'],
             'header' => $mailinfo['header'],
@@ -477,13 +454,13 @@ class Thread extends VerySimpleModel {
      */
     function lookupByEmailHeaders(&$mailinfo) {
         $possibles = array();
-        foreach (array('in-reply-to', 'references') as $header) {
+        foreach (array('mid', 'in-reply-to', 'references') as $header) {
             $matches = array();
             if (!isset($mailinfo[$header]) || !$mailinfo[$header])
                 continue;
             // Header may have multiple entries (usually separated by
             // spaces ( )
-            elseif (!preg_match_all('/<[^>@]+@[^>]+>/', $mailinfo[$header],
+            elseif (!preg_match_all('/<([^>@]+@[^>]+)>/', $mailinfo[$header],
                         $matches))
                 continue;
 
@@ -491,12 +468,12 @@ class Thread extends VerySimpleModel {
             // (parent) on the far right.
             // @see rfc 1036, section 2.2.5
             // @see http://www.jwz.org/doc/threading.html
-            $possibles = array_merge($possibles, array_reverse($matches[0]));
+            $possibles = array_merge($possibles, array_reverse($matches[1]));
         }
 
         // Add the message id if it is embedded in the body
         $match = array();
-        if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
+        if (preg_match('`(?:class="mid-|Ref-Mid: )([^"\s]*)(?:$|")`',
                 $mailinfo['message'], $match)
             && !in_array($match[1], $possibles)
         ) {
@@ -510,7 +487,9 @@ class Thread extends VerySimpleModel {
             // from this help desk, the 'loopback' property will be set
             // to true.
             $mid_info = Mailer::decodeMessageId($mid);
-            if ($mid_info['loopback'] && isset($mid_info['uid'])
+            if (!$mid_info || !$mid_info['loopback'])
+                continue;
+            if (isset($mid_info['uid'])
                 && @$mid_info['threadId']
                 && ($t = Thread::lookup($mid_info['threadId']))
             ) {
@@ -630,6 +609,8 @@ implements TemplateVariable {
     );
 
     function postEmail($mailinfo) {
+        global $ost;
+
         if (!($thread = $this->getThread()))
             // Kind of hard to continue a discussion without a thread ...
             return false;
@@ -638,6 +619,26 @@ implements TemplateVariable {
             // Reporting success so the email can be moved or deleted.
             return true;
 
+        // Mail sent by this system will have a predictable message-id
+        // If this incoming mail matches the code, then it very likely
+        // originated from this system and looped
+        $info = Mailer::decodeMessageId($mailinfo['mid']);
+        if ($info && $info['loopback']) {
+            // This mail was sent by this system. It was received due to
+            // some kind of mail delivery loop. It should not be considered
+            // a response to an existing thread entry
+            if ($ost)
+                $ost->log(LOG_ERR, _S('Email loop detected'), sprintf(
+                _S('It appears as though &lt;%s&gt; is being used as a forwarded or fetched email account and is also being used as a user / system account. Please correct the loop or seek technical assistance.'),
+                $mailinfo['email']),
+
+                // This is quite intentional -- don't continue the loop
+                false,
+                // Force the message, even if logging is disabled
+                true);
+            return $this;
+        }
+
         return $thread->postEmail($mailinfo);
     }
 
@@ -1127,6 +1128,7 @@ implements TemplateVariable {
         // in-reply-to header
         if ($entry = ThreadEntry::objects()
             ->filter(array('email_info__mid' => $mailinfo['mid']))
+            ->order_by(false)
             ->first()
         ) {
             $seen = true;
@@ -1134,13 +1136,13 @@ implements TemplateVariable {
         }
 
         $possibles = array();
-        foreach (array('in-reply-to', 'references') as $header) {
+        foreach (array('mid', 'in-reply-to', 'references') as $header) {
             $matches = array();
             if (!isset($mailinfo[$header]) || !$mailinfo[$header])
                 continue;
             // Header may have multiple entries (usually separated by
             // spaces ( )
-            elseif (!preg_match_all('/<[^>@]+@[^>]+>/', $mailinfo[$header],
+            elseif (!preg_match_all('/<([^>@]+@[^>]+)>/', $mailinfo[$header],
                         $matches))
                 continue;
 
@@ -1148,13 +1150,13 @@ implements TemplateVariable {
             // (parent) on the far right.
             // @see rfc 1036, section 2.2.5
             // @see http://www.jwz.org/doc/threading.html
-            $possibles = array_merge($possibles, array_reverse($matches[0]));
+            $possibles = array_merge($possibles, array_reverse($matches[1]));
         }
 
         // Add the message id if it is embedded in the body
         $match = array();
-        if (preg_match('`(?:data-mid="|Ref-Mid: )([^"\s]*)(?:$|")`',
-                $mailinfo['message'], $match)
+        if (preg_match('`(?:class="mid-|Ref-Mid: )([^"\s]*)(?:$|")`',
+                (string) $mailinfo['message'], $match)
             && !in_array($match[1], $possibles)
         ) {
             $possibles[] = $match[1];
@@ -1168,7 +1170,9 @@ implements TemplateVariable {
             // from this help desk, the 'loopback' property will be set
             // to true.
             $mid_info = Mailer::decodeMessageId($mid);
-            if ($mid_info['loopback'] && isset($mid_info['uid'])
+            if (!$mid_info || !$mid_info['loopback'])
+                continue;
+            if (isset($mid_info['uid'])
                 && @$mid_info['entryId']
                 && ($t = ThreadEntry::lookup($mid_info['entryId']))
                 && ($t->thread_id == $mid_info['threadId'])
@@ -1198,7 +1202,8 @@ implements TemplateVariable {
                 $mid = "$left@$right";
             }
             $entries = ThreadEntry::objects()
-                ->filter(array('email_info__mid' => $mid));
+                ->filter(array('email_info__mid' => $mid))
+                ->order_by(false);
             foreach ($entries as $t) {
                 // Capture the first match thread item
                 if (!$thread)
@@ -1248,24 +1253,6 @@ implements TemplateVariable {
             }
         }
 
-        // Search for the message-id token in the body
-        // *DEPRECATED* the current algo on outgoing mail will use
-        // Mailer::getMessageId as the message id tagged here
-        if (preg_match('`(?:class="mid-|Ref-Mid: )([^"\s]*)(?:$|")`',
-                $mailinfo['message'], $match)) {
-            // Support new Message-Id format
-            if (($info = Mailer::decodeMessageId($match[1]))
-                && $info['loopback']
-                && $info['entryId']
-            ) {
-                return ThreadEntry::lookup($info['entryId']);
-            }
-            // Support old (deprecated) reference format
-            if ($thread = ThreadEntry::lookupByRefMessageId($match[1],
-                    $mailinfo['email']))
-                return $thread;
-        }
-
         return null;
     }
 
diff --git a/include/class.user.php b/include/class.user.php
index 8d5b6836859e2aa0531bdbd659f1c680bb61c8ea..1dd4736b2740c2ecdbed95b228bef50d02f637b0 100644
--- a/include/class.user.php
+++ b/include/class.user.php
@@ -207,7 +207,9 @@ implements TemplateVariable {
         $user = static::lookupByEmail($vars['email']);
         if (!$user && $create) {
             $name = $vars['name'];
-            if (!$name)
+            if (is_array($name))
+                $name = implode(', ', $name);
+            elseif (!$name)
                 list($name) = explode('@', $vars['email'], 2);
 
             $user = User::create(array(
@@ -503,7 +505,10 @@ implements TemplateVariable {
         foreach ($forms as $entry) {
             if (($f=$entry->getDynamicForm()) && $f->get('type') == 'U') {
                 if (($name = $f->getField('name'))) {
-                    $this->name = $name->getClean();
+                    $name = $name->getClean();
+                    if (is_array($name))
+                        $name = implode(', ', $name);
+                    $this->name = $name;
                 }
 
                 if (($email = $f->getField('email'))) {
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index fef80c6008f3bae8555da341d00dd55a237e316e..cf3c5e1d92d1c83fd9b02a9bea18bc12f7e3470e 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -95,10 +95,12 @@ if ($settings['keywords']) {
         $tickets->filter(array('number__startswith'=>$q));
     } else { //Deep search!
         // Use the search engine to perform the search
-        $tickets = $ost->searcher->find($q, $tickets)->distinct('ticket_id');
+        $tickets = $ost->searcher->find($q, $tickets);
     }
 }
 
+$tickets->distinct('ticket_id');
+
 TicketForm::ensureDynamicDataView();
 
 $total=$tickets->count();
diff --git a/include/staff/filter.inc.php b/include/staff/filter.inc.php
index 8767a0ec8c790103f7549558308590983d0aa26c..817d20b6b82b6f9b10e18e78cd3b96a3c853aa42 100644
--- a/include/staff/filter.inc.php
+++ b/include/staff/filter.inc.php
@@ -342,7 +342,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
    };
    $(function() {
      $('#dynamic-actions').sortable({helper: fixHelper, opacity: 0.5});
-     var next = <?php echo $maxi; ?>;
+     var next = <?php echo $maxi ?: 0; ?>;
      $('#add-rule').click(function() {
        var clone = $('#new-rule-template tr').clone();
        clone.find('[data-name=rulew]').attr('name', 'rules['+next+'][w]');
@@ -351,5 +351,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
        clone.appendTo('#rules');
        next++;
      });
+<?php if (!$info['rules']) { ?>
+        $('#add-rule').trigger('click').trigger('click');
+<?php } ?>
    });
 </script>
diff --git a/include/staff/settings-access.inc.php b/include/staff/settings-access.inc.php
deleted file mode 100644
index c7cbc5034e79f4347a99778595c6c20f3a2cbc4c..0000000000000000000000000000000000000000
--- a/include/staff/settings-access.inc.php
+++ /dev/null
@@ -1,225 +0,0 @@
-<?php
-if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) die('Access Denied');
-
-?>
-<h2><?php echo __('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><?php echo __('Configure Access to this Help Desk'); ?></h4>
-            </th>
-        </tr>
-    </thead>
-    <tbody>
-        <tr>
-            <th colspan="2">
-                <em><b><?php echo __('Agent Authentication Settings'); ?></b></em>
-            </th>
-        </tr>
-        <tr><td><?php echo __('Password Expiration Policy'); ?>:</th>
-            <td>
-                <select name="passwd_reset_period">
-                   <option value="0"> &mdash; <?php echo __('No expiration'); ?> &mdash;</option>
-                  <?php
-                    for ($i = 1; $i <= 12; $i++) {
-                        echo sprintf('<option value="%d" %s>%s</option>',
-                                $i,(($config['passwd_reset_period']==$i)?'selected="selected"':''),
-                                sprintf(_N('Monthly', 'Every %d months', $i), $i));
-                    }
-                    ?>
-                </select>
-                <font class="error"><?php echo $errors['passwd_reset_period']; ?></font>
-                <i class="help-tip icon-question-sign" href="#password_expiration_policy"></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Allow Password Resets'); ?>:</th>
-            <td>
-              <input type="checkbox" name="allow_pw_reset" <?php echo $config['allow_pw_reset']?'checked="checked"':''; ?>>
-              &nbsp;<i class="help-tip icon-question-sign" href="#allow_password_resets"></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Reset Token Expiration'); ?>:</th>
-            <td>
-              <input type="text" name="pw_reset_window" size="6" value="<?php
-                    echo $config['pw_reset_window']; ?>">
-                    <em><?php echo __('minutes'); ?></em>
-                    <i class="help-tip icon-question-sign" href="#reset_token_expiration"></i>
-                &nbsp;<font class="error"><?php echo $errors['pw_reset_window']; ?></font>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Agent 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> <?php echo __(
-                'failed login attempt(s) allowed before a lock-out is enforced'); ?>
-                <br/>
-                <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> <?php echo __('minutes locked out'); ?>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Agent Session Timeout'); ?>:</td>
-            <td>
-              <input type="text" name="staff_session_timeout" size=6 value="<?php echo $config['staff_session_timeout']; ?>">
-                <?php echo __('minutes'); ?> <em><?php echo __('(0 to disable)'); ?></em>. <i class="help-tip icon-question-sign" href="#staff_session_timeout"></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Bind Agent Session to IP'); ?>:</td>
-            <td>
-              <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>>
-              <i class="help-tip icon-question-sign" href="#bind_staff_session_to_ip"></i>
-            </td>
-        </tr>
-        <tr>
-            <th colspan="2">
-                <em><b><?php echo __('End User Authentication Settings'); ?></b></em>
-            </th>
-        </tr>
-        <tr><td><?php echo __('Registration Required'); ?>:</td>
-            <td><input type="checkbox" name="clients_only" <?php
-                if ($config['clients_only'])
-                    echo 'checked="checked"'; ?>/> <?php echo __(
-                    'Require registration and login to create tickets'); ?>
-            <i class="help-tip icon-question-sign" href="#registration_method"></i>
-            </td>
-        <tr><td><?php echo __('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 agents can register users'),)
-    as $key=>$val) { ?>
-        <option value="<?php echo $key; ?>" <?php
-        if ($config['client_registration'] == $key)
-            echo 'selected="selected"'; ?>><?php echo $val;
-        ?></option><?php
-    } ?>
-            </select>
-            <i class="help-tip icon-question-sign" href="#registration_method"></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('User 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> <?php echo __(
-                'failed login attempt(s) allowed before a lock-out is enforced'); ?>
-                <br/>
-                <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> <?php echo __('minutes locked out'); ?>
-            </td>
-        </tr>
-        <tr><td><?php echo __('User Session Timeout'); ?>:</td>
-            <td>
-              <input type="text" name="client_session_timeout" size=6 value="<?php echo $config['client_session_timeout']; ?>">
-              <i class="help-tip icon-question-sign" href="#client_session_timeout"></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Client Quick Access'); ?>:</td>
-            <td><input type="checkbox" name="client_verify_email" <?php
-                if ($config['client_verify_email'])
-                    echo 'checked="checked"'; ?>/> <?php echo __(
-                'Require email verification on "Check Ticket Status" page'); ?>
-            <i class="help-tip icon-question-sign" href="#client_verify_email"></i>
-            </td>
-        </tr>
-    </tbody>
-    <thead>
-        <tr>
-            <th colspan="2">
-                <h4><?php echo __('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">
-    <div style="padding:2px 5px">
-    <a href="#ajax.php/content/<?php echo $id; ?>/manage"
-    onclick="javascript:
-        $.dialog($(this).attr('href').substr(1), 201);
-    return false;" class="pull-left"><i class="icon-file-text icon-2x"
-        style="color:#bbb;"></i> </a>
-    <span style="display:inline-block;width:90%;width:calc(100% - 32px);padding-left:10px;line-height:1.2em">
-    <a href="#ajax.php/content/<?php echo $id; ?>/manage"
-    onclick="javascript:
-        $.dialog($(this).attr('href').substr(1), 201);
-    return false;"><?php
-    echo Format::htmlchars($title); ?></a><br/>
-        <span class="faded"><?php
-        echo Format::display($notes); ?>
-    <em>(<?php echo sprintf(__('Last Updated %s'), Format::datetime($upd));
-        ?>)</em></span>
-    </div></td></tr><?php
-}; ?>
-        <tr>
-            <th colspan="2">
-                <em><b><?php echo __(
-                'Authentication and Registration Templates'); ?></b></em>
-            </th>
-        </tr>
-        <?php $manage_content(__('Agents'), 'pwreset-staff'); ?>
-        <?php $manage_content(__('Clients'), 'pwreset-client'); ?>
-        <?php $manage_content(__('Guest Ticket Access'), 'access-link'); ?>
-        <tr>
-            <th colspan="2">
-                <em><b><?php echo __('Sign In Pages'); ?></b></em>
-            </th>
-        </tr>
-        <?php $manage_content(__('Agent Login Banner'), 'banner-staff'); ?>
-        <?php $manage_content(__('Client Sign-In Page'), 'banner-client'); ?>
-        <tr>
-            <th colspan="2">
-                <em><b><?php echo __('User 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><?php echo __('Agent Account Registration'); ?></b></em>
-            </th>
-        </tr>
-        <?php $manage_content(__('Agent Welcome Email'), 'registration-staff'); ?>
-</tbody>
-</table>
-<p style="text-align:center">
-    <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes'); ?>">
-    <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes'); ?>">
-</p>
-</form>
diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php
index 8edf21818a2268df33dfbfea2c977ede1afea26f..4fe241476415916aac3d6295ee943fd12ef6caf9 100644
--- a/include/staff/settings-tickets.inc.php
+++ b/include/staff/settings-tickets.inc.php
@@ -145,6 +145,23 @@ if(!($maxfileuploads=ini_get('max_file_uploads')))
                 <span class="error"><?php echo $errors['default_help_topic']; ?></span>
             </td>
         </tr>
+        <tr>
+            <td width="180"><?php echo __('Lock Semantics'); ?>:</td>
+            <td>
+                <select name="ticket_lock" <?php if ($cfg->getLockTime() == 0) echo 'disabled="disabled"'; ?>>
+<?php foreach (array(
+    Lock::MODE_DISABLED => __('Disabled'),
+    Lock::MODE_ON_VIEW => __('Lock on view'),
+    Lock::MODE_ON_ACTIVITY => __('Lock on activity'),
+) as $v => $desc) { ?>
+                <option value="<?php echo $v; ?>" <?php
+                    if ($config['ticket_lock'] == $v) echo 'selected="selected"';
+                    ?>><?php echo $desc; ?></option>
+<?php } ?>
+                </select>
+                <div class="error"><?php echo $errors['ticket_lock']; ?></div>
+            </td>
+        </tr>
         <tr>
             <td><?php echo __('Maximum <b>Open</b> Tickets');?>:</td>
             <td>
diff --git a/include/staff/templates/thread-entries.tmpl.php b/include/staff/templates/thread-entries.tmpl.php
index 7e5e1638824a5491a1ec11d1cdf8b51299b853c0..a3f3bdfc725a80b0c636933286c3b50b63d03e01 100644
--- a/include/staff/templates/thread-entries.tmpl.php
+++ b/include/staff/templates/thread-entries.tmpl.php
@@ -4,6 +4,13 @@ $events = $events->getIterator();
 $events->rewind();
 $event = $events->current();
 $htmlId = $options['html-id'] ?: ('thread-'.$this->getId());
+
+$thread_attachments = array();
+foreach (Attachment::objects()->filter(array(
+    'thread_entry__thread__id' => $this->getId(),
+))->select_related('thread_entry', 'file') as $att) {
+    $thread_attachments[$att->object_id][] = $att;
+}
 ?>
 <div id="<?php echo $htmlId; ?>">
     <div id="thread-items" data-thread-id="<?php echo $this->getId(); ?>">
@@ -58,16 +65,16 @@ $htmlId = $options['html-id'] ?: ('thread-'.$this->getId());
         // Set inline image urls.
         <?php
         $urls = array();
-        foreach (AttachmentFile::objects()->filter(array(
-            'attachments__thread_entry__thread__id' => $this->getId(),
-            'attachments__inline' => true,
-        )) as $file) {
-            $urls[strtolower($file->getKey())] = array(
-                'download_url' => $file->getDownloadUrl(),
-                'filename' => $file->name,
+        foreach ($thread_attachments as $eid=>$atts) {
+            foreach ($atts as $A) {
+                if (!$A->inline)
+                    continue;
+                $urls[strtolower($A->file->getKey())] = array(
+                    'download_url' => $A->file->getDownloadUrl(),
+                    'filename' => $A->getFilename(),
                 );
-
             }
+        }
         ?>
         $('#'+container).data('imageUrls', <?php echo JsonDataEncoder::encode($urls); ?>);
         // Trigger thread processing.
diff --git a/include/staff/templates/thread-entry.tmpl.php b/include/staff/templates/thread-entry.tmpl.php
index 3f3bfee7c97316a62a87ac00716eafdc7e513a32..4a86b68ee0f17bffd1c9647c8fca4bae081a2731 100644
--- a/include/staff/templates/thread-entry.tmpl.php
+++ b/include/staff/templates/thread-entry.tmpl.php
@@ -70,10 +70,11 @@ if ($user)
     // The strangeness here is because .has_attachments is an annotation from
     // Thread::getEntries(); however, this template may be used in other
     // places such as from thread entry editing
-    if (isset($entry->has_attachments) ? $entry->has_attachments
-            : $entry->attachments->filter(array('inline'=>0))->count()) { ?>
+    $atts = isset($thread_attachments) ? $thread_attachments[$entry->id] : $entry->attachments;
+    if (isset($atts) && $atts) {
+?>
     <div class="attachments"><?php
-        foreach ($entry->attachments as $A) {
+        foreach ($atts as $A) {
             if ($A->inline)
                 continue;
             $size = '';
@@ -87,12 +88,13 @@ if ($user)
             target="_blank"><?php echo Format::htmlchars($A->getFilename());
         ?></a><?php echo $size;?>
         </span>
-<?php   }  ?>
-    </div>
-<?php } ?>
+<?php   }
+    echo '</div>';
+    }
+?>
     </div>
 <?php
-    if ($urls = $entry->getAttachmentUrls()) { ?>
+    if (!isset($thread_attachments) && ($urls = $entry->getAttachmentUrls())) { ?>
         <script type="text/javascript">
             $('#thread-entry-<?php echo $entry->getId(); ?>')
                 .data('urls', <?php
diff --git a/include/staff/templates/ticket-preview.tmpl.php b/include/staff/templates/ticket-preview.tmpl.php
index 168847fc8ee4fdfda4a9b590d3b69cf6b78b3941..859bf0a8957b812634a774b1db0e7e2c4477f9b8 100644
--- a/include/staff/templates/ticket-preview.tmpl.php
+++ b/include/staff/templates/ticket-preview.tmpl.php
@@ -8,6 +8,7 @@ $staff=$ticket->getStaff();
 $lock=$ticket->getLock();
 $role=$thisstaff->getRole($ticket->getDeptId());
 $error=$msg=$warn=null;
+$thread = $ticket->getThread();
 
 if($lock && $lock->getStaffId()==$thisstaff->getId())
     $warn.='&nbsp;<span class="Icon lockedTicket">'
@@ -34,12 +35,12 @@ echo '<ul class="tabs" id="ticket-preview">';
 echo '
         <li class="active"><a id="preview_tab" href="#preview"
             ><i class="icon-list-alt"></i>&nbsp;'.__('Ticket Summary').'</a></li>';
-if ($ticket->getThread()->getNumCollaborators()) {
+if ($thread && $thread->getNumCollaborators()) {
 echo sprintf('
         <li><a id="collab_tab" href="#collab"
             ><i class="icon-fixed-width icon-group
             faded"></i>&nbsp;'.__('Collaborators (%d)').'</a></li>',
-            $ticket->getThread()->getNumCollaborators());
+            $thread->getNumCollaborators());
 }
 echo '</ul>';
 echo '<div id="ticket-preview_container">';
@@ -121,7 +122,7 @@ echo '</div>'; // ticket preview content.
     <table border="0" cellspacing="" cellpadding="1">
         <colgroup><col style="min-width: 250px;"></col></colgroup>
         <?php
-        if (($collabs=$ticket->getThread()->getCollaborators())) {?>
+        if ($thread && ($collabs=$thread->getCollaborators())) {?>
         <?php
             foreach($collabs as $collab) {
                 echo sprintf('<tr><td %s><i class="icon-%s"></i>
@@ -141,7 +142,7 @@ echo '</div>'; // ticket preview content.
     echo sprintf('<span><a class="collaborators"
                             href="#tickets/%d/collaborators">%s</a></span>',
                             $ticket->getId(),
-                            $ticket->getThread()->getNumCollaborators()
+                            $thread && $thread->getNumCollaborators()
                                 ? __('Manage Collaborators') : __('Add Collaborator')
                                 );
     ?>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 6ec03ae8992c1da3ac8d7bd6aa08a789c5902d93..156b2c323556a57f4de5771baae88a64eb123fc0 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -16,6 +16,8 @@ $user  = $ticket->getOwner(); //Ticket User (EndUser)
 $team  = $ticket->getTeam();  //Assigned team.
 $sla   = $ticket->getSLA();
 $lock  = $ticket->getLock();  //Ticket lock obj
+if (!$lock && $cfg->getTicketLockMode() == Lock::MODE_ON_VIEW)
+    $lock = $ticket->acquireLock($thisstaff->getId());
 $mylock = ($lock && $lock->getStaffId() == $thisstaff->getId()) ? $lock : null;
 $id    = $ticket->getId();    //Ticket ID.
 
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index a388e5cad52f243bf16189a66e3b0df7f45eb49f..5adab6cec0eb49ac93584102a36b4b8c646bd90b 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -196,21 +196,6 @@ $pageNav = new Pagenate($count, $page, PAGE_LIMIT);
 $pageNav->setURL('tickets.php', $args);
 $tickets = $pageNav->paginate($tickets);
 
-// Rewrite $tickets to use a nested query, which will include the LIMIT part
-// in order to speed the result
-//
-// ATM, advanced search with keywords doesn't support the subquery approach
-if ($use_subquery) {
-    $orig_tickets = clone $tickets;
-    $tickets2 = TicketModel::objects();
-    $tickets2->values = $tickets->values;
-    $tickets2->filter(array('ticket_id__in' => $tickets->values_flat('ticket_id')));
-
-    // Transfer the order_by from the original tickets
-    $tickets2->order_by($orig_tickets->getSortFields());
-    $tickets = $tickets2;
-}
-
 // Apply requested sorting
 $queue_sort_key = sprintf(':Q%s:%s:sort', ObjectModel::OBJECT_TYPE_TICKET, $queue_name);
 
@@ -299,6 +284,17 @@ case 'updated':
     break;
 }
 
+// Rewrite $tickets to use a nested query, which will include the LIMIT part
+// in order to speed the result
+$orig_tickets = clone $tickets;
+$tickets2 = TicketModel::objects();
+$tickets2->values = $tickets->values;
+$tickets2->filter(array('ticket_id__in' => $tickets->values_flat('ticket_id')));
+
+// Transfer the order_by from the original tickets
+$tickets2->order_by($orig_tickets->getSortFields());
+$tickets = $tickets2;
+
 // Save the query to the session for exporting
 $_SESSION[':Q:tickets'] = $tickets;
 
@@ -307,6 +303,7 @@ TicketForm::ensureDynamicDataView();
 // Select pertinent columns
 // ------------------------------------------------------------
 $tickets->values('lock__staff_id', 'staff_id', 'isoverdue', 'team_id', 'ticket_id', 'number', 'cdata__subject', 'user__default_email__address', 'source', 'cdata__priority__priority_color', 'cdata__priority__priority_desc', 'status_id', 'status__name', 'status__state', 'dept_id', 'dept__name', 'user__name', 'lastupdate', 'isanswered', 'staff__firstname', 'staff__lastname', 'team__name');
+
 // Add in annotations
 $tickets->annotate(array(
     'collab_count' => TicketThread::objects()
@@ -590,7 +587,7 @@ $(function() {
             +'?count='+count
             +'&_uid='+new Date().getTime();
             $.dialog(url, [201], function (xhr) {
-                $.pjax.reload('#pjax-container');
+                $.pjax({url: 'tickets.php', container: '#pjax-container'});
              });
         }
         return false;
diff --git a/js/redactor-plugins.js b/js/redactor-plugins.js
index 7ecef2bba40bb6859f9094ef66fff9cb1513f7a6..0a65831274553f2a10524a05df64d2237fadebca 100644
--- a/js/redactor-plugins.js
+++ b/js/redactor-plugins.js
@@ -354,7 +354,7 @@ RedactorPlugins.fullscreen = function()
                                     func: this.table.show,
                                     observe: {
                                         element: 'table',
-                                        in: {
+                                        'in': {
                                             attr: {
                                                 'class': 'redactor-dropdown-link-inactive',
                                                 'aria-disabled': true,
diff --git a/scp/css/scp.css b/scp/css/scp.css
index a933d2855f952b678ccf3fbbc3e9b6d3da7c3947..4556855d36102a2e1085a66dcfdafae46cf568fd 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1047,6 +1047,9 @@ img.avatar {
   border-top-color: rgba(0,0,0,0.2);
   border-radius: 0 0 6px 6px;
 }
+.thread-body .attachments:empty {
+    display: none;
+}
 .thread-body .attachments .filesize {
   margin-left: 0.5em;
   line-height: 1em;
diff --git a/scp/emails.php b/scp/emails.php
index 82da3ad7c1929dc49c60ec5835a5b057d30e7f53..9ff1c70a771ad9942977a415e8b2cae8d4794ffa 100644
--- a/scp/emails.php
+++ b/scp/emails.php
@@ -50,14 +50,8 @@ if($_POST){
             } else {
                 $count=count($_POST['ids']);
 
-                $sql='SELECT count(dept_id) FROM '.DEPT_TABLE.' dept '
-                    .' WHERE email_id IN ('.implode(',', db_input($_POST['ids'])).') '
-                    .' OR autoresp_email_id IN ('.implode(',', db_input($_POST['ids'])).')';
-
-                list($depts)=db_fetch_row(db_query($sql));
-                if($depts>0) {
-                    $errors['err'] = __('One or more of the selected emails is being used by a department. Remove association first!');
-                } elseif(!strcasecmp($_POST['a'], 'delete')) {
+                switch (strtolower($_POST['a'])) {
+                case 'delete':
                     $i=0;
                     foreach($_POST['ids'] as $k=>$v) {
                         if($v!=$cfg->getDefaultEmailId() && ($e=Email::lookup($v)) && $e->delete())
@@ -73,8 +67,9 @@ if($_POST){
                     elseif(!$errors['err'])
                         $errors['err'] = sprintf(__('Unable to delete %s'),
                             _N('selected email', 'selected emails', $count));
+                    break;
 
-                } else {
+                default:
                     $errors['err'] = __('Unknown action - get technical help.');
                 }
             }
diff --git a/scp/js/ticket.js b/scp/js/ticket.js
index 61ba8c7b84f9364b1863042dd32a4986dacbf598..b16cc8ea386dca8cf6931a9bd1e1b6b81aabeaf9 100644
--- a/scp/js/ticket.js
+++ b/scp/js/ticket.js
@@ -20,30 +20,29 @@
     if (!this.$element.data('lockObjectId'))
       return;
     this.objectId = this.$element.data('lockObjectId');
-    this.lockId = options.lockId || this.$element.data('lockId') || undefined;
     this.fails = 0;
     this.disabled = false;
     getConfig().then(function(c) {
       if (c.lock_time)
-        this.setup();
+        this.setup(options.lockId || this.$element.data('lockId') || undefined);
     }.bind(this));
   }
 
   Lock.prototype = {
     constructor: Lock,
+    registry: [],
 
-    setup: function() {
+    setup: function(lockId) {
       // When something inside changes or is clicked which requires a lock,
       // attempt to fetch one (lazily)
       $(':input', this.$element).on('keyup, change', this.acquire.bind(this));
       $(':submit', this.$element).click(this.ensureLocked.bind(this));
-      $(document).on('pjax:start', this.shutdown.bind(this));
 
       // If lock already held, assume full time of lock remains, but warn
       // user about pending expiration
-      if (this.lockId) {
+      if (lockId) {
         getConfig().then(function(c) {
-          this.lockTimeout(c.lock_time - 20);
+          this.update({id: lockId, time: c.lock_time - 10});
         }.bind(this));
       }
     },
@@ -126,12 +125,20 @@
         type: 'POST',
         url: 'ajax.php/lock/{0}/release'.replace('{0}', this.lockId),
         data: 'delete',
-        async: false,
         cache: false,
+        success: this.clearAll.bind(this),
         complete: this.destroy.bind(this)
       });
     },
 
+    clearAll: function() {
+      // Clear all other current locks with the same ID as this
+      $.each(Lock.prototype.registry, function(i, l) {
+        if (l.lockId && l.lockId == this.lockId)
+          l.shutdown();
+      }.bind(this));
+    },
+
     shutdown: function() {
       clearTimeout(this.warning);
       clearTimeout(this.retryTimer);
@@ -159,6 +166,7 @@
         // Set up release on away navigation
         $(document).off('.exclusive');
         $(document).on('pjax:click.exclusive', $.proxy(this.release, this));
+        Lock.prototype.registry.push(this);
       }
 
       this.lockId = lock.id;
diff --git a/scp/tickets.php b/scp/tickets.php
index e4c1fc10345bcd549ed8ca1e2fbbcbfa862f17aa..b8fc7ae449b7a25c9f72e46c2e0231f491872364 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -25,6 +25,7 @@ require_once(INCLUDE_DIR.'class.export.php');       // For paper sizes
 
 $page='';
 $ticket = $user = null; //clean start.
+$redirect = false;
 //LOCKDOWN...See if the id provided is actually valid and if the user has access.
 if($_REQUEST['id']) {
     if(!($ticket=Ticket::lookup($_REQUEST['id'])))
@@ -131,6 +132,7 @@ if($_POST && !$errors):
 
                 // Go back to the ticket listing page on reply
                 $ticket = null;
+                $redirect = 'tickets.php';
 
             } elseif(!$errors['err']) {
                 $errors['err']=__('Unable to post the reply. Correct the errors below and try again!');
@@ -173,6 +175,7 @@ if($_POST && !$errors):
                     Draft::deleteForNamespace('ticket.note.'.$ticket->getId(),
                         $thisstaff->getId());
 
+                 $redirect = 'tickets.php';
             } else {
 
                 if(!$errors['err'])
@@ -187,6 +190,7 @@ if($_POST && !$errors):
                 $errors['err']=__('Permission Denied. You are not allowed to edit tickets');
             elseif($ticket->update($_POST,$errors)) {
                 $msg=__('Ticket updated successfully');
+                $redirect = 'tickets.php?id='.$ticket->getId();
                 $_REQUEST['a'] = null; //Clear edit action - going back to view.
                 //Check to make sure the staff STILL has access post-update (e.g dept change).
                 if(!$ticket->checkStaffPerm($thisstaff))
@@ -335,6 +339,12 @@ if($_POST && !$errors):
         $thisstaff ->resetStats(); //We'll need to reflect any changes just made!
 endif;
 
+if ($redirect) {
+    if ($msg)
+        Messages::success($msg);
+    Http::redirect($redirect);
+}
+
 /*... Quick stats ...*/
 $stats= $thisstaff->getTicketsStats();