diff --git a/bootstrap.php b/bootstrap.php
index 6fa05aa508dc48443d1235f8fb3f1a5a09aa755b..6e245a142cb2310166abf2ba65f7a2a0dbe07780 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -80,7 +80,6 @@ class Bootstrap {
         define('TEAM_TABLE',$prefix.'team');
         define('TEAM_MEMBER_TABLE',$prefix.'team_member');
         define('DEPT_TABLE',$prefix.'department');
-        define('GROUP_TABLE', $prefix.'group');
         define('STAFF_DEPT_TABLE', $prefix.'staff_dept_access');
         define('ROLE_TABLE', $prefix.'role');
 
diff --git a/css/thread.css b/css/thread.css
index 41c327a089b234544e5a3ff47d726d0a730b177c..bb02ba597a963606ad29f56d8a742013d77638fd 100644
--- a/css/thread.css
+++ b/css/thread.css
@@ -396,7 +396,7 @@
 .thread-body blockquote,
 .thread-body pre {
 	font-size: 14px;
-	line-height: 1.4rem;
+	line-height: 1.5rem;
 }
 
 /* Adjust plain/text messages posted as <pre> in the thread body to show in
diff --git a/include/ajax.admin.php b/include/ajax.admin.php
index 39743c1b6a33f8888e9a42ba00a5fc287d2d212d..e5139d8da599486a06c77e60bffc7c29961fa49e 100644
--- a/include/ajax.admin.php
+++ b/include/ajax.admin.php
@@ -176,9 +176,13 @@ class AdminAjaxAPI extends AjaxController {
                     'name' => (string) $staff->getName(),
                 ), 'application/json'));
             }
-            foreach ($errors as $name=>$desc)
-                if ($F = $form->getField($name))
+            foreach ($errors as $name=>$desc) {
+                if ($F = $form->getField($name)) {
                     $F->addError($desc);
+                    unset($errors[$name]);
+                }
+            }
+            $errors['err'] = implode(", ", $errors);
         }
 
         $title = __("Add New Agent");
diff --git a/include/class.canned.php b/include/class.canned.php
index 9ea3a66cb82bf82b4526cda1c7b2108862b0e3f9..affa59d9fca329791522140d50f16890bcfbd18f 100644
--- a/include/class.canned.php
+++ b/include/class.canned.php
@@ -15,25 +15,6 @@
 **********************************************************************/
 include_once(INCLUDE_DIR.'class.file.php');
 
-class CannedModel {
-
-    const PERM_MANAGE = 'canned.manage';
-
-    static protected $perms = array(
-            self::PERM_MANAGE => array(
-                'title' =>
-                /* @trans */ 'Premade',
-                'desc'  =>
-                /* @trans */ 'Ability to add/update/disable/delete canned responses')
-    );
-
-    static function getPermissions() {
-        return self::$perms;
-    }
-}
-
-RolePermission::register( /* @trans */ 'Knowledgebase', CannedModel::getPermissions());
-
 class Canned
 extends VerySimpleModel {
     static $meta = array(
@@ -52,6 +33,20 @@ extends VerySimpleModel {
         ),
     );
 
+    const PERM_MANAGE = 'canned.manage';
+
+    static protected $perms = array(
+            self::PERM_MANAGE => array(
+                'title' =>
+                /* @trans */ 'Premade',
+                'desc'  =>
+                /* @trans */ 'Ability to add/update/disable/delete canned responses')
+    );
+
+    static function getPermissions() {
+        return self::$perms;
+    }
+
     function getId(){
         return $this->canned_id;
     }
@@ -297,4 +292,6 @@ extends VerySimpleModel {
         return true;
     }
 }
+RolePermission::register( /* @trans */ 'Knowledgebase', Canned::getPermissions());
+
 ?>
diff --git a/include/class.client.php b/include/class.client.php
index bbd6270c5f7ee781f58f2335ea29e20c895bb687..92b58b1ef761ec0006e2996a99897b38b6051413 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -253,13 +253,13 @@ class  EndUser extends BaseAuthenticatedUser {
         if (!($stats=$this->getTicketStats()))
             return 0;
 
-        return $stats['org-open']+$stats['org-closed'];
+        return $stats['org']['open']+$stats['org']['closed'];
     }
     function getNumOpenOrganizationTickets() {
-        return ($stats=$this->getTicketStats())?$stats['org-open']:0;
+        return ($stats=$this->getTicketStats())?$stats['org']['open']:0;
     }
     function getNumClosedOrganizationTickets() {
-        return ($stats=$this->getTicketStats())?$stats['org-closed']:0;
+        return ($stats=$this->getTicketStats())?$stats['org']['closed']:0;
     }
 
     function getAccount() {
@@ -278,17 +278,31 @@ class  EndUser extends BaseAuthenticatedUser {
     private function getStats() {
         $basic = Ticket::objects()
             ->annotate(array('count' => SqlAggregate::COUNT('ticket_id')))
-            ->values('status__state', 'topic_id')
-            ->filter(Q::any(array(
-                'user_id' => $this->getId(),
-                'thread__collaborators__user_id' => $this->getId(),
-            )));
+            ->values('status__state', 'topic_id', 'user__org_id');
+
+        $q = new Q(); $q->union();
+        if ($this->getOrgId())
+            $q->add(array('user__org_id' => $this->getOrgId()));
+
+        // Share tickets among the organization for owners only
+        $owners = clone $basic;
+        $q->add(array('user_id' => $this->getId()));
+        $owners->filter($q);
+
+        $collabs = clone $basic;
+        $collabs->filter(array('thread__collaborators__user_id' => $this->getId()));
+
+        // TODO: Implement UNION ALL support in the ORM
 
         $stats = array('open' => 0, 'closed' => 0, 'topics' => array());
-        foreach ($basic as $row) {
-            $stats[$row['status__state']] += $row['count'];
-            if ($row['topic_id'])
-                $stats['topics'][$row['topic_id']] += $row['count'];
+        foreach (array($owners, $collabs) as $rs) {
+            foreach ($rs as $row) {
+                $stats[$row['status__state']] += $row['count'];
+                if ($row['topic_id'])
+                    $stats['topics'][$row['topic_id']] += $row['count'];
+                if ($row['user__org_id'])
+                    $stats['org'][$row['status__state']] += $row['count'];
+            }
         }
         return $stats;
     }
diff --git a/include/class.dept.php b/include/class.dept.php
index d27e5923c62914880e794168e348d31aac38cafc..9723c8925721c7e83b338271683c6dab10e49d91 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -532,7 +532,7 @@ implements TemplateVariable {
         global $cfg;
 
         $id = $this->id;
-        if ($id != $vars['id'])
+        if ($id && $id != $vars['id'])
             $errors['err']=__('Missing or invalid Dept ID (internal error).');
 
         if (!$vars['name']) {
@@ -576,8 +576,15 @@ implements TemplateVariable {
         $this->flags = isset($vars['assign_members_only']) ? self::FLAG_ASSIGN_MEMBERS_ONLY : 0;
         $this->path = $this->getFullPath();
 
-        if ($this->save())
-            return $this->extended->saveAll();
+        $wasnew = $this->__new__;
+        if ($this->save() && $this->extended->saveAll()) {
+            if ($wasnew) {
+                // The ID wasn't available until after the commit
+                $this->path = $this->getFullPath();
+                $this->save();
+            }
+            return true;
+        }
 
         if (isset($this->id))
             $errors['err']=sprintf(__('Unable to update %s.'), __('this department'))
diff --git a/include/class.faq.php b/include/class.faq.php
index a551beefaba06f1a653745cf63f1f4de4899c474..361518d282bdeefa6d59b9cd6ad00c136cb4acc7 100644
--- a/include/class.faq.php
+++ b/include/class.faq.php
@@ -155,11 +155,6 @@ class FAQ extends VerySimpleModel {
         return $this->save();
     }
 
-    function logView() {
-        $this->views++;
-        $this->save();
-    }
-
     function printPdf() {
         global $thisstaff;
         require_once(INCLUDE_DIR.'class.pdf.php');
@@ -380,7 +375,7 @@ class FAQ extends VerySimpleModel {
     static function getFeatured() {
         return self::objects()
             ->filter(array('ispublished__in'=>array(1,2), 'category__ispublic'=>1))
-            ->order_by('-ispublished','-views');
+            ->order_by('-ispublished');
     }
 
     static function findIdByQuestion($question) {
diff --git a/include/class.http.php b/include/class.http.php
index c358355a7cd438c621e25d9eabc548709c3ad128..5e14ee932bb2acbd013b348083a4176a13fc42cb 100644
--- a/include/class.http.php
+++ b/include/class.http.php
@@ -61,18 +61,20 @@ class Http {
         exit;
     }
 
-    function cacheable($etag, $modified, $ttl=3600) {
+    function cacheable($etag, $modified=false, $ttl=3600) {
         // Thanks, http://stackoverflow.com/a/1583753/1025836
         // Timezone doesn't matter here — but the time needs to be
         // consistent round trip to the browser and back.
-        $last_modified = strtotime($modified." GMT");
-        header("Last-Modified: ".date('D, d M Y H:i:s', $last_modified)." GMT", false);
+        if ($modified) {
+            $last_modified = strtotime($modified." GMT");
+            header("Last-Modified: ".date('D, d M Y H:i:s', $last_modified)." GMT", false);
+        }
         header('ETag: "'.$etag.'"');
         header("Cache-Control: private, max-age=$ttl");
         header('Expires: ' . gmdate('D, d M Y H:i:s', Misc::gmtime() + $ttl)." GMT");
         header('Pragma: private');
-        if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified ||
-            @trim($_SERVER['HTTP_IF_NONE_MATCH'], '" ') == $etag) {
+        if (($modified && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified)
+            || @trim($_SERVER['HTTP_IF_NONE_MATCH'], '" ') == $etag) {
                 header("HTTP/1.1 304 Not Modified");
                 exit();
         }
diff --git a/include/class.nav.php b/include/class.nav.php
index 8acff9052bc7d9da1436c2c6e067f50ed802d812..e2dde777539cf2aa5157a68aa6fe4f559f01ae89 100644
--- a/include/class.nav.php
+++ b/include/class.nav.php
@@ -177,7 +177,7 @@ class StaffNav {
                     if($staff) {
                         if ($staff->hasPerm(FAQ::PERM_MANAGE))
                             $subnav[]=array('desc'=>__('Categories'),'href'=>'categories.php','iconclass'=>'faq-categories');
-                        if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(CannedModel::PERM_MANAGE))
+                        if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(Canned::PERM_MANAGE, false))
                             $subnav[]=array('desc'=>__('Canned Responses'),'href'=>'canned.php','iconclass'=>'canned');
                     }
                    break;
diff --git a/include/class.staff.php b/include/class.staff.php
index cd8b4e264503c74f17311701cec3f320d79da58c..d3cdff2a13d8d92a1baeb88cacbcced6d1bfff5e 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -28,7 +28,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable {
     static $meta = array(
         'table' => STAFF_TABLE,
         'pk' => array('staff_id'),
-        'select_related' => array('dept'),
         'joins' => array(
             'dept' => array(
                 'constraint' => array('dept_id' => 'Dept.id'),
@@ -1240,12 +1239,6 @@ class ChangeDepartmentForm
 extends AbstractForm {
     function buildFields() {
         return array(
-            'header' => new FreeTextField(array(
-                'configuration' => array(
-                    'content' => __('Change the primary department and primary role of the selected agents'),
-                    'classes' => ' ',
-                )
-            )),
             'dept_id' => new ChoiceField(array(
                 'default' => 0,
                 'required' => true,
@@ -1278,6 +1271,10 @@ extends AbstractForm {
         );
     }
 
+    function getInstructions() {
+        return __('Change the primary department and primary role of the selected agents');
+    }
+
     function getClean() {
         $clean = parent::getClean();
         $clean['eavesdrop'] = $clean['eavesdrop'] ? 1 : 0;
@@ -1374,7 +1371,9 @@ extends AbstractForm {
 
     function getClean() {
         $clean = parent::getClean();
-        list($clean['username'],) = preg_split('/[^\w-]/', $clean['email'], 2);
+        list($clean['username'],) = preg_split('/[^\w.-]/', $clean['email'], 2);
+        if (Staff::lookup($clean['username']))
+            $clean['username'] = mb_strtolower($clean['firstname']);
         $clean['role_id'] = 1;
         return $clean;
     }
diff --git a/include/class.thread.php b/include/class.thread.php
index 4d45e5f95e7ab86a3c0241f9de68142d5f76b4c1..20cb0f8f246a26e351813595d7c778a640422c70 100644
--- a/include/class.thread.php
+++ b/include/class.thread.php
@@ -2361,7 +2361,12 @@ implements TemplateVariable {
         $vars['threadId'] = $this->getId();
         $vars['staffId'] = 0;
 
-        return MessageThreadEntry::create($vars, $errors);
+        if (!($message = MessageThreadEntry::create($vars, $errors)))
+            return $message;
+
+        $this->lastmessage = SqlFunction::NOW();
+        $this->save();
+        return $message;
     }
 
     function addResponse($vars, &$errors) {
@@ -2369,7 +2374,12 @@ implements TemplateVariable {
         $vars['threadId'] = $this->getId();
         $vars['userId'] = 0;
 
-        return ResponseThreadEntry::create($vars, $errors);
+        if (!($resp = ResponseThreadEntry::create($vars, $errors)))
+            return $resp;
+
+        $this->lastresponse = SqlFunction::NOW();
+        $this->save();
+        return $resp;
     }
 
     function getVar($name) {
diff --git a/include/class.ticket.php b/include/class.ticket.php
index b19fc6e9f4f17f5bbebc1d94b3fb5f340d17086c..11bcda3db6cecb7a821110c15b048f284bf6eb6b 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -143,7 +143,7 @@ class TicketModel extends VerySimpleModel {
 
     function getEffectiveDate() {
          return Format::datetime(max(
-             strtotime($this->lastmessage),
+             strtotime($this->thread->lastmessage),
              strtotime($this->closed),
              strtotime($this->reopened),
              strtotime($this->created)
@@ -683,7 +683,7 @@ implements RestrictedAccess, Threadable {
     }
 
     function getLastMessageDate() {
-        return $this->lastmessage;
+        return $this->thread->lastmessage;
     }
 
     function getLastMsgDate() {
@@ -691,7 +691,7 @@ implements RestrictedAccess, Threadable {
     }
 
     function getLastResponseDate() {
-        return $this->lastresponse;
+        return $this->thread->lastresponse;
     }
 
     function getLastRespDate() {
@@ -1283,7 +1283,6 @@ implements RestrictedAccess, Threadable {
 
     function onResponse($response, $options=array()) {
         $this->isanswered = 1;
-        $this->lastresponse = SqlFunction::NOW();
         $this->save();
 
         $vars = array_merge($options,
@@ -2090,9 +2089,6 @@ implements RestrictedAccess, Threadable {
             }
         }
 
-        // Set the last message time here
-        $this->lastmessage = SqlFunction::NOW();
-
         if (!$alerts)
             return $message; //Our work is done...
 
diff --git a/include/class.variable.php b/include/class.variable.php
index 0efd70d71b422a5d262931b50085d8c264eca2d5..8c10fc61c76d245e9c7c0f2ad4ee1a6ca5ed184e 100644
--- a/include/class.variable.php
+++ b/include/class.variable.php
@@ -97,7 +97,10 @@ class VariableReplacer {
         }
 
         // Recurse with $rv
-        return $this->getVar($rv, $remainder);
+        if (is_object($rv) || $remainder)
+            return $this->getVar($rv, $remainder);
+
+        return $rv;
     }
 
     function replaceVars($input) {
diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php
index 36c25d5cfe2912d52dfb8984dbd15dca7c80aaf1..3ab1c273c8429a69fbc82aaa6d5497e3330a49b2 100644
--- a/include/client/faq.inc.php
+++ b/include/client/faq.inc.php
@@ -63,5 +63,3 @@ if ($faq->getHelpTopics()->count()) { ?>
 </div>
 
 </div>
-
-<?php $faq->logView(); ?>
diff --git a/include/i18n/en_US/team.yaml b/include/i18n/en_US/team.yaml
index 16ae1244976ab7ed90a88e795a7a3395e986022b..136de6505dc57c689d06142771e897069423f129 100644
--- a/include/i18n/en_US/team.yaml
+++ b/include/i18n/en_US/team.yaml
@@ -2,15 +2,15 @@
 # Initial teams defined for the system.
 #
 # Fields:
-# isenabled - (bool:1|0) true or false if the team should be initially
+# flags - (int)
+#   - isenabled - (0x01) true or false if the team should be initially
 #       enabled
-# noalerts - (bool:1|0)
+#   - noalerts - (0x02)
 # name - Descriptive name for the team
 # notes - Administrative notes (viewable internal only)
 #
 ---
-- isenabled: 1
-  noalerts: 0
+- flags: 0x01
   name: Level I Support
   notes: |
     Tier 1 support, responsible for the initial iteraction with customers
diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php
index c5f474255a47f260155a158278ddc0e2c5e4f2c5..ba30fdb29148172cc805f00ec668edad7199ac73 100644
--- a/include/staff/department.inc.php
+++ b/include/staff/department.inc.php
@@ -10,6 +10,8 @@ if($dept && $_REQUEST['a']!='add') {
     $info['id'] = $dept->getId();
     $qs += array('id' => $dept->getId());
 } else {
+    if (!$dept)
+        $dept = Dept::create();
     $title=__('Add New Department');
     $action='create';
     $submit_text=__('Create Dept');
@@ -293,6 +295,14 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
 <div id="access" class="hidden tab_content">
   <table class="two-column table" width="100%">
     <tbody>
+        <tr class="header">
+            <td colspan="2">
+                <?php echo __('Department Members'); ?>
+                <div><small>
+                <?php echo __('Agents who are primary members of this department'); ?>
+                </small></div>
+            </td>
+        </tr>
 <?php
 $agents = Staff::getStaffMembers();
 foreach ($dept->getMembers() as $member) {
@@ -354,6 +364,7 @@ foreach ($dept->getMembers() as $member) {
 
 <script type="text/javascript">
 var addAccess = function(staffid, name, role, alerts, primary, error) {
+  if (!staffid) return;
   var copy = $('#member_template').clone();
 
   copy.find('td:first').append(document.createTextNode(name));
diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php
index 3478dcefd3d97c5a69dd62a77a19e05c08c3e712..fdb94df4c8f485e510a29531e8d8bb2c53536468 100644
--- a/include/staff/staff.inc.php
+++ b/include/staff/staff.inc.php
@@ -411,6 +411,7 @@ foreach ($staff->teams as $TM) {
 
 <script type="text/javascript">
 var addAccess = function(daid, name, role, alerts, error) {
+  if (!daid) return;
   var copy = $('#extended_access_template').clone();
 
   copy.find('[data-name=dept_access\\[\\]]')
@@ -448,6 +449,7 @@ $(document).on('click', 'a.drop-access', function() {
 });
 
 var joinTeam = function(teamid, name, alerts, error) {
+  if (!teamid) return;
   var copy = $('#team_member_template').clone();
 
   copy.find('[data-name=teams\\[\\]]')
diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php
index 4df36165646be7b0e5bb059544fd31bf2b88fbf1..a9b6a87194d154812862ba952f31d24119872eba 100644
--- a/include/staff/staffmembers.inc.php
+++ b/include/staff/staffmembers.inc.php
@@ -82,7 +82,7 @@ $agents->limit($pageNav->getLimit())->offset($pageNav->getStart());
 ?>
 <h2><?php echo __('Agents');?></h2>
 
-<div class="pull-left" style="width:700px;">
+<div class="pull-left">
     <form action="staff.php" method="GET" name="filter">
      <input type="hidden" name="a" value="filter" >
         <select name="did" id="did">
diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php
index c055f8743711d05bee1bbf6e842d199ee2ee9659..92ad9f13dfb017b363d7ced525c53248ee80422e 100644
--- a/include/staff/team.inc.php
+++ b/include/staff/team.inc.php
@@ -80,16 +80,9 @@ $info = $team->getInfo();
             </td>
             <td>
                 <span>
-                <select name="lead_id">
+                <select id="team-lead-select" name="lead_id" data-quick-add="staff">
                     <option value="0">&mdash; <?php echo __('None');?> &mdash;</option>
-                    <?php
-                    if ($members) {
-                        foreach($members as $k=>$staff){
-                            $selected=($team->lead_id && $staff->getId()==$team->lead_id)?'selected="selected"':'';
-                            echo sprintf('<option value="%d" %s>%s</option>',$staff->getId(),$selected,$staff->getName());
-                        }
-                    }
-                    ?>
+                  <option value="0" data-quick-add>&mdash; <?php echo __('Add New');?> &mdash;</option>
                 </select>
                 &nbsp;<span class="error"><?php echo $errors['lead_id']; ?></span>
                 <i class="help-tip icon-question-sign" href="#lead"></i>
@@ -184,6 +177,7 @@ foreach ($members as $m)
 
 <script type="text/javascript">
 var addMember = function(staffid, name, alerts, error) {
+  if (!staffid) return;
   var copy = $('#member_template').clone();
 
   copy.find('[data-name=members\\[\\]]')
@@ -200,19 +194,28 @@ var addMember = function(staffid, name, alerts, error) {
 };
 
 $('#add_member').find('button').on('click', function() {
-  var selected = $('#add_access').find(':selected');
+  var selected = $('#add_access').find(':selected'),
+      id = selected.val();
   addMember(selected.val(), selected.text(), true);
+  if ($('#team-lead-select option[value='+id+']').length === 0) {
+    $('#team-lead-select').find('option[data-quick-add]')
+    .before(
+      $('<option>').val(selected.val()).text(selected.text())
+    );
+  }
   selected.remove();
   return false;
 });
 
 $(document).on('click', 'a.drop-membership', function() {
-  var tr = $(this).closest('tr');
+  var tr = $(this).closest('tr'),
+      id = tr.find('input[name^=members][type=hidden]').val();
   $('#add_access').append(
     $('<option>')
-    .attr('value', tr.find('input[name^=members][type=hidden]').val())
+    .attr('value', id)
     .text(tr.find('td:first').text())
   );
+  $('#team-lead-select option[value='+id+']').remove();
   tr.fadeOut(function() { $(this).remove(); });
   return false;
 });
diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php
index fe92935d10799e14796174c8cde866561857d635..7d9140b455d68e8d5541d18f08f7506da856851c 100644
--- a/include/staff/ticket-edit.inc.php
+++ b/include/staff/ticket-edit.inc.php
@@ -40,7 +40,7 @@ if ($_POST)
             <span id="client-name"><?php echo Format::htmlchars($user->getName()); ?></span>
             &lt;<span id="client-email"><?php echo $user->getEmail(); ?></span>&gt;
             </a>
-            <a class="action-button" style="overflow:inherit" href="#"
+            <a class="inline action-button" style="overflow:inherit" href="#"
                 onclick="javascript:
                     $.userLookup('ajax.php/tickets/<?php echo $ticket->getId(); ?>/change-user',
                             function(user) {
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 8cdc0b454229427f4106555138e3aca8cd282136..695ac4d745a312fbe8a23042c8ef4e254d549f20 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -59,7 +59,7 @@ if($ticket->isOverdue())
        <div class="content">
         <div class="pull-right flush-right">
             <?php
-            if ($role->hasPerm(Email::PERM_BANLIST)
+            if ($thisstaff->hasPerm(Email::PERM_BANLIST)
                     || $role->hasPerm(TicketModel::PERM_EDIT)
                     || ($dept && $dept->isManager($thisstaff))) { ?>
             <span class="action-button pull-right" data-dropdown="#action-dropdown-more">
@@ -145,7 +145,7 @@ if($ticket->isOverdue())
                     return false"
                     ><i class="icon-paste"></i> <?php echo __('Manage Forms'); ?></a></li>
 
-<?php           if ($role->hasPerm(Email::PERM_BANLIST)) {
+<?php           if ($thisstaff->hasPerm(Email::PERM_BANLIST)) {
                      if(!$emailBanned) {?>
                         <li><a class="confirm-action" id="ticket-banemail"
                             href="#banemail"><i class="icon-ban-circle"></i> <?php echo sprintf(
@@ -238,9 +238,19 @@ if($ticket->isOverdue())
 <?php   } ?>
                                 </ul>
                             </div>
+<?php                   } # end if ($user) ?>
+                    </td>
+                </tr>
+                <tr>
+                    <th><?php echo __('Email'); ?>:</th>
+                    <td>
+                        <span id="user-<?php echo $ticket->getOwnerId(); ?>-email"><?php echo $ticket->getEmail(); ?></span>
+                    </td>
+                </tr>
 <?php   if ($user->getOrgId()) { ?>
-                &nbsp; <span style="display:inline-block">
-                    <i class="icon-building"></i>
+                <tr>
+                    <th><?php echo __('Organization'); ?>:</th>
+                    <td><i class="icon-building"></i>
                     <?php echo Format::htmlchars($user->getOrganization()->getName()); ?>
                         <a href="tickets.php?<?php echo Http::build_query(array(
                             'status'=>'open', 'a'=>'search', 'orgid'=> $user->getOrgId()
@@ -248,7 +258,6 @@ if($ticket->isOverdue())
                         data-dropdown="#action-dropdown-org-stats">
                         (<b><?php echo $user->getNumOrganizationTickets(); ?></b>)
                         </a>
-                    </span>
                             <div id="action-dropdown-org-stats" class="action-dropdown anchor-right">
                                 <ul>
 <?php   if ($open = $user->getNumOpenOrganizationTickets()) { ?>
@@ -275,23 +284,9 @@ if($ticket->isOverdue())
 <?php   } ?>
                                 </ul>
                             </div>
-<?php   } # end if (user->org)
-                        } # end if ($user)
-                    ?>
-                    </td>
-                </tr>
-                <tr>
-                    <th><?php echo __('Email'); ?>:</th>
-                    <td>
-                        <span id="user-<?php echo $ticket->getOwnerId(); ?>-email"><?php echo $ticket->getEmail(); ?></span>
-                    </td>
-                </tr>
-                <tr>
-                    <th><?php echo __('Phone'); ?>:</th>
-                    <td>
-                        <span id="user-<?php echo $ticket->getOwnerId(); ?>-phone"><?php echo $ticket->getPhoneNumber(); ?></span>
-                    </td>
-                </tr>
+                        </td>
+                    </tr>
+<?php   } # end if (user->org) ?>
                 <tr>
                     <th><?php echo __('Source'); ?>:</th>
                     <td><?php
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index e956749626337ee84f8a6b420fb1c77712b245a4..b7b1983d109334f84a3ad9b93dfda885a3ce1b4f 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -237,10 +237,10 @@ case 'closed':
 
 case 'answered':
     $date_header = __('Last Response');
-    $date_col = 'lastresponse';
+    $date_col = 'thread__lastresponse';
     $date_fallback = '<em class="faded">'.__('unanswered').'</em>';
-    $tickets->order_by('-lastresponse');
-    $tickets->values('lastresponse');
+    $tickets->order_by('-thread__lastresponse');
+    $tickets->values('thread__lastresponse');
     break;
 
 case 'hot':
diff --git a/include/upgrader/aborted.inc.php b/include/upgrader/aborted.inc.php
index 5e4b1e9f74e8ef35fa66a0412214aec07537e6f7..846d5cd7238567a9243b92e1cc65ff711e4e9a7b 100644
--- a/include/upgrader/aborted.inc.php
+++ b/include/upgrader/aborted.inc.php
@@ -27,7 +27,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D
     </div>
     <p><strong><?php echo __('Need Help?');?></strong> <?php echo sprintf(__('We provide %1$s professional upgrade services %2$s and commercial support.'), '<a target="_blank" href="http://osticket.com/support/professional_services.php"><u>','</u></a>'); echo sprintf(__('%1$s Contact us %2$s today for <u>expedited</u> help.'), '<a target="_blank" href="http://osticket.com/support/">','</a>');?></p>
   </div>
-  <div id="sidebar">
+  <div class="sidebar">
     <h3><?php echo __('What to do?');?></h3>
     <p><?php echo sprintf(__('Restore your previous version from backup and try again or %1$s seek help %2$s.'), '<a target="_blank" href="http://osticket.com/support/">','</a>');?></p>
   </div>
diff --git a/include/upgrader/done.inc.php b/include/upgrader/done.inc.php
index 097cbe5bd7b715d95b0acd22102503857a16a1e8..58edda1cb125c666acc72c9627170b0e9da9c292 100644
--- a/include/upgrader/done.inc.php
+++ b/include/upgrader/done.inc.php
@@ -21,7 +21,7 @@ $_SESSION['ost_upgrader']=null;
         <br>
         <p><b><?php echo __('PS');?></b>: <?php echo __("Don't just make customers happy, make happy customers!");?></p>
     </div>
-    <div id="sidebar">
+    <div class="sidebar">
             <h3><?php echo __("What's Next?");?></h3>
             <p><b><?php echo __('Post-upgrade');?></b>: <?php
             echo sprintf(__('You can now go to %s to enable the system and explore the new features. For complete and up-to-date release notes see the %s'),
diff --git a/include/upgrader/rename.inc.php b/include/upgrader/rename.inc.php
index 408c2f75a06a066f39c3f1a8c7ed93b525557ed9..c553f173fe5e788a5f4250897a7321b1e98b873e 100644
--- a/include/upgrader/rename.inc.php
+++ b/include/upgrader/rename.inc.php
@@ -24,7 +24,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D
                 </form>
             </div>
     </div>
-    <div id="sidebar">
+    <div class="sidebar">
             <h3><?php echo __('Need Help?');?></h3>
             <p>
             <?php echo __('If you are looking for a greater level of support, we provide <u>professional upgrade</u> and commercial support with guaranteed response times and access to the core development team. We can also help customize osTicket or even add new features to the system to meet your unique needs. <a target="_blank" href="http://osticket.com/support">Learn More!</a>'); ?>
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 20bbf7d78e595692f660eb24112f04b267799f87..f6aa807b2faf040f3616a572ef0292b9182d2662 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-0d6099a650cc7884eb59a040feab2ce8
+98ad7d550c26ac44340350912296e673
diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql b/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql
new file mode 100644
index 0000000000000000000000000000000000000000..362a9400529e15c70c6488810c9b8d28bf711566
--- /dev/null
+++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql
@@ -0,0 +1,39 @@
+/**
+ * @signature 4e9f2e2441e82ba393df94647a1ec9ea
+ * @version v1.10.0
+ * @title Access Control 2.0
+ *
+ */
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%group_dept_access`;
+
+-- Drop `updated` if it exists (it stayed in the install script after it was
+-- removed from the update path
+SET @s = (SELECT IF(
+    (SELECT COUNT(*)
+        FROM INFORMATION_SCHEMA.COLUMNS
+        WHERE table_name = '%TABLE_PREFIX%team_member'
+        AND table_schema = DATABASE()
+        AND column_name = 'updated'
+    ) > 0,
+    "SELECT 1",
+    "ALTER TABLE `%TABLE_PREFIX%team_member` DROP `updated`"
+));
+PREPARE stmt FROM @s;
+EXECUTE stmt;
+
+-- Drop `views` and `score` from 1ee831c8 as it cannot handle translations
+SET @s = (SELECT IF(
+    (SELECT COUNT(*)
+        FROM INFORMATION_SCHEMA.COLUMNS
+        WHERE table_name = '%TABLE_PREFIX%faq'
+        AND table_schema = DATABASE()
+        AND column_name = 'views'
+    ) > 0,
+    "SELECT 1",
+    "ALTER TABLE `%TABLE_PREFIX%faq` DROP `views`, DROP `score`;"
+));
+PREPARE stmt FROM @s;
+EXECUTE stmt;
+
+ALTER TABLE `%TABLE_PREFIX%ticket` DROP `lastmessage`, DROP `lastresponse`;
diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..ab6a3f7576c1cd4b8a633c503e4051334b04057e
--- /dev/null
+++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql
@@ -0,0 +1,49 @@
+/**
+ * @signature 98ad7d550c26ac44340350912296e673
+ * @version v1.10.0
+ * @title Access Control 2.0
+ *
+ */
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%staff_dept_access`;
+CREATE TABLE `%TABLE_PREFIX%staff_dept_access` (
+  `staff_id` int(10) unsigned NOT NULL DEFAULT 0,
+  `dept_id` int(10) unsigned NOT NULL DEFAULT 0,
+  `role_id` int(10) unsigned NOT NULL DEFAULT 0,
+  `flags` int(10) unsigned NOT NULL DEFAULT 1,
+  PRIMARY KEY `staff_dept` (`staff_id`,`dept_id`),
+  KEY `dept_id` (`dept_id`)
+) DEFAULT CHARSET=utf8;
+
+INSERT INTO `%TABLE_PREFIX%staff_dept_access`
+  (`staff_id`, `dept_id`, `role_id`)
+  SELECT A1.`staff_id`, A2.`dept_id`, A2.`role_id`
+  FROM `%TABLE_PREFIX%staff` A1
+  JOIN `%TABLE_PREFIX%group_dept_access` A2 ON (A1.`group_id` = A2.`group_id`);
+
+ALTER TABLE `%TABLE_PREFIX%staff`
+  DROP `group_id`,
+  ADD `permissions` text AFTER `extra`;
+
+ALTER TABLE `%TABLE_PREFIX%team_member`
+  ADD `flags` int(10) unsigned NOT NULL DEFAULT 1 AFTER `staff_id`;
+
+ALTER TABLE `%TABLE_PREFIX%thread_collaborator`
+  ADD KEY `user_id` (`user_id`);
+
+ALTER TABLE `%TABLE_PREFIX%task`
+  ADD `closed` datetime DEFAULT NULL AFTER `duedate`;
+
+ALTER TABLE `%TABLE_PREFIX%thread`
+  ADD `lastresponse` datetime DEFAULT NULL AFTER `extra`,
+  ADD `lastmessage` datetime DEFAULT NULL AFTER `lastresponse`;
+
+UPDATE `%TABLE_PREFIX%thread` A1
+  JOIN `%TABLE_PREFIX%ticket` A2 ON (A2.`ticket_id` = A1.`object_id` AND A1.`object_type` = 'T')
+  SET A1.`lastresponse` = A2.`lastresponse`,
+      A1.`lastmessage` = A2.`lastmessage`;
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '98ad7d550c26ac44340350912296e673'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php b/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..3f336a9e17f780ef3607aba8be5044e6294cfe22
--- /dev/null
+++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php
@@ -0,0 +1,30 @@
+<?php
+
+class StaffPermissions extends MigrationTask {
+    var $description = "Add staff permissions";
+
+    function run($time) {
+        foreach (Staff::objects() as $staff) {
+            $role = $staff->getRole()->getPermission();
+            $perms = array(
+                User::PERM_CREATE,
+                User::PERM_EDIT,
+                User::PERM_DELETE,
+                User::PERM_MANAGE,
+                User::PERM_DIRECTORY,
+                Organization::PERM_CREATE,
+                Organization::PERM_EDIT,
+                Organization::PERM_DELETE,
+            );
+            if ($role->has(FAQ::PERM_MANAGE))
+                $perms[] = FAQ::PERM_MANAGE;
+            if ($role->has(Email::PERM_BANLIST))
+                $perms[] = Email::PERM_BANLIST;
+
+            $errors = array();
+            $staff->updatePerms($perms, $errors);
+            $staff->save();
+        }
+    }
+}
+return 'StaffPermissions';
diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php
index faf8c99670670a6e96c8fc95302759e791d26428..f633c36c6defcbfb7cbce5a5c2d1e1207004e5bd 100644
--- a/include/upgrader/streams/core/15b30765-dd0022fb.task.php
+++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php
@@ -207,7 +207,7 @@ class AttachmentMigrater extends MigrationTask {
             $this->enqueue($info);
         }
 
-        return $this->queueAttachments($limit);
+        return $this->getQueueLength();
     }
 
     function skip($attachId, $error) {
diff --git a/include/upgrader/streams/core/1ee831c8-36f6b328.task.php b/include/upgrader/streams/core/1ee831c8-36f6b328.task.php
index d507553358cb17a25ecef590cf3c127f89dbedc7..ddf1098207c1aacd20b0d81c40d31221118f6164 100644
--- a/include/upgrader/streams/core/1ee831c8-36f6b328.task.php
+++ b/include/upgrader/streams/core/1ee831c8-36f6b328.task.php
@@ -1,19 +1,43 @@
 <?php
+define('GROUP_TABLE', TABLE_PREFIX.'group');
+class Group extends VerySimpleModel {
+    static $meta = array(
+        'table' => GROUP_TABLE,
+        'pk' => array('id'),
+    );
+    const FLAG_ENABLED = 0x0001;
+
+    function getName() {
+        return $this->name;
+    }
+}
+
+Staff::getMeta()->addJoin('group', array(
+    'constraint' => array('group_id' => 'Group.id'),
+));
+
 class GroupRoles extends MigrationTask {
     var $description = "Migrate permissions from Group to Role";
 
     static $pmap = array(
-            'can_create_tickets'    => 'ticket.create',
-            'can_edit_tickets'      => 'ticket.edit',
-            'can_post_ticket_reply' => 'ticket.reply',
-            'can_delete_tickets'    => 'ticket.delete',
-            'can_close_tickets'     => 'ticket.close',
-            'can_assign_tickets'    => 'ticket.assign',
-            'can_transfer_tickets'  => 'ticket.transfer',
-            'can_ban_emails'        => 'emails.banlist',
-            'can_manage_premade'    => 'canned.manage',
-            'can_manage_faq'        => 'faq.manage',
-            'can_view_staff_stats'  => 'stats.agents',
+            'ticket.create' => 'can_create_tickets',
+            'ticket.edit' => 'can_edit_tickets',
+            'ticket.reply' => 'can_post_ticket_reply',
+            'ticket.delete' => 'can_delete_tickets',
+            'ticket.close' => 'can_close_tickets',
+            'ticket.assign' => 'can_assign_tickets',
+            'ticket.transfer' => 'can_transfer_tickets',
+            'task.create' => 'can_create_tickets',
+            'task.edit' => 'can_edit_tickets',
+            'task.reply' => 'can_post_ticket_reply',
+            'task.delete' => 'can_delete_tickets',
+            'task.close' => 'can_close_tickets',
+            'task.assign' => 'can_assign_tickets',
+            'task.transfer' => 'can_transfer_tickets',
+            'emails.banlist' => 'can_ban_emails',
+            'canned.manage' => 'can_manage_premade',
+            'faq.manage' => 'can_manage_faq',
+            'stats.agents' => 'can_view_staff_stats',
     );
 
     function run($max_time) {
@@ -30,7 +54,7 @@ class GroupRoles extends MigrationTask {
                     'notes' => $group->getName()
                     );
             $perms = array();
-            foreach (self::$pmap as  $k => $v) {
+            foreach (self::$pmap as  $v => $k) {
                 if ($group->{$k})
                     $perms[] = $v;
             }
diff --git a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
index e9920fd42d992586552c50fd77e8db0aebc53d78..4eb98233d7ff69fad3fbafdcdb573b0304715a87 100644
--- a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
+++ b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql
@@ -14,10 +14,6 @@
 ALTER TABLE `%TABLE_PREFIX%attachment`
     ADD `lang` varchar(16) AFTER `inline`;
 
-ALTER TABLE `%TABLE_PREFIX%faq`
-    ADD `views` int(10) unsigned NOT NULL default '0' AFTER `notes`,
-    ADD `score` int(10) NOT NULL default '0' AFTER `views`;
-
 ALTER TABLE `%TABLE_PREFIX%staff`
     ADD `lang` varchar(16) DEFAULT NULL AFTER `signature`,
     ADD `timezone` varchar(64) default NULL AFTER `lang`,
diff --git a/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql
index 67a21aa7759080efc95cc08428285ce8490462c9..5db5be880fadc8107b909d5df1d99d3016b85de1 100644
--- a/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql
+++ b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql
@@ -66,7 +66,6 @@ ALTER TABLE `%TABLE_PREFIX%email`
 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';
@@ -110,9 +109,6 @@ INSERT INTO `%TABLE_PREFIX%content`
         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`)
@@ -122,9 +118,6 @@ INSERT INTO `%TABLE_PREFIX%content`
         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');
diff --git a/scp/canned.php b/scp/canned.php
index 85af1b2c46e4420a0e7528f95a33e76d8ab96060..8a54473754f103860f95c793059888b9e9a19193 100644
--- a/scp/canned.php
+++ b/scp/canned.php
@@ -18,7 +18,7 @@ include_once(INCLUDE_DIR.'class.canned.php');
 
 /* check permission */
 if(!$thisstaff
-        || !$thisstaff->getRole()->hasPerm(CannedModel::PERM_MANAGE)
+        || !$thisstaff->getRole()->hasPerm(Canned::PERM_MANAGE, false)
         || !$cfg->isCannedResponseEnabled()) {
     header('Location: kb.php');
     exit;
diff --git a/scp/css/dropdown.css b/scp/css/dropdown.css
index 8a5b560d8dd69c13d29a2d0f71af9965ee3d1a92..c0b90c83a9fc3b080df495c16e74c18380b88770 100644
--- a/scp/css/dropdown.css
+++ b/scp/css/dropdown.css
@@ -43,8 +43,9 @@
 .action-dropdown ul li > a i {
   margin-right: 0.1em;
 }
-.action-dropdown ul li > a:hover {
-  background-color: #08C !important;
+.action-dropdown ul li > a:hover,
+.action-dropdown ul li.active > a:hover {
+  background-color: #08C;
   color: #FFF !important;
   cursor: pointer;
 }
@@ -98,10 +99,6 @@
   left: auto;
   right: 10px;
 }
-.action-button {
-  line-height: 22px;
-  height: 22px;
-}
 .action-button span,
 .action-button a {
   color: inherit;
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 8287b4ce28c2de7d65e9a7d90e20406c2b5765b0..a65fb829425a7e9968b2d62097869879c079ee79 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -247,9 +247,7 @@ a time.relative {
     border-left:1px solid #ccc;
     border-right:1px solid #ccc;
     border-radius: 0 0 5px 5px;
-
     display:block;
-    padding-left: 5px;
     -moz-box-shadow: 3px 3px 3px #ccc;
     -webkit-box-shadow: 3px 3px 3px #ccc;
     box-shadow: 3px 3px 3px #ccc;
@@ -601,13 +599,6 @@ a.print {
     background-image:url(../images/icons/printer.gif);
 }
 
-.btn {
-    padding:3px 10px;
-    background:url(../images/btn_bg.png) top left repeat-x #ccc;
-    border:1px solid #777;
-    color:#000;
-}
-
 .btn_sm {
     padding:2px 5px;
     font-size:0.9em;
@@ -617,10 +608,6 @@ a.print {
     font-weight:bold;
 }
 
-.btn:hover, .btn_sm:hover {
-    background-position: bottom left;
-}
-
 .search label {
     display:block;
     line-height:25px;
@@ -1884,6 +1871,10 @@ input[type=button].small, .small.button, input[type=submit].small {
   border: none;
 }
 
+.action-button.inline, .button.inline {
+    vertical-align: middle;
+}
+
 /* Dynamic forms in dialogs */
 .dialog th, .tip_box th {
     text-align: left;
@@ -1948,8 +1939,6 @@ ul.progress li.yes small {color:green; }
 ul.progress li.no small {color:red;}
 
 #bar { clear: both; padding-top: 10px; height: 24px; line-height: 24px; text-align: center; border-top: 1px solid #aaaaaa; }
-#bar a, #bar .btn { display: inline-block; margin: 0; height: 24px; line-height: 24px; font-weight: bold; border: 1px solid #666666; text-decoration: none; padding: 0 10px; background: url('../images/grey_btn_bg.png?1312910883') top left repeat-x; color: #333; }
-#bar a:hover, #bar .btn:hover, #bar .btnover { background-position: bottom left; }
 #bar a.unstyled, #bar a.unstyled:hover { font-weight: normal; background: none; border: none; text-decoration: underline; color: #2a67ac; }
 
 #bar.error { background: #ffd; text-align: center; color: #a00; font-weight: bold; }
@@ -2649,7 +2638,7 @@ input[type=checkbox] {
   margin-top: 3px;
 }
 
-input, select, textarea {
+input, textarea {
     padding: 3px 5px;
     font-size: 0.95em;
     font-family: inherit;
diff --git a/scp/tickets.php b/scp/tickets.php
index 7027667a12d6204df6d6ba69f33fb75e2805351d..f3bbe8d7c00f6409974251bf5326aa7c9ec670ff 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -318,7 +318,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'banemail':
-                    if (!$role->hasPerm(Email::PERM_BANLIST)) {
+                    if (!$thisstaff->hasPerm(Email::PERM_BANLIST)) {
                         $errors['err']=__('Permission Denied. You are not allowed to ban emails');
                     } elseif(BanList::includes($ticket->getEmail())) {
                         $errors['err']=__('Email already in banlist');
@@ -329,7 +329,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'unbanemail':
-                    if (!$role->hasPerm(Email::PERM_BANLIST)) {
+                    if (!$thisstaff->hasPerm(Email::PERM_BANLIST)) {
                         $errors['err'] = __('Permission Denied. You are not allowed to remove emails from banlist.');
                     } elseif(Banlist::remove($ticket->getEmail())) {
                         $msg = __('Email removed from banlist');
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index c10b1ed9eedb718e63a05c58946b4c08bf45d285..3986191a94c517f3cbdb39be38b00cc4081bf1cf 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -53,8 +53,8 @@ class Installer extends SetupWizard {
         $f['lname']         = array('type'=>'string',   'required'=>1, 'error'=>__('Last name required'));
         $f['admin_email']   = array('type'=>'email',    'required'=>1, 'error'=>__('Valid email required'));
         $f['username']      = array('type'=>'username', 'required'=>1, 'error'=>__('Username required'));
-        $f['passwd']        = array('type'=>'password', 'required'=>1, 'error'=>__('Password required'));
-        $f['passwd2']       = array('type'=>'password', 'required'=>1, 'error'=>__('Confirm Password'));
+        $f['passwd']        = array('type'=>'string', 'required'=>1, 'error'=>__('Password required'));
+        $f['passwd2']       = array('type'=>'string', 'required'=>1, 'error'=>__('Confirm Password'));
         $f['prefix']        = array('type'=>'string',   'required'=>1, 'error'=>__('Table prefix required'));
         $f['dbhost']        = array('type'=>'string',   'required'=>1, 'error'=>__('Host name required'));
         $f['dbname']        = array('type'=>'string',   'required'=>1, 'error'=>__('Database name required'));
@@ -73,6 +73,14 @@ class Installer extends SetupWizard {
         //Admin's pass confirmation.
         if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2']))
             $this->errors['passwd2']=__('Password(s) do not match');
+        try {
+            require_once INCLUDE_DIR.'class.auth.php';
+            PasswordPolicy::checkPassword($vars['passwd'], null);
+        }
+        catch (BadPassword $e) {
+            $this->errors['passwd'] = $e->getMessage();
+        }
+
         //Check table prefix underscore required at the end!
         if($vars['prefix'] && substr($vars['prefix'], -1)!='_')
             $this->errors['prefix']=__('Bad prefix. Must have underscore (_) at the end. e.g \'ost_\'');
@@ -110,8 +118,9 @@ class Installer extends SetupWizard {
             }
         }
 
-        //bailout on errors.
-        if($this->errors) return false;
+        // bailout on errors.
+        if ($this->errors)
+            return false;
 
         /*************** We're ready to install ************************/
         define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install.
@@ -151,111 +160,125 @@ class Installer extends SetupWizard {
             }
         }
 
-        if(!$this->errors) {
-
-            // TODO: Use language selected from install worksheet
-            $i18n = new Internationalization($vars['lang_id']);
-            $i18n->loadDefaultData();
-
-            Signal::send('system.install', $this);
-
-            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'sla` ORDER BY `id` LIMIT 1';
-            $sla_id_1 = db_result(db_query($sql, false));
-
-            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'department` ORDER BY `id` LIMIT 1';
-            $dept_id_1 = db_result(db_query($sql, false));
-
-            $sql='SELECT `tpl_id` FROM `'.TABLE_PREFIX.'email_template_group` ORDER BY `tpl_id` LIMIT 1';
-            $template_id_1 = db_result(db_query($sql, false));
-
-            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'group` ORDER BY `id` LIMIT 1';
-            $group_id_1 = db_result(db_query($sql, false));
-
-            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'role` ORDER BY `id` LIMIT 1';
-            $role_id_1 = db_result(db_query($sql, false));
+        if ($this->errors)
+            return false;
 
-            //Create admin user.
-            $sql='INSERT INTO '.TABLE_PREFIX.'staff SET created=NOW() '
-                .', isactive=1, isadmin=1, max_page_size=25 '
-                .', group_id='.db_input($group_id_1)
-                .', dept_id='.db_input($dept_id_1)
-                .', role_id='.db_input($role_id_1)
-                .', email='.db_input($vars['admin_email'])
-                .', firstname='.db_input($vars['fname'])
-                .', lastname='.db_input($vars['lname'])
-                .', username='.db_input($vars['username'])
-                .', passwd='.db_input(Passwd::hash($vars['passwd']));
-            if(!db_query($sql, false) || !($uid=db_insert_id()))
-                $this->errors['err']=__('Unable to create admin user (#6)');
+        // TODO: Use language selected from install worksheet
+        $i18n = new Internationalization($vars['lang_id']);
+        $i18n->loadDefaultData();
+
+        Signal::send('system.install', $this);
+
+        list($sla_id) = Sla::objects()->order_by('id')->values_flat('id')->first();
+        list($dept_id) = Dept::objects()->order_by('id')->values_flat('id')->first();
+        list($role_id) = Role::objects()->order_by('id')->values_flat('id')->first();
+
+        $sql='SELECT `tpl_id` FROM `'.TABLE_PREFIX.'email_template_group` ORDER BY `tpl_id` LIMIT 1';
+        $template_id_1 = db_result(db_query($sql, false));
+
+        // Create admin user.
+        $staff = Staff::create(array(
+            'isactive' => 1,
+            'isadmin' => 1,
+            'max_page_size' => 25,
+            'dept_id' => $dept_id,
+            'role_id' => $role_id,
+            'email' => $vars['admin_email'],
+            'firstname' => $vars['fname'],
+            'lastname' => $vars['lname'],
+            'username' => $vars['username'],
+        ));
+        $staff->updatePerms(array(
+            User::PERM_CREATE,
+            User::PERM_EDIT,
+            User::PERM_DELETE,
+            User::PERM_MANAGE,
+            User::PERM_DIRECTORY,
+            Organization::PERM_CREATE,
+            Organization::PERM_EDIT,
+            Organization::PERM_DELETE,
+            FAQ::PERM_MANAGE,
+            Email::PERM_BANLIST,
+        ), $errors);
+        $staff->setPassword($vars['passwd']);
+        if (!$staff->save()) {
+            $this->errors['err'] = __('Unable to create admin user (#6)');
+            return false;
         }
 
-        if(!$this->errors) {
-            //Create default emails!
-            $email = $vars['email'];
-            list(,$domain)=explode('@',$vars['email']);
-            $sql='INSERT INTO '.TABLE_PREFIX.'email (`name`,`email`,`created`,`updated`) VALUES '
-                    ." ('Support','$email',NOW(),NOW())"
-                    .",('osTicket Alerts','alerts@$domain',NOW(),NOW())"
-                    .",('','noreply@$domain',NOW(),NOW())";
-            $support_email_id = db_query($sql, false) ? db_insert_id() : 0;
-
-
-            $sql='SELECT `email_id` FROM '.TABLE_PREFIX."email WHERE `email`='alerts@$domain' LIMIT 1";
-            $alert_email_id = db_result(db_query($sql, false));
-
-            //Create config settings---default settings!
-            $defaults = array(
-                'default_email_id'=>$support_email_id,
-                'alert_email_id'=>$alert_email_id,
-                'default_dept_id'=>$dept_id_1, 'default_sla_id'=>$sla_id_1,
-                'default_template_id'=>$template_id_1,
-                'default_timezone' => $vars['timezone'] ?: date_default_timezone_get(),
-                'admin_email'=>$vars['admin_email'],
-                'schema_signature'=>$streams['core'],
-                'helpdesk_url'=>URL,
-                'helpdesk_title'=>$vars['name']);
-            $config = new Config('core');
-            if (!$config->updateAll($defaults))
-                $this->errors['err']=__('Unable to create config settings').' (#7)';
-
-            // Set company name
-            require_once(INCLUDE_DIR.'class.company.php');
-            $company = new Company();
-            $company->getForm()->setAnswer('name', $vars['name']);
-            $company->getForm()->save();
-
-			foreach ($streams as $stream=>$signature) {
-				if ($stream != 'core') {
-                    $config = new Config($stream);
-                    if (!$config->update('schema_signature', $signature))
-                        $this->errors['err']=__('Unable to create config settings').' (#8)';
-				}
-			}
+        // Create default emails!
+        $email = $vars['email'];
+        list(,$domain) = explode('@', $vars['email']);
+        foreach (array(
+            "Support" => $email,
+            "osTicket Alerts" => "alerts@$domain",
+            '' => "noreply@$domain",
+        ) as $name => $mailbox) {
+            $mb = Email::create(array(
+                'name' => $name,
+                'email' => $mailbox,
+                'dept_id' => $dept_id,
+            ));
+            $mb->save();
+            if ($mailbox == $email)
+                $support_email_id = $mb->email_id;
+            if ($mailbox == "alerts@$domain")
+                $alert_email_id = $mb->email_id;
         }
 
-        if($this->errors) return false; //Abort on internal errors.
+        //Create config settings---default settings!
+        $defaults = array(
+            'default_email_id'=>$support_email_id,
+            'alert_email_id'=>$alert_email_id,
+            'default_dept_id'=>$dept_id, 'default_sla_id'=>$sla_id,
+            'default_template_id'=>$template_id_1,
+            'default_timezone' => $vars['timezone'] ?: date_default_timezone_get(),
+            'admin_email'=>$vars['admin_email'],
+            'schema_signature'=>$streams['core'],
+            'helpdesk_url'=>URL,
+            'helpdesk_title'=>$vars['name']
+        );
+
+        $config = new Config('core');
+        if (!$config->updateAll($defaults))
+            $this->errors['err']=__('Unable to create config settings').' (#7)';
+
+        // Set company name
+        require_once(INCLUDE_DIR.'class.company.php');
+        $company = new Company();
+        $company->getForm()->setAnswer('name', $vars['name']);
+        $company->getForm()->save();
+
+        foreach ($streams as $stream => $signature) {
+            if ($stream != 'core') {
+                $config = new Config($stream);
+                if (!$config->update('schema_signature', $signature))
+                    $this->errors['err'] = __('Unable to create config settings').' (#8)';
+				    }
+			  }
+
+        if ($this->errors)
+            return false; //Abort on internal errors.
 
 
         //Rewrite the config file - MUST be done last to allow for installer recovery.
-        $configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile);
-        $configFile= str_replace('%ADMIN-EMAIL',$vars['admin_email'],$configFile);
-        $configFile= str_replace('%CONFIG-DBHOST',$vars['dbhost'],$configFile);
-        $configFile= str_replace('%CONFIG-DBNAME',$vars['dbname'],$configFile);
-        $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile);
-        $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile);
-        $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile);
-        $configFile= str_replace('%CONFIG-SIRI',Misc::randCode(32),$configFile);
-        if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) {
+        $configFile = strtr($configFile, array(
+            "define('OSTINSTALLED',FALSE);" => "define('OSTINSTALLED',TRUE);",
+            '%ADMIN-EMAIL' => $vars['admin_email'],
+            '%CONFIG-DBHOST' => $vars['dbhost'],
+            '%CONFIG-DBNAME' => $vars['dbname'],
+            '%CONFIG-DBUSER' => $vars['dbuser'],
+            '%CONFIG-DBPASS' => $vars['dbpass'],
+            '%CONFIG-PREFIX' => $vars['prefix'],
+            '%CONFIG-SIRI' => Misc::randCode(32),
+        ));
+        if (!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) {
             $this->errors['err']=__('Unable to write to config file. Permission denied! (#5)');
             return false;
         }
         @fclose($fp);
 
         /************* Make the system happy ***********************/
-
-        $sql='UPDATE '.TABLE_PREFIX."email SET dept_id=$dept_id_1";
-        db_query($sql, false);
-
         global $cfg;
         $cfg = new OsticketConfig();
 
@@ -266,9 +289,9 @@ class Installer extends SetupWizard {
         $ticket = Ticket::create($ticket_vars, $errors, 'api', false, false);
 
         if ($ticket
-                && ($org = Organization::objects()->order_by('id')->one())) {
-
-            $user=User::lookup($ticket->getOwnerId());
+            && ($org = Organization::objects()->order_by('id')->one())
+        ) {
+            $user = User::lookup($ticket->getOwnerId());
             $user->setOrganization($org);
         }
 
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 720cb2557760c13c459deda590849b8494ec91c1..105d0420a28ce9d3755328854fdbc2dc22e45c63 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -37,8 +37,6 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` (
   `answer` text NOT NULL,
   `keywords` tinytext,
   `notes` text,
-  `views` int(10) unsigned NOT NULL default '0',
-  `score` int(10) NOT NULL default '0',
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY  (`faq_id`),
@@ -559,8 +557,7 @@ CREATE TABLE `%TABLE_PREFIX%staff` (
   PRIMARY KEY  (`staff_id`),
   UNIQUE KEY `username` (`username`),
   KEY `dept_id` (`dept_id`),
-  KEY `issuperuser` (`isadmin`),
-  KEY `group_id` (`group_id`,`staff_id`)
+  KEY `issuperuser` (`isadmin`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%staff_dept_access`;
@@ -615,6 +612,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%thread` (
   `object_id` int(11) unsigned NOT NULL,
   `object_type` char(1) NOT NULL,
   `extra` text,
+  `lastresponse` datetime DEFAULT NULL,
+  `lastmessage` datetime DEFAULT NULL,
   `created` datetime NOT NULL,
   PRIMARY KEY (`id`),
   KEY `object_id` (`object_id`),
@@ -682,8 +681,6 @@ CREATE TABLE `%TABLE_PREFIX%ticket` (
   `est_duedate` datetime default NULL,
   `reopened` datetime default NULL,
   `closed` datetime default NULL,
-  `lastmessage` datetime default NULL,
-  `lastresponse` datetime default NULL,
   `lastupdate` datetime default NULL,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
@@ -772,7 +769,8 @@ CREATE TABLE `%TABLE_PREFIX%thread_collaborator` (
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY (`id`),
-  UNIQUE KEY `collab` (`thread_id`,`user_id`)
+  UNIQUE KEY `collab` (`thread_id`,`user_id`),
+  KEY `user_id` (`user_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%task`;
@@ -787,6 +785,7 @@ CREATE TABLE `%TABLE_PREFIX%task` (
   `lock_id` int(11) unsigned NOT NULL DEFAULT '0',
   `flags` int(10) unsigned NOT NULL DEFAULT '0',
   `duedate` datetime DEFAULT NULL,
+  `closed` datetime DEFAULT NULL,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
   PRIMARY KEY (`id`),