diff --git a/bootstrap.php b/bootstrap.php
index c5a03fe2ab45d9bbe2982b95e4d4b6444caec192..424f3637c1bc5cc9ecde66edd30fd07555490562 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -80,8 +80,9 @@ class Bootstrap {
         define('TEAM_TABLE',$prefix.'team');
         define('TEAM_MEMBER_TABLE',$prefix.'team_member');
         define('DEPT_TABLE',$prefix.'department');
-        define('GROUP_TABLE',$prefix.'groups');
+        define('GROUP_TABLE', $prefix.'group');
         define('GROUP_DEPT_TABLE', $prefix.'group_dept_access');
+        define('ROLE_TABLE', $prefix.'role');
 
         define('FAQ_TABLE',$prefix.'faq');
         define('FAQ_TOPIC_TABLE',$prefix.'faq_topic');
diff --git a/include/ajax.draft.php b/include/ajax.draft.php
index 5fc24e77a9c5e08d3e8607a6613c4e1a7fdaec6b..8d264ff486639b94df5622028ea611c656f130a0 100644
--- a/include/ajax.draft.php
+++ b/include/ajax.draft.php
@@ -291,7 +291,7 @@ class DraftAjaxAPI extends AjaxController {
 
         $draft = Draft::create(array(
             'staff_id' => $thisstaff->getId(),
-            'namespace' => $namepace
+            'namespace' => $namespace
         ));
         if (!$draft->save())
             Http::response(500, 'Unable to create draft');
diff --git a/include/ajax.i18n.php b/include/ajax.i18n.php
index 38793902e4405e5451c27da173a808f0bda69532..9d3e76b6025600129519afff79d97d551ac02ee5 100644
--- a/include/ajax.i18n.php
+++ b/include/ajax.i18n.php
@@ -67,11 +67,11 @@ class i18nAjaxAPI extends AjaxController {
         }
         foreach ($_POST as $lang => $phrase) {
             if (isset($phrases[$lang])) {
+                $p = $phrases[$lang];
                 if (!$phrase) {
                     $p->delete();
                 }
                 else {
-                    $p = $phrases[$lang];
                     // Avoid XSS injection
                     $p->text = trim(Format::striptags($phrase));
                     $p->agent_id = $thisstaff->getId();
diff --git a/include/ajax.reports.php b/include/ajax.reports.php
index e9e660a8c69b36f0c876ca63724b8735789aae94..bdaa7d9fe3fb828fe7428ed7a2c4cb03101d328d 100644
--- a/include/ajax.reports.php
+++ b/include/ajax.reports.php
@@ -42,11 +42,11 @@ class OverviewReportAjaxAPI extends AjaxController {
         $groups = array(
             "dept" => array(
                 "table" => DEPT_TABLE,
-                "pk" => "dept_id",
-                "sort" => 'T1.dept_name',
-                "fields" => 'T1.dept_name',
+                "pk" => "id",
+                "sort" => 'T1.name',
+                "fields" => 'T1.name',
                 "headers" => array(__('Department')),
-                "filter" => ('T1.dept_id IN ('.implode(',', db_input($thisstaff->getDepts())).')')
+                "filter" => ('T1.id IN ('.implode(',', db_input($thisstaff->getDepts())).')')
             ),
             "topic" => array(
                 "table" => TOPIC_TABLE,
@@ -70,7 +70,7 @@ class OverviewReportAjaxAPI extends AjaxController {
                       (T1.staff_id='.db_input($thisstaff->getId())
                         .(($depts=$thisstaff->getManagedDepartments())?
                             (' OR T1.dept_id IN('.implode(',', db_input($depts)).')'):'')
-                        .(($thisstaff->canViewStaffStats())?
+                        .(($thisstaff->getRole()->canViewStaffStats())?
                             (' OR T1.dept_id IN('.implode(',', db_input($thisstaff->getDepts())).')'):'')
                      .')'
                      )
diff --git a/include/ajax.tickets.php b/include/ajax.tickets.php
index d8fccf5dc451aa81a673e56627f00e7c82e25854..196bcc390e7147479bc5fd073dfaef3ec25e331b 100644
--- a/include/ajax.tickets.php
+++ b/include/ajax.tickets.php
@@ -536,6 +536,8 @@ class TicketsAjaxAPI extends AjaxController {
                 || !$ticket->checkStaffAccess($thisstaff))
             Http::response(404, 'Unknown ticket #');
 
+        $role = $thisstaff->getRole($ticket->getDeptId());
+
         $info = array();
         $state = null;
         switch($status) {
@@ -544,12 +546,12 @@ class TicketsAjaxAPI extends AjaxController {
                 $state = 'open';
                 break;
             case 'close':
-                if (!$thisstaff->canCloseTickets())
+                if (!$role->canCloseTickets())
                     Http::response(403, 'Access denied');
                 $state = 'closed';
                 break;
             case 'delete':
-                if (!$thisstaff->canDeleteTickets())
+                if (!$role->canDeleteTickets())
                     Http::response(403, 'Access denied');
                 $state = 'deleted';
                 break;
@@ -584,20 +586,21 @@ class TicketsAjaxAPI extends AjaxController {
                     __($status->getName()));
         else {
             // Make sure the agent has permission to set the status
+            $role = $thisstaff->getRole($ticket->getDeptId());
             switch(mb_strtolower($status->getState())) {
                 case 'open':
-                    if (!$thisstaff->canCloseTickets()
-                            && !$thisstaff->canCreateTickets())
+                    if (!$role->canCloseTickets()
+                            && !$role->canCreateTickets())
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
                                 __('to reopen tickets'));
                     break;
                 case 'closed':
-                    if (!$thisstaff->canCloseTickets())
+                    if (!$role->canCloseTickets())
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
                                 __('to resolve/close tickets'));
                     break;
                 case 'deleted':
-                    if (!$thisstaff->canDeleteTickets())
+                    if (!$role->canDeleteTickets())
                         $errors['err'] = sprintf(__('You do not have permission %s.'),
                                 __('to archive/delete tickets'));
                     break;
diff --git a/include/class.auth.php b/include/class.auth.php
index e6fed10c5089badd4597d9ab220964b717964281..b115258952323bd069ece521c8643b7211f5fa45 100644
--- a/include/class.auth.php
+++ b/include/class.auth.php
@@ -4,7 +4,7 @@ interface AuthenticatedUser {
     // Get basic information
     function getId();
     function getUsername();
-    function getRole();
+    function getUserType();
 
     //Backend used to authenticate the user
     function getAuthBackend();
@@ -30,7 +30,7 @@ implements AuthenticatedUser {
     // Get basic information
     abstract function getId();
     abstract function getUsername();
-    abstract function getRole();
+    abstract function getUserType();
 
     //Backend used to authenticate the user
     abstract function getAuthBackend();
diff --git a/include/class.client.php b/include/class.client.php
index a8b39d1e5135bc69e01fccf9ef711bfbf32b613b..39b8241c9da43a839e70770f5632a8d236b94abf 100644
--- a/include/class.client.php
+++ b/include/class.client.php
@@ -232,7 +232,7 @@ class  EndUser extends BaseAuthenticatedUser {
         return $this->user->getEmail();
     }
 
-    function getRole() {
+    function getUserType() {
         return $this->isOwner() ? 'owner' : 'collaborator';
     }
 
diff --git a/include/class.config.php b/include/class.config.php
index 164171a920a964493fbd24ebe969f814709c65ee..6fd4208b64e2f1e9a0f8fbc3aa5d44a8fb161704 100644
--- a/include/class.config.php
+++ b/include/class.config.php
@@ -39,6 +39,10 @@ class Config {
         if (!isset($_SESSION['cfg:'.$this->section]))
             $_SESSION['cfg:'.$this->section] = array();
         $this->session = &$_SESSION['cfg:'.$this->section];
+        $this->load();
+    }
+
+    function load() {
 
         $sql='SELECT id, `key`, value, `updated` FROM '.$this->table
             .' WHERE `'.$this->section_column.'` = '.db_input($this->section);
diff --git a/include/class.dept.php b/include/class.dept.php
index d6d1fd9248d2aac08dd6bc6f2de3f786c6610dad..a3129c909dfe991fe80b3588a5d098fa1d35a997 100644
--- a/include/class.dept.php
+++ b/include/class.dept.php
@@ -18,22 +18,35 @@ class Dept extends VerySimpleModel {
 
     static $meta = array(
         'table' => DEPT_TABLE,
-        'pk' => array('dept_id'),
+        'pk' => array('id'),
         'joins' => array(
+            'email' => array(
+                'constraint' => array('email_id' => 'EmailModel.email_id'),
+                'null' => true,
+             ),
             'sla' => array(
                 'constraint' => array('sla_id' => 'SLA.sla_id'),
                 'null' => true,
             ),
             'manager' => array(
+                'null' => true,
                 'constraint' => array('manager_id' => 'Staff.staff_id'),
             ),
+            'members' => array(
+                'null' => true,
+                'list' => true,
+                'reverse' => 'Staff.dept',
+            ),
             'groups' => array(
+                'null' => true,
+                'list' => true,
                 'reverse' => 'GroupDeptAccess.dept'
             ),
         ),
     );
 
-    var $members;
+    var $_members;
+    var $_groupids;
     var $config;
 
     var $template;
@@ -55,17 +68,17 @@ class Dept extends VerySimpleModel {
     }
 
     function getId() {
-        return $this->dept_id;
+        return $this->id;
     }
 
     function getName() {
-        return $this->dept_name;
+        return $this->name;
     }
 
     function getLocalName($locale=false) {
         $tag = $this->getTranslateTag();
         $T = CustomDataTranslation::translate($tag);
-        return $T != $tag ? $T : $this->dept_name;
+        return $T != $tag ? $T : $this->name;
     }
     static function getLocalById($id, $subtag, $default) {
         $tag = _H(sprintf('dept.%s.%s', $subtag, $id));
@@ -82,11 +95,12 @@ class Dept extends VerySimpleModel {
     }
 
     function getEmail() {
-        if(!$this->email)
-            if(!($this->email = Email::lookup($this->getEmailId())) && $cfg)
-                $this->email = $cfg->getDefaultEmail();
+        global $cfg;
+
+        if ($this->email)
+            return $this->email;
 
-        return $this->email;
+        return $cfg? $cfg->getDefaultEmail() : null;
     }
 
     function getNumMembers() {
@@ -94,12 +108,12 @@ class Dept extends VerySimpleModel {
     }
 
     function getMembers($criteria=null) {
-        if (!$this->members || $criteria) {
+        if (!$this->_members || $criteria) {
             $members = Staff::objects()
                 ->filter(Q::any(array(
                     'dept_id' => $this->getId(),
                     new Q(array(
-                        'group__depts__dept_id' => $this->getId(),
+                        'group__depts__id' => $this->getId(),
                         'group__depts__group_membership' => self::ALERTS_DEPT_AND_GROUPS,
                     )),
                     'staff_id' => $this->manager_id
@@ -112,14 +126,14 @@ class Dept extends VerySimpleModel {
                     'onvacation' => 0,
                 ));
 
-            $qs->order_by('lastname', 'firstname');
+            $members->order_by('lastname', 'firstname');
 
             if ($criteria)
-                return $qs->all();
+                return $members->all();
 
-            $this->members = $qs->all();
+            $this->_members = $members->all();
         }
-        return $this->members;
+        return $this->_members;
     }
 
     function getAvailableMembers() {
@@ -177,7 +191,7 @@ class Dept extends VerySimpleModel {
     }
 
     function getSignature() {
-        return $this->dept_signature;
+        return $this->signature;
     }
 
     function canAppendSignature() {
@@ -225,7 +239,12 @@ class Dept extends VerySimpleModel {
     }
 
     function getHashtable() {
-        return $this->ht;
+        $ht = $this->ht;
+        if (static::$meta['joins'])
+            foreach (static::$meta['joins'] as $k => $v)
+                unset($ht[$k]);
+
+        return $ht;
     }
 
     function getInfo() {
@@ -233,42 +252,54 @@ class Dept extends VerySimpleModel {
     }
 
     function getAllowedGroups() {
-        if ($this->groups)
-            return $this->groups;
 
-        $groups = GroupDept::object()
-            ->filter(array('dept_id' => $this->getId()))
-            ->values_flat('group_id');
+        if (!isset($this->_groupids)) {
+            $this->_groupids = array();
+
+            $groups = GroupDeptAccess::objects()
+                ->filter(array('dept_id' => $this->getId()))
+                ->values_flat('group_id');
 
-        foreach ($groups as $row) {
-            list($id) = $row;
-            $this->groups[] = $id;
+            foreach ($groups as $row)
+                $this->_groupids[] = $row[0];
         }
-        return $this->groups;
+
+        return $this->_groupids;
     }
 
-    function updateSettings($vars) {
+    function updateGroups($groups_ids, $vars) {
 
-        // Groups allowes to access department
-        if($vars['groups'] && is_array($vars['groups'])) {
-            $groups = GroupDept::object()
+        // Groups allowed to access department
+        if (is_array($groups_ids)) {
+            $groups = GroupDeptAccess::objects()
                 ->filter(array('dept_id' => $this->getId()));
             foreach ($groups as $group) {
-                if ($idx = array_search($group->group_id, $vars['groups']))
-                    unset($vars['groups'][$idx]);
-                else
+                if ($idx = array_search($group->group_id, $groups_ids)) {
+                    unset($groups_ids[$idx]);
+                    $roleId = $vars['group'.$group->group_id.'_role_id'];
+                    if ($roleId != $group->role_id) {
+                        $group->set('role_id', $roleId ?: 0);
+                        $group->save();
+                    }
+                } else {
                     $group->delete();
+                }
             }
-            foreach ($vars['groups'] as $id) {
-                GroupDept::create(array(
-                    'dept_id'=>$this->getId(), 'group_id'=>$id
+            foreach ($groups_ids as $id) {
+                $roleId = $vars['group'.$id.'_role_id'];
+                GroupDeptAccess::create(array(
+                    'dept_id' => $this->getId(),
+                    'group_id' => $id,
+                    'role_id' => $roleId ?: 0,
                 ))->save();
             }
         }
 
-        // Misc. config settings
-        $this->getConfig()->set('assign_members_only', $vars['assign_members_only']);
+    }
 
+    function updateSettings($vars) {
+        $this->updateGroups($vars['groups'] ?: array(), $vars);
+        $this->getConfig()->set('assign_members_only', $vars['assign_members_only']);
         return true;
     }
 
@@ -279,16 +310,14 @@ class Dept extends VerySimpleModel {
             // Default department cannot be deleted
             || $this->getId()==$cfg->getDefaultDeptId()
             // Department  with users cannot be deleted
-            || Staff::objects()
-                ->filter(array('dept_id'=>$this->getId()))
-                ->count()
+            || $this->members->count()
         ) {
             return 0;
         }
 
         parent::delete();
         $id = $this->getId();
-        $sql='DELETE FROM '.DEPT_TABLE.' WHERE dept_id='.db_input($id).' LIMIT 1';
+        $sql='DELETE FROM '.DEPT_TABLE.' WHERE id='.db_input($id).' LIMIT 1';
         if(db_query($sql) && ($num=db_affected_rows())) {
             // DO SOME HOUSE CLEANING
             //Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes.
@@ -318,8 +347,8 @@ class Dept extends VerySimpleModel {
     /*----Static functions-------*/
 	static function getIdByName($name) {
         $row = static::objects()
-            ->filter(array('dept_name'=>$name))
-            ->values_flat('dept_id')
+            ->filter(array('name'=>$name))
+            ->values_flat('id')
             ->first();
 
         return $row ? $row[0] : 0;
@@ -339,38 +368,59 @@ class Dept extends VerySimpleModel {
     }
 
     static function getDepartments( $criteria=null) {
+        static $depts = null;
+
+        if (!isset($depts) || $criteria) {
+            $depts = array();
+            $query = self::objects();
+            if (isset($criteria['publiconly']))
+                $query->filter(array(
+                            'ispublic' => ($criteria['publiconly'] ? 1 : 0)));
+
+            if ($manager=$criteria['manager'])
+                $query->filter(array(
+                            'manager_id' => is_object($manager)?$manager->getId():$manager));
+
+            $query->order_by('name')
+                ->values_flat('id', 'name');
+
+            $names = array();
+            foreach ($query as $row)
+                $names[$row[0]] = $row[1];
+
+            // Fetch local names
+            foreach (CustomDataTranslation::getDepartmentNames(array_keys($names)) as $id=>$name) {
+                // Translate the department
+                $names[$id] = $name;
+            }
 
-        $depts = self::objects();
-        if ($criteria['publiconly'])
-            $depts->filter(array('public' => 1));
-
-        if ($manager=$criteria['manager'])
-            $depts->filter(array('manager_id' => is_object($manager)?$manager->getId():$manager));
-
-        $depts->order_by('dept_name')
-            ->values_flat('dept_id', 'dept_name');
+            if ($criteria)
+                return $names;
 
-        $names = array();
-        foreach ($depts as $row) {
-            list($id, $name) = $row;
-            $names[$id] = $name;
+            $depts = $names;
         }
 
-        // Fetch local names
-        foreach (CustomDataTranslation::getDepartmentNames(array_keys($names)) as $id=>$name) {
-            // Translate the department
-            $names[$id] = $name;
-        }
-        return $names;
+        return $depts;
     }
 
-    function getPublicDepartments() {
-        return self::getDepartments(array('publiconly'=>true));
+    static function getPublicDepartments() {
+        static $depts =null;
+
+        if (!$depts)
+            $depts = self::getDepartments(array('publiconly'=>true));
+
+        return $depts;
     }
 
-    static function create($vars, &$errors) {
+    static function create($vars=false, &$errors=array()) {
         $dept = parent::create($vars);
-        $dept->create = SqlFunction::NOW();
+        $dept->created = SqlFunction::NOW();
+        return $dept;
+    }
+
+    static function __create($vars, &$errors) {
+        $dept = self::create($vars);
+        $dept->save();
         return $dept;
     }
 
@@ -383,7 +433,7 @@ class Dept extends VerySimpleModel {
     function update($vars, &$errors) {
         global $cfg;
 
-        if (isset($this->dept_id) && $this->getId() != $vars['id'])
+        if (isset($this->id) && $this->getId() != $vars['id'])
             $errors['err']=__('Missing or invalid Dept ID (internal error).');
 
         if (!$vars['name']) {
@@ -391,7 +441,7 @@ class Dept extends VerySimpleModel {
         } elseif (strlen($vars['name'])<4) {
             $errors['name']=__('Name is too short.');
         } elseif (($did=static::getIdByName($vars['name']))
-                && (!isset($this->dept_id) || $did!=$this->getId())) {
+                && (!isset($this->id) || $did!=$this->getId())) {
             $errors['name']=__('Department already exists');
         }
 
@@ -408,8 +458,8 @@ class Dept extends VerySimpleModel {
         $this->sla_id = isset($vars['sla_id'])?$vars['sla_id']:0;
         $this->autoresp_email_id = isset($vars['autoresp_email_id'])?$vars['autoresp_email_id']:0;
         $this->manager_id = $vars['manager_id']?$vars['manager_id']:0;
-        $this->dept_name = Format::striptags($vars['name']);
-        $this->dept_signature = Format::sanitize($vars['signature']);
+        $this->name = Format::striptags($vars['name']);
+        $this->signature = Format::sanitize($vars['signature']);
         $this->group_membership = $vars['group_membership'];
         $this->ticket_auto_response = isset($vars['ticket_auto_response'])?$vars['ticket_auto_response']:1;
         $this->message_auto_response = isset($vars['message_auto_response'])?$vars['message_auto_response']:1;
@@ -417,7 +467,7 @@ class Dept extends VerySimpleModel {
         if ($this->save())
             return $this->updateSettings($vars);
 
-        if (isset($this->dept_id))
+        if (isset($this->id))
             $errors['err']=sprintf(__('Unable to update %s.'), __('this department'))
                .' '.__('Internal error occurred');
         else
@@ -435,10 +485,13 @@ class GroupDeptAccess extends VerySimpleModel {
         'pk' => array('dept_id', 'group_id'),
         'joins' => array(
             'dept' => array(
-                'constraint' => array('dept_id' => 'Dept.dept_id'),
+                'constraint' => array('dept_id' => 'Dept.id'),
             ),
             'group' => array(
-                'constraint' => array('group_id' => 'Group.group_id'),
+                'constraint' => array('group_id' => 'Group.id'),
+            ),
+            'role' => array(
+                'constraint' => array('role_id' => 'Role.id'),
             ),
         ),
     );
diff --git a/include/class.email.php b/include/class.email.php
index d38806f75f668c558872f2035208edeab924f2b2..53a966d71e23e8f5f6f5d01c09b570247c1d3948 100644
--- a/include/class.email.php
+++ b/include/class.email.php
@@ -11,10 +11,41 @@
 
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
-
 include_once(INCLUDE_DIR.'class.dept.php');
 include_once(INCLUDE_DIR.'class.mailfetch.php');
 
+class EmailModel extends VerySimpleModel {
+    static $meta = array(
+        'table' => EMAIL_TABLE,
+        'pk' => array('email_id'),
+        'joins' => array(
+            'priority' => array(
+                'constraint' => array('priority_id' => 'Priority.priority_id'),
+                'null' => true,
+            ),
+            'dept' => array(
+                'constraint' => array('dept_id' => 'Dept.id'),
+                'null' => true,
+            ),
+            'topic' => array(
+                'constraint' => array('topic_id' => 'Topic.topic_id'),
+                'null' => true,
+            ),
+        )
+    );
+
+    function getId() {
+        return $this->email_id;
+    }
+
+    function __toString() {
+        if ($this->name)
+            return sprintf('%s <%s>', $this->name, $this->email);
+
+        return $this->email;
+    }
+}
+
 class Email {
     var $id;
     var $address;
diff --git a/include/class.format.php b/include/class.format.php
index 186a02d0e4016766d3d94ab3d5b0ff1572f0f85f..e6761abfea7a62b8f7b19487f202f10f6e19dad4 100644
--- a/include/class.format.php
+++ b/include/class.format.php
@@ -513,7 +513,7 @@ class Format {
         $format = self::getStrftimeFormat($format);
         // TODO: Properly convert to local time
         $time = DateTime::createFromFormat('U', $timestamp, new DateTimeZone('UTC'));
-        $time->setTimeZone(new DateTimeZone($cfg->getTimeZone()));
+        $time->setTimeZone(new DateTimeZone($cfg->getTimezone()));
         $timestamp = $time->getTimestamp();
         return strftime($format ?: $strftimeFallback, $timestamp);
     }
@@ -547,7 +547,7 @@ class Format {
         return self::__formatDate($timestamp,
             $format ?: $cfg->getTimeFormat(), $fromDb,
             IntlDateFormatter::NONE, IntlDateFormatter::SHORT,
-            '%x', $timezone ?: $cfg->getTimeZone());
+            '%x', $timezone ?: $cfg->getTimezone());
     }
 
     function date($timestamp, $fromDb=true, $format=false, $timezone=false) {
@@ -556,25 +556,25 @@ class Format {
         return self::__formatDate($timestamp,
             $format ?: $cfg->getDateFormat(), $fromDb,
             IntlDateFormatter::SHORT, IntlDateFormatter::NONE,
-            '%X', $timezone ?: $cfg->getTimeZone());
+            '%X', $timezone ?: $cfg->getTimezone());
     }
 
     function datetime($timestamp, $fromDb=true, $timezone=false) {
         global $cfg;
 
         return self::__formatDate($timestamp,
-            $format ?: $cfg->getDateTimeFormat(), $fromDb,
-            IntlDateFormatter::SHORT, IntlDateFormatter::SHORT,
-            '%X %x', $timezone ?: $cfg->getTimeZone());
+                $cfg->getDateTimeFormat(), $fromDb,
+                IntlDateFormatter::SHORT, IntlDateFormatter::SHORT,
+                '%X %x', $timezone ?: $cfg->getTimezone());
     }
 
     function daydatetime($timestamp, $fromDb=true, $timezone=false) {
         global $cfg;
 
         return self::__formatDate($timestamp,
-            $format ?: $cfg->getDayDateTimeFormat(), $fromDb,
-            IntlDateFormatter::FULL, IntlDateFormatter::SHORT,
-            '%X %x', $timezone ?: $cfg->getTimeZone());
+                $cfg->getDayDateTimeFormat(), $fromDb,
+                IntlDateFormatter::FULL, IntlDateFormatter::SHORT,
+                '%X %x', $timezone ?: $cfg->getTimezone());
     }
 
     function getStrftimeFormat($format) {
@@ -742,7 +742,7 @@ class Format {
         if (class_exists('IntlBreakIterator')) {
             // Split by word boundaries
             if ($tokenizer = IntlBreakIterator::createWordInstance(
-                    $lang ?: ($cfg ? $cfg->getSystemLanguage() : 'en_US'))
+                    $lang ?: ($cfg ? $cfg->getPrimaryLanguage() : 'en_US'))
             ) {
                 $tokenizer->setText($text);
                 $tokens = array();
diff --git a/include/class.group.php b/include/class.group.php
index 6352289749de218a7841c40fa1cc4f8daf966d37..3bbf296f5ecbfaf2173311ac1f23a4124b31746b 100644
--- a/include/class.group.php
+++ b/include/class.group.php
@@ -18,37 +18,79 @@ class Group extends VerySimpleModel {
 
     static $meta = array(
         'table' => GROUP_TABLE,
-        'pk' => array('group_id'),
+        'pk' => array('id'),
+        'joins' => array(
+            'members' => array(
+                'null' => true,
+                'list' => true,
+                'reverse' => 'Staff.group',
+            ),
+            'depts' => array(
+                'null' => true,
+                'list' => true,
+                'reverse' => 'GroupDeptAccess.group',
+            ),
+            'role' => array(
+                'constraint' => array('role_id' => 'Role.id')
+            ),
+        ),
     );
 
-    var $members;
+    const FLAG_ENABLED = 0X0001;
+
     var $departments;
 
     function getHashtable() {
         $base = $this->ht;
-        $base['name'] = $base['group_name'];
-        $base['isactive'] = $base['group_enabled'];
+        $base['name'] = $base['name'];
+        $base['isactive'] = $base['flags'];
         return $base;
     }
 
-    function getInfo(){
+    function getInfo() {
         return $this->getHashtable();
     }
 
-    function getId(){
-        return $this->group_id;
+    function getId() {
+        return $this->id;
+    }
+
+    function getRoleId() {
+        return $this->role_id;
+    }
+
+    function getRole($deptId=0) {
+
+        if ($deptId // Department specific role.
+                && ($roles=$this->getDepartmentsAccess())
+                && isset($roles[$deptId])
+                && $roles[$deptId]
+                && ($role=Role::lookup($roles[$deptId]))
+                && $role->isEnabled())
+            return $role;
+
+        // Default role for this group.
+        return $this->role;
+    }
+
+    function getName() {
+        return $this->name;
     }
 
-    function getName(){
-        return $this->group_name;
+    function getCreateDate() {
+        return $this->created;
     }
 
-    function getNumUsers(){
-        return Staff::objects()->filter(array('group_id'=>$this->getId()))->count();
+    function getUpdateDate() {
+        return $this->updated;
     }
 
-    function isEnabled(){
-        return $this->group_enabled;
+    function getNumMembers() {
+        return $this->members ? $this->members->count() : 0;
+     }
+
+    function isEnabled() {
+        $this->get('flags') & self::FLAG_ENABLED !== 0;
     }
 
     function isActive(){
@@ -81,34 +123,47 @@ class Group extends VerySimpleModel {
         return $this->members;
     }
 
-    //Get departments the group is allowed to access.
+    //Get departments & roles the group is allowed to access.
     function getDepartments() {
+        return array_keys($this->getDepartmentsAccess());
+    }
+
+    function getDepartmentsAccess() {
         if (!isset($this->departments)) {
             $this->departments = array();
             foreach (GroupDeptAccess::objects()
                 ->filter(array('group_id'=>$this->getId()))
-                ->values_flat('dept_id') as $gda
+                ->values_flat('dept_id', 'role_id') as $gda
             ) {
-                $this->departments[] = $gda[0];
+                $this->departments[$gda[0]] = $gda[1];
             }
         }
+
         return $this->departments;
     }
 
-
-    function updateDeptAccess($dept_ids) {
+    function updateDeptAccess($dept_ids, $vars=array()) {
         if (is_array($dept_ids)) {
             $groups = GroupDeptAccess::objects()
                 ->filter(array('group_id' => $this->getId()));
             foreach ($groups as $group) {
-                if ($idx = array_search($group->dept_id, $dept_ids))
+                if ($idx = array_search($group->dept_id, $dept_ids)) {
                     unset($dept_ids[$idx]);
-                else
+                    $roleId = $vars['dept'.$group->dept_id.'_role_id'];
+                    if ($roleId != $group->role_id) {
+                        $group->set('role_id', $roleId ?: 0);
+                        $group->save();
+                    }
+                } else {
                     $group->delete();
+                }
             }
             foreach ($dept_ids as $id) {
+                $roleId = $vars['dept'.$id.'_role_id'];
                 GroupDeptAccess::create(array(
-                    'group_id'=>$this->getId(), 'dept_id'=>$id
+                    'group_id' => $this->getId(),
+                    'dept_id' => $id,
+                    'role_id' => $roleId ?: 0
                 ))->save();
             }
         }
@@ -118,7 +173,7 @@ class Group extends VerySimpleModel {
     function delete() {
 
         // Can't delete with members
-        if ($this->getNumUsers())
+        if ($this->getNumMembers())
             return false;
 
         if (!parent::delete())
@@ -132,36 +187,8 @@ class Group extends VerySimpleModel {
         return true;
     }
 
-    /*** Static functions ***/
-    static function getIdByName($name){
-        $id = static::objects()->filter(array('group_name'=>trim($name)))
-            ->values_flat('group_id')->first();
-
-        return $id ? $id[0] : 0;
-    }
-
-    static function getGroupNames($localize=true) {
-        static $groups=array();
-
-        if (!$groups) {
-            $query = static::objects()
-                ->values_flat('group_id', 'group_name', 'group_enabled')
-                ->order_by('group_name');
-            foreach ($query as $row) {
-                list($id, $name, $enabled) = $row;
-                $groups[$id] = sprintf('%s%s',
-                    self::getLocalById($id, 'name', $name),
-                    $enabled ? '' : ' ' . __('(disabled)'));
-            }
-        }
-        // TODO: Sort groups if $localize
-        return $groups;
-    }
-
-    static function create($vars=false) {
-        $group = parent::create($vars);
-        $group->created = SqlFunction::NOW();
-        return $group;
+    function __toString() {
+        return $this->getName();
     }
 
     function save($refetch=false) {
@@ -172,40 +199,37 @@ class Group extends VerySimpleModel {
     }
 
     function update($vars,&$errors) {
-        if (isset($this->group_id) && $this->getId() != $vars['id'])
-            $errors['err']=__('Missing or invalid group ID');
+        if (isset($this->id) && $this->getId() != $vars['id'])
+            $errors['err'] = __('Missing or invalid group ID');
 
         if (!$vars['name']) {
-            $errors['name']=__('Group name required');
+            $errors['name'] = __('Group name required');
         } elseif(strlen($vars['name'])<3) {
-            $errors['name']=__('Group name must be at least 3 chars.');
+            $errors['name'] = __('Group name must be at least 3 chars.');
         } elseif (($gid=static::getIdByName($vars['name']))
-                && (!isset($this->group_id) || $gid!=$this->getId())) {
-            $errors['name']=__('Group name already exists');
+                && (!isset($this->id) || $gid!=$this->getId())) {
+            $errors['name'] = __('Group name already exists');
         }
 
+        if (!$vars['role_id'])
+            $errors['role_id'] = __('Role selection required');
+
         if ($errors)
             return false;
 
-        $this->group_name=Format::striptags($vars['name']);
-        $this->group_enabled=$vars['isactive'];
-        $this->can_create_tickets=$vars['can_create_tickets'];
-        $this->can_delete_tickets=$vars['can_delete_tickets'];
-        $this->can_edit_tickets=$vars['can_edit_tickets'];
-        $this->can_assign_tickets=$vars['can_assign_tickets'];
-        $this->can_transfer_tickets=$vars['can_transfer_tickets'];
-        $this->can_close_tickets=$vars['can_close_tickets'];
-        $this->can_ban_emails=$vars['can_ban_emails'];
-        $this->can_manage_premade=$vars['can_manage_premade'];
-        $this->can_manage_faq=$vars['can_manage_faq'];
-        $this->can_post_ticket_reply=$vars['can_post_ticket_reply'];
-        $this->can_view_staff_stats=$vars['can_view_staff_stats'];
-        $this->notes=Format::sanitize($vars['notes']);
+        $this->name = Format::striptags($vars['name']);
+        $this->role_id = $vars['role_id'];
+        $this->notes = Format::sanitize($vars['notes']);
+
+        if ($vars['isactive'])
+            $this->flags = ($this->flags | self::FLAG_ENABLED);
+        else
+            $this->flags =  ($this->flags & ~self::FLAG_ENABLED);
 
         if ($this->save())
-            return $this->updateDeptAccess($vars['depts'] ?: array());
+            return $this->updateDeptAccess($vars['depts'] ?: array(), $vars);
 
-        if (isset($this->group_id)) {
+        if (isset($this->id)) {
             $errors['err']=sprintf(__('Unable to update %s.'), __('this group'))
                .' '.__('Internal error occurred');
         }
@@ -215,5 +239,72 @@ class Group extends VerySimpleModel {
         }
         return false;
     }
+
+    /*** Static functions ***/
+    static function getIdByName($name){
+        $id = static::objects()->filter(array('name'=>trim($name)))
+            ->values_flat('id')->first();
+
+        return $id ? $id[0] : 0;
+    }
+
+    static function create($vars=false) {
+        $group = parent::create($vars);
+        $group->created = SqlFunction::NOW();
+        return $group;
+    }
+
+    static function __create($vars, &$errors) {
+        $g = self::create($vars);
+        $g->save();
+        if ($vars['depts'])
+            $g->updateDeptAccess($vars['depts'], $vars);
+
+        return $g;
+    }
+
+    static function getGroups($criteria=array()) {
+        static $groups = null;
+        if (!isset($groups) || $criteria) {
+            $groups = array();
+            $query = static::objects()
+                ->values_flat('id', 'name', 'flags')
+                ->order_by('name');
+
+            $filters = array();
+            if (isset($criteria['active']))
+                $filters += array(
+                        'isactive' => $criteria['active'] ? 1 : 0);
+
+            if ($filters)
+                $query->filter($filters);
+
+            $names = array();
+            foreach ($query as $row) {
+                list($id, $name, $flags) = $row;
+                $names[$id] = sprintf('%s%s',
+                    self::getLocalById($id, 'name', $name),
+                    $flags ? '' : ' ' . __('(disabled)'));
+            }
+
+            //TODO: sort if $criteria['localize'];
+            if ($criteria)
+                return $names;
+
+            $groups = $names;
+        }
+
+        return $groups;
+    }
+
+    static function getActiveGroups() {
+        static $groups = null;
+
+        if (!isset($groups))
+            $groups = self::getGroups(array('active'=>true));
+
+        return $groups;
+    }
+
 }
 ?>
diff --git a/include/class.i18n.php b/include/class.i18n.php
index d5d524a9782d7d6ecd97d2e912937a5318476a0d..4b764717a82cdc7a51651f7f6eb3bc6f11c8d333 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -49,21 +49,23 @@ class Internationalization {
     function loadDefaultData() {
         # notrans -- do not translate the contents of this array
         $models = array(
-            'department.yaml' =>    'Dept::create',
-            'sla.yaml' =>           'SLA::create',
+            'department.yaml' =>    'Dept::__create',
+            'sla.yaml' =>           'SLA::__create',
             'form.yaml' =>          'DynamicForm::create',
             'list.yaml' =>          'DynamicList::create',
             // Note that department, sla, and forms are required for
             // help_topic
             'help_topic.yaml' =>    'Topic::__create',
             'filter.yaml' =>        'Filter::create',
-            'team.yaml' =>          'Team::create',
+            'team.yaml' =>          'Team::__create',
             // Organization
             'organization.yaml' =>  'Organization::__create',
             // Ticket
-            'ticket_status.yaml' =>  'TicketStatus::__create',
+            'ticket_status.yaml' => 'TicketStatus::__create',
+            // Role
+            'role.yaml' =>          'Role::__create',
             // Note that group requires department
-            'group.yaml' =>         'Group::create',
+            'group.yaml' =>         'Group::__create',
             'file.yaml' =>          'AttachmentFile::create',
             'sequence.yaml' =>      'Sequence::__create',
         );
@@ -384,10 +386,8 @@ class Internationalization {
             return $thisstaff->getLocale()
                 ?: self::getCurrentLanguage();
         }
-        if (!$locale)
-            $locale = $cfg->getDefaultLocale();
 
-        if (!$locale)
+        if (!($locale = $cfg->getDefaultLocale()))
             $locale = self::getCurrentLanguage();
 
         return $locale;
diff --git a/include/class.lock.php b/include/class.lock.php
index c61771e67f5a3b166734e41d8c6ee3ebd03c977c..6e0588978e29b71d125b0602388923b50edc6898 100644
--- a/include/class.lock.php
+++ b/include/class.lock.php
@@ -71,6 +71,7 @@ class TicketLock extends VerySimpleModel {
 
     //Renew existing lock.
     function renew($lockTime=0) {
+        global $cfg;
 
         if(!$lockTime || !is_numeric($lockTime)) //XXX: test to  make it works.
             $lockTime = $cfg->getLockTime();
diff --git a/include/class.nav.php b/include/class.nav.php
index bc4956f084b4b810b17d242637f38bcc365bbee8..551d6265be765983d602570863eda20733500dca 100644
--- a/include/class.nav.php
+++ b/include/class.nav.php
@@ -161,9 +161,9 @@ class StaffNav {
                 case 'kbase':
                     $subnav[]=array('desc'=>__('FAQs'),'href'=>'kb.php', 'urls'=>array('faq.php'), 'iconclass'=>'kb');
                     if($staff) {
-                        if($staff->canManageFAQ())
+                        if($staff->getRole()->canManageFAQ())
                             $subnav[]=array('desc'=>__('Categories'),'href'=>'categories.php','iconclass'=>'faq-categories');
-                        if($staff->canManageCannedResponses())
+                        if($staff->getRole()->canManageCannedResponses())
                             $subnav[]=array('desc'=>__('Canned Responses'),'href'=>'canned.php','iconclass'=>'canned');
                     }
                    break;
@@ -260,6 +260,7 @@ class AdminNav extends StaffNav{
                     $subnav[]=array('desc'=>__('Agents'),'href'=>'staff.php','iconclass'=>'users');
                     $subnav[]=array('desc'=>__('Teams'),'href'=>'teams.php','iconclass'=>'teams');
                     $subnav[]=array('desc'=>__('Groups'),'href'=>'groups.php','iconclass'=>'groups');
+                    $subnav[]=array('desc'=>__('Roles'),'href'=>'roles.php','iconclass'=>'lists');
                     $subnav[]=array('desc'=>__('Departments'),'href'=>'departments.php','iconclass'=>'departments');
                     break;
                 case 'apps':
diff --git a/include/class.orm.php b/include/class.orm.php
index f9e51f55332c15e6546cee9cec5688a29d19e10b..a1328a6fdb03e1b4c7ab8d2bd76aa30112840da2 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -491,6 +491,24 @@ class SqlFunction {
     }
 }
 
+class SqlExpr extends SqlFunction {
+    function __construct($args) {
+        $this->args = $args;
+    }
+
+    function toSql($compiler, $model=false, $alias=false) {
+        $O = array();
+        foreach ($this->args as $field=>$value) {
+            list($field, $op) = $compiler->getField($field, $model);
+            if (is_callable($op))
+                $O[] = call_user_func($op, $field, $value, $model);
+            else
+                $O[] = sprintf($op, $field, $compiler->input($value));
+        }
+        return implode(' ', $O) . ($alias ? ' AS ' . $alias : '');
+    }
+}
+
 class SqlExpression extends SqlFunction {
     var $operator;
     var $operands;
@@ -515,6 +533,10 @@ class SqlExpression extends SqlFunction {
                 $operator = '+'; break;
             case 'times':
                 $operator = '*'; break;
+            case 'bitand':
+                $operator = '&'; break;
+            case 'bitor':
+                $operator = '|'; break;
             default:
                 throw new InvalidArgumentException('Invalid operator specified');
         }
@@ -566,7 +588,7 @@ class SqlCode extends SqlFunction {
     }
 }
 
-class Aggregate extends SqlFunction {
+class SqlAggregate extends SqlFunction {
 
     var $func;
     var $expr;
@@ -590,7 +612,10 @@ class Aggregate extends SqlFunction {
     }
 
     function toSql($compiler, $model=false, $alias=false) {
-        $options = array('constraint'=>$this->constraint);
+        $options = array(
+                'constraint' => $this->constraint,
+                'distinct' => $this->distinct);
+
         list($field) = $compiler->getField($this->expr, $model, $options);
         return sprintf('%s(%s)%s', $this->func, $field,
             $alias && $this->alias ? ' AS '.$compiler->quote($this->alias) : '');
@@ -781,12 +806,12 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable {
         if (!is_array($annotations))
             $annotations = func_get_args();
         foreach ($annotations as $name=>$A) {
-            if ($A instanceof Aggregate) {
+            if ($A instanceof SqlAggregate) {
                 if (is_int($name))
                     $name = $A->getFieldName();
                 $A->setAlias($name);
-                $this->annotations[$name] = $A;
             }
+            $this->annotations[$name] = $A;
         }
         return $this;
     }
@@ -991,7 +1016,7 @@ class ModelInstanceManager extends ResultSet {
      * The annotated fields are in the AnnotatedModel instance and the
      * database-backed fields are managed by the Model instance.
      */
-    function getOrBuild($modelClass, $fields) {
+    function getOrBuild($modelClass, $fields, $cache=true) {
         // Check for NULL primary key, used with related model fetching. If
         // the PK is NULL, then consider the object to also be NULL
         foreach ($modelClass::$meta['pk'] as $pkf) {
@@ -1006,8 +1031,7 @@ class ModelInstanceManager extends ResultSet {
         // be the root model's fields. The annotated fields will be wrapped
         // using an AnnotatedModel instance.
         if ($annotations && $modelClass == $this->model) {
-            foreach ($annotations as $A) {
-                $name = $A->getAlias();
+            foreach ($annotations as $name=>$A) {
                 if (isset($fields[$name])) {
                     $extras[$name] = $fields[$name];
                     unset($fields[$name]);
@@ -1312,7 +1336,8 @@ class SqlCompiler {
 
         // Call pushJoin for each segment in the join path. A new JOIN
         // fragment will need to be emitted and/or cached
-        $push = function($p, $path, $extra=false) use (&$model) {
+        $self = $this;
+        $push = function($p, $path, $extra=false) use (&$model, $self) {
             $model::_inspect();
             if (!($info = $model::$meta['joins'][$p])) {
                 throw new OrmException(sprintf(
@@ -1322,7 +1347,7 @@ class SqlCompiler {
             $crumb = implode('__', $path);
             $path[] = $p;
             $tip = implode('__', $path);
-            $alias = $this->pushJoin($crumb, $tip, $model, $info, $extra);
+            $alias = $self->pushJoin($crumb, $tip, $model, $info, $extra);
             // Roll to foreign model
             foreach ($info['constraint'] as $local => $foreign) {
                 list($model, $f) = explode('.', $foreign);
@@ -1351,6 +1376,10 @@ class SqlCompiler {
             $field = $alias.'.'.$this->quote($field);
         else
             $field = $this->quote($field);
+
+        if (isset($options['distinct']) && $options['distinct'])
+            $field = " DISTINCT $field";
+
         if (isset($options['model']) && $options['model'])
             $operator = $model;
         return array($field, $operator);
@@ -1775,6 +1804,9 @@ class MySqlCompiler extends SqlCompiler {
                 }
                 // TODO: Throw exception if $field can be indentified as
                 //       invalid
+                if ($field instanceof SqlFunction)
+                    $field = $field->toSql($this, $model);
+
                 $orders[] = $field.' '.$dir;
             }
             $sort = ' ORDER BY '.implode(', ', $orders);
@@ -1857,10 +1889,11 @@ class MySqlCompiler extends SqlCompiler {
             }
         }
         $fields = array_keys($fields);
+        $group_by = array();
         // Add in annotations
         if ($queryset->annotations) {
-            foreach ($queryset->annotations as $A) {
-                $fields[] = $T = $A->toSql($this, $model, true);
+            foreach ($queryset->annotations as $alias=>$A) {
+                $fields[] = $T = $A->toSql($this, $model, $alias);
                 // TODO: Add to last fieldset in fieldMap
                 if ($fieldMap)
                     $fieldMap[0][0][] = $A->getAlias();
diff --git a/include/class.priority.php b/include/class.priority.php
index 67bb0d8e33c87ea1a6e1a17bf9c16ba130c7c21e..ac6976d29bb53517b41ee82d5dc4bf536e124bf0 100644
--- a/include/class.priority.php
+++ b/include/class.priority.php
@@ -59,8 +59,7 @@ class Priority extends VerySimpleModel {
             $objects->filter(array('ispublic'=>1));
 
         foreach ($objects as $row) {
-            list($id, $name) = $row;
-            $priorities[$id] = $name;
+            $priorities[$row[0]] = $row[1];
         }
 
         return $priorities;
diff --git a/include/class.role.php b/include/class.role.php
new file mode 100644
index 0000000000000000000000000000000000000000..36ea3f9a5a74eac80e077ef46221b6e300f8c9b3
--- /dev/null
+++ b/include/class.role.php
@@ -0,0 +1,348 @@
+<?php
+/*********************************************************************
+    class.role.php
+
+    Role-based access
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2014 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+class RoleModel extends VerySimpleModel {
+    static $meta = array(
+        'table' => ROLE_TABLE,
+        'pk' => array('id'),
+        'joins' => array(
+            'groups' => array(
+                'null' => true,
+                'list' => true,
+                'reverse' => 'Group.role',
+            ),
+        ),
+    );
+
+    // Flags
+    const FLAG_ENABLED   = 0x0001;
+
+    protected function hasFlag($flag) {
+        return ($this->get('flags') & $flag) !== 0;
+    }
+
+    protected function clearFlag($flag) {
+        return $this->set('flags', $this->get('flags') & ~$flag);
+    }
+
+    protected function setFlag($flag) {
+        return $this->set('flags', $this->get('flags') | $flag);
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function getName() {
+        return $this->name;
+    }
+
+    function getCreateDate() {
+        return $this->created;
+    }
+
+    function getUpdateDate() {
+        return $this->updated;
+    }
+
+    function getInfo() {
+        return $this->ht;
+    }
+
+    function isEnabled() {
+        return $this->hasFlag(self::FLAG_ENABLED);
+    }
+
+    function isDeleteable() {
+        return ($this->groups->count() == 0);
+    }
+
+}
+
+class Role extends RoleModel {
+    var $form;
+    var $entry;
+
+    var $_perm;
+
+    function getPermission() {
+        if (!$this->_perm)
+            $this->_perm = new RolePermission('role.'.$this->getId());
+
+        return $this->_perm;
+    }
+
+    function getPermissionInfo() {
+        return $this->getPermission()->getInfo();
+    }
+
+    function to_json() {
+
+        $info = array(
+                'id'    => $this->getId(),
+                'name'  => $this->getName()
+                );
+
+        return JsonDataEncoder::encode($info);
+    }
+
+    function __toString() {
+        return (string) $this->getName();
+    }
+
+    function __call($what, $args) {
+        $rv = null;
+        if($this->getPermission() && is_callable(array($this->_perm, $what)))
+            $rv = $args
+                ? call_user_func_array(array($this->_perm, $what), $args)
+                : call_user_func(array($this->_perm, $what));
+
+        return $rv;
+    }
+
+    private function updatePerms($vars, &$errors=array()) {
+
+        $config = array();
+        foreach (RolePermission::allPermissions() as $g => $perms) {
+            foreach($perms as $k => $v)
+                $config[$k] = in_array($k, $vars) ? 1 : 0;
+        }
+
+        $this->getPermission()->updateAll($config);
+        $this->getPermission()->load();
+    }
+
+    function update($vars, &$errors) {
+
+        if (!$vars['name'])
+            $errors['name'] = __('Name required');
+        elseif (($r=Role::lookup(array('name'=>$vars['name'])))
+                && $r->getId() != $vars['id'])
+            $errors['name'] = __('Name already in-use');
+        elseif (!$vars['perms'] || !count($vars['perms']))
+            $errors['err'] = __('Must check at least one permission for the role');
+
+        if ($errors)
+            return false;
+
+        $this->name = $vars['name'];
+        $this->notes = $vars['notes'];
+        if (!$this->save(true))
+            return false;
+
+        $this->updatePerms($vars['perms'], $errors);
+
+        return true;
+    }
+
+    function save($refetch=false) {
+        if (count($this->dirty))
+            $this->set('updated', new SqlFunction('NOW'));
+        if (isset($this->dirty['notes']))
+            $this->notes = Format::sanitize($this->notes);
+
+        return parent::save($refetch | $this->dirty);
+    }
+
+    function delete() {
+
+        if (!$this->isDeleteable())
+            return false;
+
+        if (!parent::delete())
+            return false;
+
+        // Remove dept access entries
+        GroupDeptAccess::objects()
+            ->filter(array('role_id'=>$this->getId()))
+            ->update(array('role_id' => 0));
+
+        // Delete permission settings
+         $this->getPermission()->destroy();
+
+        return true;
+    }
+
+    static function create($vars=false) {
+        $role = parent::create($vars);
+        $role->created = SqlFunction::NOW();
+        return $role;
+    }
+
+    static function __create($vars, &$errors) {
+        $role = self::create($vars);
+        $role->save();
+        if ($vars['permissions'])
+            $role->updatePerms($vars['permissions']);
+
+        return $role;
+    }
+
+    static function getRoles($criteria=null) {
+        static $roles = null;
+
+        if (!isset($roles) || $criteria) {
+
+            $filters = array();
+            if (isset($criteria['enabled'])) {
+                if ($criteria['enabled'])
+                    $filters += array(
+                            'flags__hasbit' => self::FLAG_ENABLED);
+                else
+                    $filters [] = Q::not(array(
+                                'flags__hasbit' => self::FLAG_ENABLED));
+            }
+            $query = self::objects()
+                ->order_by('name')
+                ->values_flat('id', 'name');
+
+            if ($filters)
+                $query->filter($filters);
+
+            $names = array();
+            foreach ($query as $row)
+                $names[$row[0]] = $row[1];
+
+            // TODO: Localize
+
+            if ($criteria) return $names;
+
+            $roles = $names;
+        }
+
+        return $roles;
+    }
+
+    static function getActiveRoles() {
+        static $roles = null;
+
+        if (!isset($roles))
+            $roles = self::getRoles(array('enabled' => true));
+
+        return $roles;
+    }
+}
+
+
+class RolePermission extends Config {
+
+    static $_permissions = array(
+            /* @trans */ 'Tickets' => array(
+                'ticket.create'  => array(
+                    /* @trans */ 'Create',
+                    /* @trans */ 'Ability to open tickets on behalf of users'),
+                'ticket.edit'   => array(
+                    /* @trans */ 'Edit',
+                    /* @trans */ 'Ability to edit tickets'),
+                'ticket.assign'   => array(
+                    /* @trans */ 'Assign',
+                    /* @trans */ 'Ability to assign tickets to agents or teams'),
+                'ticket.transfer'   => array(
+                    /* @trans */ 'Transfer',
+                    /* @trans */ 'Ability to transfer tickets between departments'),
+                'ticket.reply'  => array(
+                    /* @trans */ 'Post Reply',
+                    /* @trans */ 'Ability to post a ticket reply'),
+                'ticket.close'   => array(
+                    /* @trans */ 'Close',
+                    /* @trans */ 'Ability to close tickets'),
+                'ticket.delete'   => array(
+                    /* @trans */ 'Delete',
+                    /* @trans */ 'Ability to delete tickets'),
+                ),
+            /* @trans */ 'Knowledgebase' => array(
+                'kb.premade'   => array(
+                    /* @trans */ 'Premade',
+                    /* @trans */ 'Ability to add/update/disable/delete canned responses'),
+                'kb.faq'   => array(
+                    /* @trans */ 'FAQ',
+                    /* @trans */ 'Ability to add/update/disable/delete knowledgebase categories and FAQs'),
+                ),
+            /* @trans */ 'Misc.' => array(
+                'stats.agents'   => array(
+                    /* @trans */ 'Stats',
+                    /* @trans */ 'Ability to view stats of other agents in allowed departments'),
+                'emails.banlist'   => array(
+                    /* @trans */ 'Banlist',
+                    /* @trans */ 'Ability to add/remove emails from banlist via ticket interface'),
+                ),
+            );
+
+    static function allPermissions() {
+        return static::$_permissions;
+    }
+
+    function get($var) {
+        return (bool) parent::get($var);
+    }
+
+    /* tickets */
+    function canCreateTickets() {
+        return ($this->get('ticket.create'));
+    }
+
+    function canEditTickets() {
+        return ($this->get('ticket.edit'));
+    }
+
+    function canAssignTickets() {
+        return ($this->get('ticket.assign'));
+    }
+
+    function canTransferTickets() {
+        return ($this->get('ticket.transfer'));
+    }
+
+    function canPostReply() {
+        return ($this->get('ticket.reply'));
+    }
+
+    function canCloseTickets() {
+        return ($this->get('ticket.close'));
+    }
+
+    function canDeleteTickets() {
+        return ($this->get('ticket.delete'));
+    }
+
+    /* Knowledge base */
+    function canManagePremade() {
+        return ($this->get('kb.premade'));
+    }
+
+    function canManageCannedResponses() {
+        return ($this->canManagePremade());
+    }
+
+    function canManageFAQ() {
+        return ($this->get('kb.faq'));
+    }
+
+    function canManageFAQs() {
+        return ($this->canManageFAQ());
+    }
+
+    /* stats */
+    function canViewStaffStats() {
+        return ($this->get('stats.agents'));
+    }
+
+    /* email */
+    function canBanEmails() {
+        return ($this->get('emails.banlist'));
+    }
+}
+?>
diff --git a/include/class.search.php b/include/class.search.php
index 68076e619847a82a9ee9bba0736b98fe3cdd0623..ff2ccbdcac8c87a13e1f3774cd85696da9dd54be 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -317,38 +317,13 @@ class MysqlSearchBackend extends SearchBackend {
             $criteria->filter(array('ticket_id'=>new SqlCode($key)));
             $criteria->distinct('ticket_id');
             }
-            /*
-                    case 'email':
-                    case 'org_id':
-                    case 'form_id':
-                    default:
-                        if (strpos($name, 'cdata.') === 0) {
-                            // Search ticket CDATA table
-                            $cdata_search = true;
-                            $name = substr($name, 6);
-                            if (is_array($value)) {
-                                $where[] = '(' . implode(' OR ', array_map(
-                                    function($k) use ($name) {
-                                        return sprintf('FIND_IN_SET(%s, cdata.`%s`)',
-                                            db_input($k), $name);
-                                    }, $value)
-                                ) . ')';
-                            }
-                            else {
-                                $where[] = sprintf("cdata.%s = %s", $name, db_input($value));
-                            }
-                        }
-                    }
-                }
-             */
-
             // TODO: Consider sorting preferences
         }
 
         // TODO: Ensure search table exists;
         if (false) {
-            // Create the search table automatically
-            $class::createSearchTable();
+            // TODO: Create the search table automatically
+            // $class::createSearchTable();
         }
         return $criteria;
     }
diff --git a/include/class.sla.php b/include/class.sla.php
index 490508660f54d2e1303bf391d48a8082eb758e05..083663695f4d245710bc6742b4650bc8fedad9b0 100644
--- a/include/class.sla.php
+++ b/include/class.sla.php
@@ -3,7 +3,6 @@
     class.sla.php
 
     SLA
-
     Peter Rotich <peter@osticket.com>
     Copyright (c)  2006-2013 osTicket
     http://www.osticket.com
@@ -14,75 +13,54 @@
     vim: expandtab sw=4 ts=4 sts=4:
 **********************************************************************/
 
-class SlaModel extends VerySimpleModel {
+class SLA extends VerySimpleModel {
+
     static $meta = array(
         'table' => SLA_TABLE,
-        'pk' => array('sla_id'),
+        'pk' => array('id'),
     );
-}
-
-class SLA {
-
-    var $id;
-
-    var $info;
-    var $config;
-
-    function SLA($id) {
-        $this->id=0;
-        $this->load($id);
-    }
 
-    function load($id=0) {
+    //TODO: Use flags
 
-        if(!$id && !($id=$this->getId()))
-            return false;
-
-        $sql='SELECT * FROM '.SLA_TABLE.' WHERE id='.db_input($id);
-        if(!($res=db_query($sql)) || !db_num_rows($res))
-            return false;
-
-        $this->ht=db_fetch_array($res);
-        $this->id=$this->ht['id'];
-        return true;
-    }
-
-    function reload() {
-        return $this->load();
-    }
+    var $_config;
 
     function getId() {
         return $this->id;
     }
 
     function getName() {
-        return $this->ht['name'];
+        return $this->name;
     }
 
     function getGracePeriod() {
-        return $this->ht['grace_period'];
-    }
-
-    function getNotes() {
-        return $this->ht['notes'];
+        return $this->grace_period;
     }
 
     function getHashtable() {
-        return array_merge($this->getConfig()->getInfo(), $this->ht);
+        $this->getHashtable();
     }
 
     function getInfo() {
-        return $this->getHashtable();
+        return array_merge($this->getConfig()->getInfo(), $this->ht);
     }
 
     function getConfig() {
-        if (!isset($this->config))
-            $this->config = new SlaConfig($this->getId());
-        return $this->config;
+        if (!isset($this->_config))
+            $this->_config = new SlaConfig($this->getId());
+
+        return $this->_config;
+    }
+
+    function getCreateDate() {
+        return $this->created;
+    }
+
+    function getUpdateDate() {
+        return $this->updated;
     }
 
     function isActive() {
-        return ($this->ht['isactive']);
+        return ($this->isactive);
     }
 
     function isTransient() {
@@ -90,7 +68,7 @@ class SLA {
     }
 
     function sendAlerts() {
-        return (!$this->ht['disable_overdue_alerts']);
+        return $this->disable_overdue_alerts;
     }
 
     function alertOnOverdue() {
@@ -98,32 +76,68 @@ class SLA {
     }
 
     function priorityEscalation() {
-        return ($this->ht['enable_priority_escalation']);
+        return ($this->enable_priority_escalation);
     }
 
     function getTranslateTag($subtag) {
-        return _H(sprintf('sla.%s.%s', $subtag, $this->id));
+        return _H(sprintf('sla.%s.%s', $subtag, $this->getId()));
     }
+
     function getLocal($subtag) {
         $tag = $this->getTranslateTag($subtag);
         $T = CustomDataTranslation::translate($tag);
         return $T != $tag ? $T : $this->ht[$subtag];
     }
+
     static function getLocalById($id, $subtag, $default) {
         $tag = _H(sprintf('sla.%s.%s', $subtag, $id));
         $T = CustomDataTranslation::translate($tag);
         return $T != $tag ? $T : $default;
     }
 
-    function update($vars,&$errors) {
+    function update($vars, &$errors) {
+
+        if (!$vars['grace_period'])
+            $errors['grace_period'] = __('Grace period required');
+        elseif (!is_numeric($vars['grace_period']))
+            $errors['grace_period'] = __('Numeric value required (in hours)');
 
-        if(!SLA::save($this->getId(),$vars,$errors))
+        if (!$vars['name'])
+            $errors['name'] = __('Name is required');
+        elseif (($sid=SLA::getIdByName($vars['name'])) && $sid!=$vars['id'])
+            $errors['name'] = __('Name already exists');
+
+        if ($errors)
             return false;
 
-        $this->reload();
-        $this->getConfig()->set('transient', isset($vars['transient']) ? 1 : 0);
+        $this->isactive = $vars['isactive'];
+        $this->name = $vars['name'];
+        $this->grace_period = $vars['grace_period'];
+        $this->disable_overdue_alerts = isset($vars['disable_overdue_alerts']) ? 1 : 0;
+        $this->enable_priority_escalation = isset($vars['enable_priority_escalation'])? 1: 0;
+        $this->notes = Format::sanitize($vars['notes']);
+
+        if ($this->save()) {
+            $this->getConfig()->set('transient', isset($vars['transient']) ? 1 : 0);
+            return true;
+        }
 
-        return true;
+        if (isset($this->id)) {
+            $errors['err']=sprintf(__('Unable to update %s.'), __('this SLA plan'))
+               .' '.__('Internal error occurred');
+        } else {
+            $errors['err']=sprintf(__('Unable to add %s.'), __('this SLA plan'))
+               .' '.__('Internal error occurred');
+        }
+
+        return false;
+    }
+
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+
+        return parent::save($refetch || $this->dirty);
     }
 
     function delete() {
@@ -132,6 +146,7 @@ class SLA {
         if(!$cfg || $cfg->getDefaultSLAId()==$this->getId())
             return false;
 
+        //TODO: Use ORM to delete & update
         $id=$this->getId();
         $sql='DELETE FROM '.SLA_TABLE.' WHERE id='.db_input($id).' LIMIT 1';
         if(db_query($sql) && ($num=db_affected_rows())) {
@@ -144,86 +159,43 @@ class SLA {
     }
 
     /** static functions **/
-    function create($vars,&$errors) {
-        if (($id = SLA::save(0,$vars,$errors)) && ($sla = self::lookup($id)))
-            $sla->getConfig()->set('transient',
-                isset($vars['transient']) ? 1 : 0);
-        return $id;
-    }
-
-    function getSLAs() {
+    static function getSLAs($criteria=array()) {
 
-        $slas=array();
+       $slas = self::objects()
+           ->order_by('name')
+           ->values_flat('id', 'name', 'isactive', 'grace_period');
 
-        $sql='SELECT id, name, isactive, grace_period FROM '.SLA_TABLE.' ORDER BY name';
-        if(($res=db_query($sql)) && db_num_rows($res)) {
-            while($row=db_fetch_array($res))
-                $slas[$row['id']] = sprintf(__('%s (%d hours - %s)'
+        $entries = array();
+        foreach ($slas as $row) {
+            $entries[$row[0]] = sprintf(__('%s (%d hours - %s)'
                         /* Tokens are <name> (<#> hours - <Active|Disabled>) */),
-                        self::getLocalById($row['id'], 'name', $row['name']),
-                        $row['grace_period'],
-                        $row['isactive']?__('Active'):__('Disabled'));
+                        self::getLocalById($row[0], 'name', $row[1]),
+                        $row[3],
+                        $row[2] ? __('Active') : __('Disabled'));
         }
 
-        return $slas;
+        return $entries;
     }
 
+    static function getIdByName($name) {
+        $row = static::objects()
+            ->filter(array('name'=>$name))
+            ->values_flat('id')
+            ->first();
 
-    function getIdByName($name) {
-
-        $sql='SELECT id FROM '.SLA_TABLE.' WHERE name='.db_input($name);
-        if(($res=db_query($sql)) && db_num_rows($res))
-            list($id)=db_fetch_row($res);
-
-        return $id;
+        return $row ? $row[0] : 0;
     }
 
-    function lookup($id) {
-        return ($id && is_numeric($id) && ($sla= new SLA($id)) && $sla->getId()==$id)?$sla:null;
+    static function create($vars=false, &$errors=array()) {
+        $sla = parent::create($vars);
+        $sla->created = SqlFunction::NOW();
+        return $sla;
     }
 
-    function save($id,$vars,&$errors) {
-
-        if(!$vars['grace_period'])
-            $errors['grace_period']=__('Grace period required');
-        elseif(!is_numeric($vars['grace_period']))
-            $errors['grace_period']=__('Numeric value required (in hours)');
-
-        if(!$vars['name'])
-            $errors['name']=__('Name is required');
-        elseif(($sid=SLA::getIdByName($vars['name'])) && $sid!=$id)
-            $errors['name']=__('Name already exists');
-
-        if($errors) return false;
-
-        $sql=' updated=NOW() '.
-             ',isactive='.db_input($vars['isactive']).
-             ',name='.db_input($vars['name']).
-             ',grace_period='.db_input($vars['grace_period']).
-             ',disable_overdue_alerts='.db_input(isset($vars['disable_overdue_alerts'])?1:0).
-             ',enable_priority_escalation='.db_input(isset($vars['enable_priority_escalation'])?1:0).
-             ',notes='.db_input(Format::sanitize($vars['notes']));
-
-        if($id) {
-            $sql='UPDATE '.SLA_TABLE.' SET '.$sql.' WHERE id='.db_input($id);
-            if(db_query($sql))
-                return true;
-
-            $errors['err']=sprintf(__('Unable to update %s.'), __('this SLA plan'))
-               .' '.__('Internal error occurred');
-        }else{
-            if (isset($vars['id']))
-                $sql .= ', id='.db_input($vars['id']);
-
-            $sql='INSERT INTO '.SLA_TABLE.' SET '.$sql.',created=NOW() ';
-            if(db_query($sql) && ($id=db_insert_id()))
-                return $id;
-
-            $errors['err']=sprintf(__('Unable to add %s.'), __('this SLA plan'))
-               .' '.__('Internal error occurred');
-        }
-
-        return false;
+    static function __create($vars, &$errors=array()) {
+        $sla = self::create($vars);
+        $sla->save();
+        return $sla;
     }
 }
 
@@ -231,8 +203,8 @@ require_once(INCLUDE_DIR.'class.config.php');
 class SlaConfig extends Config {
     var $table = CONFIG_TABLE;
 
-    function SlaConfig($id) {
-        parent::Config("sla.$id");
+    function __construct($id) {
+        parent::__construct("sla.$id");
     }
 }
 ?>
diff --git a/include/class.staff.php b/include/class.staff.php
index 57b35bd02a22ef325a5ab4f6352cb627a4425a86..63b87d9884d3e1b532c5b1fe3dbca171552d3a57 100644
--- a/include/class.staff.php
+++ b/include/class.staff.php
@@ -17,6 +17,7 @@ include_once(INCLUDE_DIR.'class.ticket.php');
 include_once(INCLUDE_DIR.'class.dept.php');
 include_once(INCLUDE_DIR.'class.error.php');
 include_once(INCLUDE_DIR.'class.team.php');
+include_once(INCLUDE_DIR.'class.role.php');
 include_once(INCLUDE_DIR.'class.group.php');
 include_once(INCLUDE_DIR.'class.passwd.php');
 include_once(INCLUDE_DIR.'class.user.php');
@@ -31,24 +32,27 @@ implements AuthenticatedUser {
         'select_related' => array('group'),
         'joins' => array(
             'dept' => array(
-                'constraint' => array('dept_id' => 'Dept.dept_id'),
+                'constraint' => array('dept_id' => 'Dept.id'),
             ),
             'group' => array(
-                'constraint' => array('group_id' => 'Group.group_id'),
+                'constraint' => array('group_id' => 'Group.id'),
             ),
             'teams' => array(
-                'constraint' => array('staff_id' => 'StaffTeamMember.staff_id'),
+                'null' => true,
+                'list' => true,
+                'reverse' => 'TeamMember.staff',
             ),
         ),
     );
 
     var $authkey;
     var $departments;
-    var $teams;
     var $timezone;
     var $stats = array();
     var $_extra;
     var $passwd_change;
+    var $_roles = null;
+    var $_teams = null;
 
     function __onload() {
         // WE have to patch info here to support upgrading from old versions.
@@ -76,7 +80,7 @@ implements AuthenticatedUser {
 
     // AuthenticatedUser implementation...
     // TODO: Move to an abstract class that extends Staff
-    function getRole() {
+    function getUserType() {
         return 'staff';
     }
 
@@ -200,28 +204,30 @@ implements AuthenticatedUser {
 
     function getDepartments() {
 
-        if (isset($this->departments))
-            return $this->departments;
-
-        // Departments the staff is "allowed" to access...
-        // based on the group they belong to + user's primary dept + user's managed depts.
-        $dept_ids = array();
-        $depts = Dept::objects()
-            ->filter(Q::any(array(
-                'dept_id' => $this->dept_id,
-                'groups__group_id' => $this->group_id,
-                'manager_id' => $this->getId(),
-            )))
-            ->values_flat('dept_id');
-
-        foreach ($depts as $row) {
-            list($id) = $row;
-            $dept_ids[] = $id;
-        }
-        if (!$dept_ids) { //Neptune help us! (fallback)
-            $dept_ids = array_merge($this->getGroup()->getDepartments(), array($this->getDeptId()));
+        if (!isset($this->departments)) {
+
+            // Departments the staff is "allowed" to access...
+            // based on the group they belong to + user's primary dept + user's managed depts.
+            $dept_ids = array();
+            $depts = Dept::objects()
+                ->filter(Q::any(array(
+                    'id' => $this->dept_id,
+                    'groups__group_id' => $this->group_id,
+                    'manager_id' => $this->getId(),
+                )))
+                ->values_flat('id');
+
+            foreach ($depts as $row)
+                $dept_ids[] = $row[0];
+
+            if (!$dept_ids) { //Neptune help us! (fallback)
+                $dept_ids = array_merge($this->getGroup()->getDepartments(), array($this->getDeptId()));
+            }
+
+            $this->departments = array_filter(array_unique($dept_ids));
         }
-        return $this->departments = array_filter(array_unique($dept_ids));
+
+        return $this->departments;
     }
 
     function getDepts() {
@@ -263,6 +269,57 @@ implements AuthenticatedUser {
         return $this->locale;
     }
 
+    function getRole($dept=null) {
+
+        if ($dept) {
+            $deptId = is_object($dept) ? $dept->getId() : $dept;
+            if (isset($this->_roles[$deptId]))
+                return $this->_roles[$deptId];
+
+            if (($role=$this->group->getRole($deptId)))
+                return $this->_roles[$deptId] = $role;
+        }
+
+        return $this->group->getRole();
+    }
+
+    function hasPermission($perm) {
+        static $perms = null;
+        if (!isset($perms[$perm])) {
+            $perms[$perm] = false;
+            foreach($this->getDepartments() as $deptId) {
+                if (($role=$this->getRole($deptId))
+                        && $role->getPermission()
+                        && $role->getPermission()->get($perm))
+                    $perms[$perm] = true;
+            }
+        }
+
+        return $perms[$perm];
+    }
+
+    function canCreateTickets() {
+        return $this->hasPermission('ticket.create');
+    }
+
+    function canAssignTickets() {
+        return $this->hasPermission('ticket.create');
+    }
+
+    function canCloseTickets() {
+        return $this->hasPermission('ticket.close');
+    }
+
+    function canDeleteTickets() {
+        return $this->hasPermission('ticket.delete');
+    }
+
+    function canManageTickets() {
+        return ($this->isAdmin()
+                || $this->canDeleteTickets()
+                || $this->canCloseTickets());
+    }
+
     function isManager() {
         return (($dept=$this->getDept()) && $dept->getManagerId()==$this->getId());
     }
@@ -272,7 +329,7 @@ implements AuthenticatedUser {
     }
 
     function isGroupActive() {
-        return $this->group->group_enabled;
+        return $this->group->isEnabled();
     }
 
     function isactive() {
@@ -311,80 +368,15 @@ implements AuthenticatedUser {
         return ($deptId && in_array($deptId, $this->getDepts()) && !$this->isAccessLimited());
     }
 
-    function canCreateTickets() {
-        return $this->group->can_create_tickets;
-    }
-
-    function canEditTickets() {
-        return $this->group->can_edit_tickets;
-    }
-
-    function canDeleteTickets() {
-        return $this->group->can_delete_tickets;
-    }
-
-    function canCloseTickets() {
-        return $this->group->can_close_tickets;
-    }
-
-    function canPostReply() {
-        return $this->group->can_post_ticket_reply;
-    }
-
-    function canViewStaffStats() {
-        return $this->group->can_view_staff_stats;
-    }
-
-    function canAssignTickets() {
-        return $this->group->can_assign_tickets;
-    }
-
-    function canTransferTickets() {
-        return $this->group->can_transfer_tickets;
-    }
-
-    function canBanEmails() {
-        return $this->group->can_ban_emails;
-    }
-
-    function canManageTickets() {
-        return ($this->isAdmin()
-                 || $this->canDeleteTickets()
-                    || $this->canCloseTickets());
-    }
-
-    function canManagePremade() {
-        return $this->group->can_manage_premade;
-    }
-
-    function canManageCannedResponses() {
-        return $this->canManagePremade();
-    }
-
-    function canManageFAQ() {
-        return $this->group->can_manage_faq;
-    }
-
-    function canManageFAQs() {
-        return $this->canManageFAQ();
-    }
-
-    function showAssignedTickets() {
-        return $this->show_assigned_tickets;
-    }
-
     function getTeams() {
 
-        if (!isset($this->teams)) {
-            $this->teams = array();
-            $sql='SELECT team_id FROM '.TEAM_MEMBER_TABLE
-                .' WHERE staff_id='.db_input($this->getId());
-            if(($res=db_query($sql)) && db_num_rows($res))
-                while(list($id)=db_fetch_row($res))
-                    $this->teams[] = $id;
+        if (!isset($this->_teams)) {
+            $this->_teams = array();
+            foreach ($this->teams as $team)
+                 $this->_teams[] = $team->team_id;
         }
 
-        return $this->teams;
+        return $this->_teams;
     }
     /* stats */
 
@@ -530,27 +522,30 @@ implements AuthenticatedUser {
     }
 
     function updateTeams($team_ids) {
-        if ($team_ids && is_array($team_ids)) {
-            $teams = StaffTeamMember::objects()
+
+        if (is_array($team_ids)) {
+            $members = TeamMember::objects()
                 ->filter(array('staff_id' => $this->getId()));
-            foreach ($teams as $member) {
+            foreach ($members as $member) {
                 if ($idx = array_search($member->team_id, $team_ids)) {
-                    // XXX: Do we really need to track the time of update?
-                    $member->updated = SqlFunction::NOW();
-                    $member->save();
                     unset($team_ids[$idx]);
-                }
-                else {
+                } else {
                     $member->delete();
                 }
             }
+
             foreach ($team_ids as $id) {
-                StaffTeamMember::create(array(
-                    'updated'=>SqlFunction::NOW(),
-                    'staff_id'=>$this->getId(), 'team_id'=>$id
+                TeamMember::create(array(
+                    'staff_id'=>$this->getId(),
+                    'team_id'=>$id
                 ))->save();
             }
+        } else {
+            TeamMember::objects()
+                ->filter(array('staff_id'=>$this->getId()))
+                ->delete();
         }
+
         return true;
     }
 
@@ -573,9 +568,7 @@ implements AuthenticatedUser {
                 .' WHERE staff_id='.db_input($this->getId()));
 
         // Cleanup Team membership table.
-        TeamMember::objects()
-            ->filter(array('staff_id'=>$this->getId()))
-            ->delete();
+        $this->updateTeams(array());
 
         return true;
     }
@@ -600,7 +593,7 @@ implements AuthenticatedUser {
 
         if ($availableonly) {
             $members = $members->filter(array(
-                'group__group_enabled' => 1,
+                'group__flags__hasbit' => Group::FLAG_ENABLED,
                 'onvacation' => 0,
                 'isactive' => 1,
             ));
@@ -608,7 +601,7 @@ implements AuthenticatedUser {
 
         $users=array();
         foreach ($members as $M) {
-            $users[$S->id] = $M->getName();
+            $users[$M->getId()] = $M->getName();
         }
 
         return $users;
@@ -741,14 +734,14 @@ implements AuthenticatedUser {
         if($vars['mobile'] && !Validator::is_phone($vars['mobile']))
             $errors['mobile']=__('Valid phone number is required');
 
-        if($vars['passwd1'] || $vars['passwd2'] || !$id) {
+        if($vars['passwd1'] || $vars['passwd2'] || !$vars['id']) {
             if($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) {
                 $errors['passwd2']=__('Passwords do not match');
             }
             elseif ($vars['backend'] != 'local' || $vars['welcome_email']) {
                 // Password can be omitted
             }
-            elseif(!$vars['passwd1'] && !$id) {
+            elseif(!$vars['passwd1'] && !$vars['id']) {
                 $errors['passwd1']=__('Temporary password is required');
                 $errors['temppasswd']=__('Required');
             } elseif($vars['passwd1'] && strlen($vars['passwd1'])<6) {
@@ -809,19 +802,4 @@ implements AuthenticatedUser {
         return false;
     }
 }
-
-class StaffTeamMember extends VerySimpleModel {
-    static $meta = array(
-        'table' => TEAM_MEMBER_TABLE,
-        'pk' => array('staff_id', 'team_id'),
-        'joins' => array(
-            'staff' => array(
-                'constraint' => array('staff_id' => 'Staff.staff_id'),
-            ),
-            'team' => array(
-                'constraint' => array('team_id' => 'Team.team_id'),
-            ),
-        ),
-    );
-}
 ?>
diff --git a/include/class.team.php b/include/class.team.php
index 8ba31af52bc5ee2800dae7936875149df6003e5f..b7bd6a5026bb1991f04e8e58f55dd55208d660f9 100644
--- a/include/class.team.php
+++ b/include/class.team.php
@@ -20,16 +20,19 @@ class Team extends VerySimpleModel {
         'table' => TEAM_TABLE,
         'pk' => array('team_id'),
         'joins' => array(
-            'staffmembers' => array(
-                'reverse' => 'StaffTeamMember.team'
-            ),
             'lead' => array(
+                'null' => true,
                 'constraint' => array('lead_id' => 'Staff.staff_id'),
             ),
+            'members' => array(
+                'null' => true,
+                'list' => true,
+                'reverse' => 'TeamMember.team',
+            ),
         ),
     );
 
-    var $members;
+    var $_members;
 
     function asVar() {
         return $this->__toString();
@@ -52,16 +55,18 @@ class Team extends VerySimpleModel {
     }
 
     function getMembers() {
-        if (!isset($this->members)) {
-            $this->members = Staff::objects()
-                ->filter(array('teams__team_id'=>$this->getId()))
-                ->order_by('lastname', 'firstname');
+
+        if (!isset($this->_members)) {
+            $this->_members = array();
+            foreach ($this->members as $m)
+                $this->_members[] = $m->staff;
         }
-        return $this->members;
+
+        return $this->_members;
     }
 
     function hasMember($staff) {
-        return $this->getMembers()
+        return $this->members
             ->filter(array('staff_id'=>$staff->getId()))
             ->count() !== 0;
     }
@@ -114,16 +119,58 @@ class Team extends VerySimpleModel {
         return $T != $tag ? $T : $default;
     }
 
-    function updateMembership($vars) {
+    function update($vars, &$errors=array()) {
 
-        // Delete staff marked for removal...
-        if ($vars['remove']) {
-            $this->staffmembers
-                ->filter(array(
-                    'staff_id__in' => $vars['remove']))
-                ->delete();
+        if (!$vars['name']) {
+            $errors['name']=__('Team name is required');
+        } elseif(($tid=self::getIdByName($vars['name'])) && $tid!=$vars['id']) {
+            $errors['name']=__('Team name already exists');
         }
-        return true;
+
+        if ($errors)
+            return false;
+
+        // Reset team lead if they're getting removed
+        if (isset($this->lead_id)
+                && $this->lead_id == $vars['lead_id']
+                && $vars['remove']
+                && in_array($this->lead_id, $vars['remove']))
+            $vars['lead_id'] =0 ;
+
+        $this->isenabled = $vars['isenabled'];
+        $this->noalerts = isset($vars['noalerts']) ? $vars['noalerts'] : 0;
+        $this->lead_id = $vars['lead_id'] ?: 0;
+        $this->name = $vars['name'];
+        $this->notes = Format::sanitize($vars['notes']);
+
+        if ($this->save()) {
+            // Remove checked members
+            if ($vars['remove'] && is_array($vars['remove'])) {
+                TeamMember::objects()
+                    ->filter(array(
+                        'staff_id__in' => $vars['remove']))
+                    ->delete();
+            }
+
+            return true;
+        }
+
+        if (isset($this->team_id)) {
+            $errors['err']=sprintf(__('Unable to update %s.'), __('this team'))
+               .' '.__('Internal error occurred');
+        } else {
+            $errors['err']=sprintf(__('Unable to create %s.'), __('this team'))
+               .' '.__('Internal error occurred');
+        }
+
+        return false;
+    }
+
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+
+        return parent::save($refetch || $this->dirty);
     }
 
     function delete() {
@@ -146,106 +193,89 @@ class Team extends VerySimpleModel {
         return true;
     }
 
-    function save($refetch=false) {
-        if ($this->dirty)
-            $this->updated = SqlFunction::NOW();
-        return parent::save($refetch || $this->dirty);
-    }
-
     /* ----------- Static function ------------------*/
+    static function getIdByName($name) {
 
-    static function getIdbyName($name) {
-        $row = static::objects()
-            ->filter(array('name'=>$name))
+        $row = self::objects()
+            ->filter(array('name'=>trim($name)))
             ->values_flat('team_id')
             ->first();
 
-        return $row ? $row[0] : null;
+        return $row ? $row[0] : 0;
     }
 
-    static function getTeams( $availableOnly=false ) {
-        static $names;
-
-        if (isset($names))
-            return $names;
-
-        $names = array();
-        $teams = static::objects()
-            ->values_flat('team_id', 'name', 'isenabled');
+    static function getTeams($criteria=array()) {
+        static $teams = null;
+        if (!$teams || $criteria) {
+            $teams = array();
+            $query = static::objects()
+                ->values_flat('team_id', 'name', 'isenabled')
+                ->order_by('name');
 
-        if ($availableOnly) {
-            //Make sure the members are active...TODO: include group check!!
-            $teams->annotate(array('members'=>Aggregate::COUNT('staffmembers')))
+            if (isset($criteria['active']) && $criteria['active']) {
+                $query->annotate(array('members_count'=>Aggregate::COUNT('members')))
                 ->filter(array(
                     'isenabled'=>1,
-                    'staffmembers__staff__isactive'=>1,
-                    'staffmembers__staff__onvacation'=>0,
-                    'staffmembers__staff__group__group_enabled'=>1,
+                    'members__staff__isactive'=>1,
+                    'members__staff__onvacation'=>0,
+                    'members__staff__group__group_enabled'=>1,
                 ))
-                ->filter(array('members__gt'=>0))
-                ->order_by('name');
-        }
-
-        foreach ($teams as $row) {
-            list($id, $name, $isenabled) = $row;
-            $names[$id] = self::getLocalById($id, 'name', $name);
-            if (!$isenabled)
-                $names[$id] .= ' ' . __('(disabled)');
+                ->filter(array('members_count__gt'=>0));
+            }
+
+            $items = array();
+            foreach ($query as $row) {
+                //TODO: Fix enabled - flags is a bit field.
+                list($id, $name, $enabled) = $row;
+                $items[$id] = sprintf('%s%s',
+                    self::getLocalById($id, 'name', $name),
+                    ($enabled || isset($criteria['active']))
+                        ? '' : ' ' . __('(disabled)'));
+            }
+
+            //TODO: sort if $criteria['localize'];
+            if ($criteria)
+                return $items;
+
+            $teams = $items;
         }
 
-        return $names;
+        return $teams;
     }
 
     static function getActiveTeams() {
-        return self::getTeams(true);
+        static $teams = null;
+
+        if (!isset($teams))
+            $teams = self::getTeams(array('active'=>true));
+
+        return $teams;
     }
 
-    static function create($vars=array()) {
+    static function create($vars=false) {
         $team = parent::create($vars);
         $team->created = SqlFunction::NOW();
         return $team;
     }
 
-    function update($vars, &$errors) {
-        if (isset($this->team_id) && $this->getId() != $vars['id'])
-            $errors['err']=__('Missing or invalid team');
-
-        if(!$vars['name']) {
-            $errors['name']=__('Team name is required');
-        } elseif(strlen($vars['name'])<3) {
-            $errors['name']=__('Team name must be at least 3 chars.');
-        } elseif(($tid=static::getIdByName($vars['name']))
-                && (!isset($this->team_id) || $tid!=$this->getId())) {
-            $errors['name']=__('Team name already exists');
-        }
-
-        if ($errors)
-            return false;
-
-        $this->isenabled = $vars['isenabled'];
-        $this->name = $vars['name'];
-        $this->noalerts = isset($vars['noalerts'])?$vars['noalerts']:0;
-        $this->notes = Format::sanitize($vars['notes']);
-        if (isset($vars['lead_id']))
-            $this->lead_id = $vars['lead_id'];
-
-        // reset team lead if they're being removed from the team
-        if ($this->getLeadId() == $vars['lead_id']
-                && $vars['remove'] && in_array($this->getLeadId(), $vars['remove']))
-            $this->lead_id = 0;
+    static function __create($vars, &$errors) {
+        return self::create($vars)->save();
+    }
 
-        if ($this->save())
-            return $this->updateMembership($vars);
+}
 
-        if ($this->__new__) {
-            $errors['err']=sprintf(__('Unable to create %s.'), __('this team'))
-               .' '.__('Internal error occurred');
-        }
-        else {
-            $errors['err']=sprintf(__('Unable to update %s.'), __('this team'))
-               .' '.__('Internal error occurred');
-        }
-        return false;
-    }
+class TeamMember extends VerySimpleModel {
+    static $meta = array(
+        'table' => TEAM_MEMBER_TABLE,
+        'pk' => array('team_id', 'staff_id'),
+        'joins' => array(
+            'team' => array(
+                'constraint' => array('team_id' => 'Team.team_id'),
+            ),
+            'staff' => array(
+                'constraint' => array('staff_id' => 'Staff.staff_id'),
+            ),
+        ),
+    );
 }
 ?>
diff --git a/include/class.ticket.php b/include/class.ticket.php
index 7b95d9b82dc6c128db158958ce572fec58bde560..1e3c1eb64240842ab2d5867bc9fcf837ceeea220 100644
--- a/include/class.ticket.php
+++ b/include/class.ticket.php
@@ -51,7 +51,7 @@ class TicketModel extends VerySimpleModel {
                 'null' => true,
             ),
             'dept' => array(
-                'constraint' => array('dept_id' => 'Dept.dept_id'),
+                'constraint' => array('dept_id' => 'Dept.id'),
             ),
             'sla' => array(
                 'constraint' => array('sla_id' => 'SlaModel.id'),
@@ -182,10 +182,10 @@ class Ticket {
         if(!$id && !($id=$this->getId()))
             return false;
 
-        $sql='SELECT  ticket.*, lock_id, dept_name '
+        $sql='SELECT  ticket.*, lock_id, dept.name as dept_name '
             .' ,count(distinct attach.attach_id) as attachments'
             .' FROM '.TICKET_TABLE.' ticket '
-            .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.dept_id) '
+            .' LEFT JOIN '.DEPT_TABLE.' dept ON (ticket.dept_id=dept.id) '
             .' LEFT JOIN '.SLA_TABLE.' sla ON (ticket.sla_id=sla.id AND sla.isactive=1) '
             .' LEFT JOIN '.TICKET_LOCK_TABLE.' tlock
                 ON ( ticket.ticket_id=tlock.ticket_id AND tlock.expire>NOW()) '
@@ -321,7 +321,7 @@ class Ticket {
         //Collaborator?
         // 1) If the user was authorized via this ticket.
         if ($user->getTicketId() == $this->getId()
-                && !strcasecmp($user->getRole(), 'collaborator'))
+                && !strcasecmp($user->getUserType(), 'collaborator'))
             return true;
 
         // 2) Query the database to check for expanded access...
@@ -980,15 +980,29 @@ class Ticket {
     function setStatus($status, $comments='', &$errors=array()) {
         global $thisstaff;
 
+        if (!$thisstaff || !($role=$thisstaff->getRole($this->getDeptId())))
+            return false;
+
         if ($status && is_numeric($status))
             $status = TicketStatus::lookup($status);
 
         if (!$status || !$status instanceof TicketStatus)
             return false;
 
-        // XXX: intercept deleted status and do hard delete
-        if (!strcasecmp($status->getState(), 'deleted'))
-            return $this->delete($comments);
+        // Double check permissions
+        switch ($status->getState()) {
+        case 'closed':
+            if (!($role->canCloseTickets()))
+                return false;
+            break;
+        case 'deleted':
+            // XXX: intercept deleted status and do hard delete
+            if ($role->canDeleteTickets())
+                return $this->delete($comments);
+            // Agent doesn't have permission to delete  tickets
+            return false;
+            break;
+        }
 
         if ($this->getStatusId() == $status->getId())
             return true;
@@ -996,6 +1010,7 @@ class Ticket {
         $sql = 'UPDATE '.TICKET_TABLE.' SET updated=NOW() '.
                ' ,status_id='.db_input($status->getId());
 
+        //TODO: move this up.
         $ecb = null;
         switch($status->getState()) {
             case 'closed':
@@ -1593,7 +1608,9 @@ class Ticket {
 
         global $cfg, $thisstaff;
 
-        if(!$thisstaff || !$thisstaff->canTransferTickets())
+        if(!$thisstaff
+                || !($role=$thisstaff->getRole($this->getDeptId()))
+                || !$role->canTransferTickets())
             return false;
 
         $currentDept = $this->getDeptName(); //Current department
@@ -1745,7 +1762,8 @@ class Ticket {
 
         if (!$user
                 || ($user->getId() == $this->getOwnerId())
-                || !$thisstaff->canEditTickets())
+                || !($role=$thisstaff->getRole($this->getDeptId()))
+                || !$role->canEditTickets())
             return false;
 
         $sql ='UPDATE '.TICKET_TABLE.' SET updated = NOW() '
@@ -2224,7 +2242,10 @@ class Ticket {
 
         global $cfg, $thisstaff;
 
-        if(!$cfg || !$thisstaff || !$thisstaff->canEditTickets())
+        if (!$cfg
+                || !$thisstaff
+                || !($role=$thisstaff->getRole($this->getDeptId()))
+                || !$role->canEditTickets())
             return false;
 
         $fields=array();
@@ -2870,7 +2891,7 @@ class Ticket {
         }
 
         // Update the estimated due date in the database
-        $this->updateEstDueDate();
+        $ticket->updateEstDueDate();
 
         /**********   double check auto-response  ************/
         //Override auto responder if the FROM email is one of the internal emails...loop control.
@@ -2926,7 +2947,7 @@ class Ticket {
     static function open($vars, &$errors) {
         global $thisstaff, $cfg;
 
-        if(!$thisstaff || !$thisstaff->canCreateTickets()) return false;
+        if (!$thisstaff || !$thisstaff->canCreateTickets()) return false;
 
         if($vars['source'] && !in_array(strtolower($vars['source']),array('email','phone','other')))
             $errors['source']=sprintf(__('Invalid source given - %s'),Format::htmlchars($vars['source']));
@@ -2943,6 +2964,9 @@ class Ticket {
         if (!$thisstaff->canAssignTickets())
             unset($vars['assignId']);
 
+        //TODO: Deny action based on selected department.
+
+
         $create_vars = $vars;
         $tform = TicketForm::objects()->one()->getForm($create_vars);
         $create_vars['cannedattachments']
@@ -2953,16 +2977,19 @@ class Ticket {
 
         $vars['msgId']=$ticket->getLastMsgId();
 
+        // Effective role for the department
+        $role = $thisstaff->getRole($ticket->getDeptId());
+
         // post response - if any
         $response = null;
-        if($vars['response'] && $thisstaff->canPostReply()) {
+        if($vars['response'] && $role->canPostReply()) {
 
             $vars['response'] = $ticket->replaceVars($vars['response']);
             // $vars['cannedatachments'] contains the attachments placed on
             // the response form.
             if(($response=$ticket->postReply($vars, $errors, false))) {
                 //Only state supported is closed on response
-                if(isset($vars['ticket_state']) && $thisstaff->canCloseTickets())
+                if(isset($vars['ticket_state']) && $role->canCloseTickets())
                     $ticket->setState($vars['ticket_state']);
             }
         }
diff --git a/include/class.topic.php b/include/class.topic.php
index ba9e1a82788c2d83c345f28f4b5f52b52de8ffe5..8bc814c8dd9afe512db91c7c4020f7b7ba09a3c9 100644
--- a/include/class.topic.php
+++ b/include/class.topic.php
@@ -40,6 +40,18 @@ class Topic extends VerySimpleModel {
                     'page_id' => 'Page.id',
                 ),
             ),
+            'dept' => array(
+                'null' => true,
+                'constraint' => array(
+                    'dept_id' => 'Dept.id',
+                ),
+            ),
+            'priority' => array(
+                'null' => true,
+                'constraint' => array(
+                    'priority_id' => 'Priority.priority_id',
+                ),
+            ),
         ),
     );
 
@@ -52,15 +64,6 @@ class Topic extends VerySimpleModel {
 
     const FLAG_CUSTOM_NUMBERS = 0x0001;
 
-    function __onload() {
-        global $cfg;
-
-        // Handle upgrade case where sort has not yet been defined
-        if (!$this->ht['sort'] && $cfg->getTopicSortMode() == 'a') {
-            static::updateSortOrder();
-        }
-    }
-
     function asVar() {
         return $this->getName();
     }
@@ -123,9 +126,6 @@ class Topic extends VerySimpleModel {
     }
 
     function getPage() {
-        if(!$this->page && $this->getPageId())
-            $this->page = Page::lookup($this->getPageId());
-
         return $this->page;
     }
 
@@ -260,7 +260,7 @@ class Topic extends VerySimpleModel {
 
     static function __create($vars, &$errors) {
         $topic = self::create();
-        $topic->save($vars, $errors);
+        $topic->update($vars, $errors);
         return $topic;
     }
 
diff --git a/include/client/faq-category.inc.php b/include/client/faq-category.inc.php
index 80168a07c83a0c1758da2304225e9e66b880a345..eb6606af8b261f9daf9d3e8fed1d17c345423a90 100644
--- a/include/client/faq-category.inc.php
+++ b/include/client/faq-category.inc.php
@@ -14,7 +14,7 @@ if(!defined('OSTCLIENTINC') || !$category || !$category->isPublic()) die('Access
 $faqs = FAQ::objects()
     ->filter(array('category'=>$category))
     ->exclude(array('ispublished'=>false))
-    ->annotate(array('has_attachments'=>Aggregate::COUNT('attachments', false,
+    ->annotate(array('has_attachments'=>SqlAggregate::COUNT('attachments', false,
         array('attachments__inline'=>0))))
     ->order_by('-ispublished', 'question');
 
@@ -55,7 +55,7 @@ foreach (Topic::objects()
     ->filter(array('faqs__faq__category__category_id'=>$category->getId()))
     as $t) { ?>
         <a href="?topicId=<?php echo urlencode($t->getId()); ?>"
-            ><?php echo $t->getFullname(); ?></a>
+            ><?php echo $t->getFullName(); ?></a>
 <?php } ?>
         </section>
     </div>
diff --git a/include/client/kb-categories.inc.php b/include/client/kb-categories.inc.php
index 27f41475eb61fa7f859fb5cd100a7bce6a29132a..c6cc4e930aad3076b4237db87f8d932d6aac1df6 100644
--- a/include/client/kb-categories.inc.php
+++ b/include/client/kb-categories.inc.php
@@ -3,7 +3,7 @@
 <?php
     $categories = Category::objects()
         ->exclude(Q::any(array('ispublic'=>false, 'faqs__ispublished'=>false)))
-        ->annotate(array('faq_count'=>Aggregate::count('faqs')))
+        ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')))
         ->filter(array('faq_count__gt'=>0));
     if ($categories->all()) { ?>
         <div><?php echo __('Click on the category to browse FAQs.'); ?></div>
@@ -45,7 +45,7 @@
             <option value="">— Browse by Topic —</option>
 <?php
 $topics = Topic::objects()
-    ->annotate(array('has_faqs'=>Aggregate::COUNT('faqs')))
+    ->annotate(array('has_faqs'=>SqlAggregate::COUNT('faqs')))
     ->filter(array('has_faqs__gt'=>0));
 foreach ($topics as $T) { ?>
         <option value="<?php echo $T->getId(); ?>"><?php echo $T->getFullName();
diff --git a/include/client/kb-search.inc.php b/include/client/kb-search.inc.php
index ee8f51f87e4132a9923387bcc21212b50fddd794..5166a6616fbd5ee2ff677124e46aeaaae15b0a2c 100644
--- a/include/client/kb-search.inc.php
+++ b/include/client/kb-search.inc.php
@@ -34,7 +34,7 @@
             <div class="header"><?php echo __('Help Topics'); ?></div>
 <?php
 foreach (Topic::objects()
-    ->annotate(array('faqs_count'=>Aggregate::count('faqs')))
+    ->annotate(array('faqs_count'=>SqlAggregate::count('faqs')))
     ->filter(array('faqs_count__gt'=>0))
     as $t) { ?>
         <div><a href="?topicId=<?php echo urlencode($t->getId()); ?>"
@@ -45,7 +45,7 @@ foreach (Topic::objects()
             <div class="header"><?php echo __('Categories'); ?></div>
 <?php
 foreach (Category::objects()
-    ->annotate(array('faqs_count'=>Aggregate::count('faqs')))
+    ->annotate(array('faqs_count'=>SqlAggregate::count('faqs')))
     ->filter(array('faqs_count__gt'=>0))
     as $C) { ?>
         <div><a href="?cid=<?php echo urlencode($C->getId()); ?>"
diff --git a/include/client/knowledgebase.inc.php b/include/client/knowledgebase.inc.php
index fa9ba29cc185d435dabaa85daad6d1f79d28a47a..ac4a82f6941f7263b97bef48974865457fbf9fe1 100644
--- a/include/client/knowledgebase.inc.php
+++ b/include/client/knowledgebase.inc.php
@@ -6,8 +6,8 @@ if(!defined('OSTCLIENTINC')) die('Access Denied');
 if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search
     $faqs = FAQ::allPublic()
         ->annotate(array(
-            'attachment_count'=>Aggregate::COUNT('attachments'),
-            'topic_count'=>Aggregate::COUNT('topics')
+            'attachment_count'=>SqlAggregate::COUNT('attachments'),
+            'topic_count'=>SqlAggregate::COUNT('topics')
         ))
         ->order_by('question');
 
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 58ac63af651345554c0f0ab120727179ad80af0c..8ebae5be121324e0a2221a51d66c0c65e6684b96 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -25,7 +25,7 @@ if(isset($_REQUEST['status'])) { //Query string status has nothing to do with th
 }
 
 $sortOptions=array('id'=>'`number`', 'subject'=>'cdata.subject',
-                    'status'=>'status.name', 'dept'=>'dept_name','date'=>'ticket.created');
+                    'status'=>'status.name', 'dept'=>'dept.name','date'=>'ticket.created');
 $orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
 //Sorting options...
 $order_by=$order=null;
@@ -46,7 +46,7 @@ $$x=' class="'.strtolower($order).'" ';
 
 $qselect='SELECT ticket.ticket_id,ticket.`number`,ticket.dept_id,isanswered, '
     .'dept.ispublic, cdata.subject,'
-    .'dept_name, status.name as status, status.state, ticket.source, ticket.created ';
+    .'dept.name as dept_name, status.name as status, status.state, ticket.source, ticket.created ';
 
 $qfrom='FROM '.TICKET_TABLE.' ticket '
       .' LEFT JOIN '.TICKET_STATUS_TABLE.' status
diff --git a/include/i18n/en_US/group.yaml b/include/i18n/en_US/group.yaml
index bc65037e180922fbf221261ae4a9e2035a5cd681..c48f1c2e577da9a1c5575b122e48bf00a78ec6fc 100644
--- a/include/i18n/en_US/group.yaml
+++ b/include/i18n/en_US/group.yaml
@@ -2,29 +2,11 @@
 # Default groups defined for the system
 #
 # Fields:
-# isactive - (bool:0|1) true or false if the group should be initially
-#       usable
+# id - Primary id for the group
+# role_id - (int)  default role for the group
+# flags - (bit mask) group flags
 # name - (string) descriptive name for the group
 # notes - (string) administrative notes (viewable internally only)
-# can_create_tickets - (bool:0|1) true or false if users of the group can
-#       create new tickets
-# can_edit_tickets - (bool:0|1) true or false if users of the group can
-#       modify and update existing tickets
-# can_delete_tickets - (bool:0|1) true or false if members of the group can
-#       delete tickets (permanently)
-# can_close_tickets - (bool:0|1) true or false if members of the group can
-#       close active tickets
-# can_assign_ticets - (bool:0|1) true or false if members of the group can
-#       assign tickets to staff
-# can_transfer_tickets - (bool:0|1) true or false if members of the group
-#       can change the department tickets are assigne dto
-# can_ban_emails - (bool:0|1) true or false if members of the group can add
-#       emails to the system ban list
-# can_manage_premade - (bool:0|1) true or false if members of the group can
-#       create, modify, and delete canned responses
-# can_manage_faq - (bool:0|1) true or false if members of the group can
-#       manage the customer-facing and internal knowledgebase
-#
 # depts: (list<Department<id>>) id's of the departments to which the group
 #       should initially have access
 #
@@ -32,54 +14,30 @@
 # The very first group listed in this document will be the primary group of
 # the initial staff member -- the administrator.
 ---
-- isactive: 1
+- id: 1
+  role_id: 1
+  flags: 1
   name: Lion Tamers
   notes: |
     System overlords. These folks (initially) have full control to all the
     departments they have access to.
-  can_create_tickets: 1
-  can_edit_tickets: 1
-  can_delete_tickets: 1
-  can_close_tickets: 1
-  can_assign_tickets: 1
-  can_transfer_tickets: 1
-  can_ban_emails: 1
-  can_manage_premade: 1
-  can_manage_faq: 1
-  can_post_ticket_reply: 1
 
   depts: [1, 2, 3]
 
-- isactive: 1
+- id: 2
+  role_id: 2
+  flags: 1
   name: Elephant Walkers
   notes: |
     Inhabitants of the ivory tower
-  can_create_tickets: 1
-  can_edit_tickets: 1
-  can_delete_tickets: 1
-  can_close_tickets: 1
-  can_assign_tickets: 1
-  can_transfer_tickets: 1
-  can_ban_emails: 1
-  can_manage_premade: 1
-  can_manage_faq: 1
-  can_post_ticket_reply: 1
 
   depts: [1, 2, 3]
 
-- isactive: 1
+- id: 3
+  role_id: 2
+  flags: 1
   name: Flea Trainers
   notes: |
     Lowly staff members
-  can_create_tickets: 1
-  can_edit_tickets: 1
-  can_delete_tickets: 0
-  can_close_tickets: 1
-  can_assign_tickets: 1
-  can_transfer_tickets: 1
-  can_ban_emails: 0
-  can_manage_premade: 0
-  can_manage_faq: 0
-  can_post_ticket_reply: 1
 
   depts: [1, 2, 3]
diff --git a/include/i18n/en_US/role.yaml b/include/i18n/en_US/role.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..f3def31e27e96f965ff5aa4fc8c3325ea225d81c
--- /dev/null
+++ b/include/i18n/en_US/role.yaml
@@ -0,0 +1,60 @@
+#
+# Default roles defined for the system
+#
+# Fields:
+# id - Primary id for the role
+# flags - (bit mask) role flags
+# name - (string) descriptive name for the role
+# notes - (string) internal notes
+# permissions: (list<keys>)
+#
+# NOTE: ------------------------------------
+# ---
+- id: 1
+  flags: 1
+  name: All Access
+  notes: |
+    Role with unlimited access
+
+  permissions: [
+    ticket.create,
+    ticket.edit,
+    ticket.assign,
+    ticket.transfer,
+    ticket.reply,
+    ticket.close,
+    ticket.delete,
+    kb.premade,
+    kb.faq,
+    stats.agents,
+    emails.banlist]
+
+- id: 2
+  flags: 1
+  name: Expanded Access
+  notes: |
+    Role with expanded access
+
+  permissions: [
+    ticket.create,
+    ticket.edit,
+    ticket.assign,
+    ticket.transfer,
+    ticket.reply,
+    ticket.close,
+    kb.premade,
+    kb.faq,
+    stats.agents,
+    emails.banlist]
+
+- id: 3
+  flags: 1
+  name: Limited Access
+  notes: |
+    Role with limited access
+
+  permissions: [
+    ticket.create,
+    ticket.assign,
+    ticket.transfer,
+    ticket.reply]
diff --git a/include/staff/cannedresponse.inc.php b/include/staff/cannedresponse.inc.php
index ad0d784fb5e0babcce5366938c7d0d5516dbe0fa..57276a6df94fbb504869a5213631005a515a108b 100644
--- a/include/staff/cannedresponse.inc.php
+++ b/include/staff/cannedresponse.inc.php
@@ -56,9 +56,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <select name="dept_id">
                     <option value="0">&mdash; <?php echo __('All Departments');?> &mdash;</option>
                     <?php
-                    $sql='SELECT dept_id, dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name';
-                    if(($res=db_query($sql)) && db_num_rows($res)) {
-                        while(list($id,$name)=db_fetch_row($res)) {
+                    if (($depts=Dept::getDepartments())) {
+                        foreach($depts as $id => $name) {
                             $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
                             echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
                         }
diff --git a/include/staff/cannedresponses.inc.php b/include/staff/cannedresponses.inc.php
index fe63868ba71655d7f596961a52abcf2e33593c66..8f729bb0cb68f5fb03680e62326b229cb77c5b4a 100644
--- a/include/staff/cannedresponses.inc.php
+++ b/include/staff/cannedresponses.inc.php
@@ -2,7 +2,7 @@
 if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied');
 
 $qstr='';
-$sql='SELECT canned.*, count(attach.file_id) as files, dept.dept_name as department '.
+$sql='SELECT canned.*, count(attach.file_id) as files, dept.name as department '.
      ' FROM '.CANNED_TABLE.' canned '.
      ' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=canned.dept_id) '.
      ' LEFT JOIN '.ATTACHMENT_TABLE.' attach
diff --git a/include/staff/categories.inc.php b/include/staff/categories.inc.php
index c88237571ff1f37e786e78b475049d4ddff1a76a..3708e09ca1a897b42137d0fe5c42c0e48c05c999 100644
--- a/include/staff/categories.inc.php
+++ b/include/staff/categories.inc.php
@@ -3,7 +3,7 @@ if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied');
 
 $qstr='';
 $categories = Category::objects()
-    ->annotate(array('faq_count'=>Aggregate::COUNT('faqs')));
+    ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')));
 $sortOptions=array('name'=>'name','type'=>'ispublic','faqs'=>'faq_count','updated'=>'updated');
 $orderWays=array('DESC'=>'-','ASC'=>'');
 $sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
diff --git a/include/staff/category.inc.php b/include/staff/category.inc.php
index f96fc6019d9563ed94e72706fd41042f44719c3c..dc0eaf086d51d997ba2659f985d93b873d408b61 100644
--- a/include/staff/category.inc.php
+++ b/include/staff/category.inc.php
@@ -1,5 +1,5 @@
 <?php
-if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->canManageFAQ()) die('Access Denied');
+if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->getRole()->canManageFAQ()) die('Access Denied');
 $info=array();
 $qstr='';
 if($category && $_REQUEST['a']!='add'){
diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php
index 7681645200ae9f88d6e3171cf987bc91342ba8f5..6828b06925d9bdee216ed73825707b5e8eafeae7 100644
--- a/include/staff/department.inc.php
+++ b/include/staff/department.inc.php
@@ -7,10 +7,9 @@ if($dept && $_REQUEST['a']!='add') {
     $title=__('Update Department');
     $action='update';
     $submit_text=__('Save Changes');
-    $info=$dept->getInfo();
-    $info['id']=$dept->getId();
+    $info = $dept->getInfo();
+    $info['id'] = $dept->getId();
     $info['groups'] = $dept->getAllowedGroups();
-
     $qstr.='&id='.$dept->getId();
 } else {
     $title=__('Add New Department');
@@ -23,8 +22,10 @@ if($dept && $_REQUEST['a']!='add') {
         $info['group_membership'] = 1;
 
     $qstr.='&a='.$_REQUEST['a'];
+
 }
-$info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
+
+$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
 ?>
 <form action="departments.php?<?php echo $qstr; ?>" method="post" id="save">
  <?php csrf_token(); ?>
@@ -32,6 +33,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
  <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
  <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
  <h2><?php echo __('Department');?></h2>
+<br>
+<ul class="tabs">
+    <li class="active"><a href="#settings">
+        <i class="icon-file"></i> <?php echo __('Settings'); ?></a></li>
+    <li><a href="#access">
+        <i class="icon-lock"></i> <?php echo __('Access'); ?></a></li>
+</ul>
+<div id="settings" class="tab_content">
  <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
         <tr>
@@ -47,7 +56,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <?php echo __('Name');?>:
             </td>
             <td>
-                <input data-translate-tag="<?php echo $dept ? $dept->getTranslationTag() : '';
+                <input data-translate-tag="<?php echo $dept ? $dept->getTranslateTag() : '';
                 ?>" type="text" size="30" name="name" value="<?php echo $info['name']; ?>">
                 &nbsp;<span class="error">*&nbsp;<?php echo $errors['name']; ?></span>
             </td>
@@ -250,30 +259,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 </span>
             </td>
         </tr>
-        <tr>
-            <th colspan="2">
-                <em><strong><?php echo __('Group Access'); ?></strong>:
-                <?php echo __('Check all groups allowed to access this department.'); ?>
-                <i class="help-tip icon-question-sign" href="#department_access"></i></em>
-            </th>
-        </tr>
-        <?php
-         $sql='SELECT group_id, group_name, count(staff.staff_id) as members '
-             .' FROM '.GROUP_TABLE.' grp '
-             .' LEFT JOIN '.STAFF_TABLE. ' staff USING(group_id) '
-             .' GROUP by grp.group_id '
-             .' ORDER BY group_name';
-         if(($res=db_query($sql)) && db_num_rows($res)){
-            while(list($id, $name, $members) = db_fetch_row($res)) {
-                if($members>0)
-                    $members=sprintf('<a href="staff.php?a=filter&gid=%d">%d</a>', $id, $members);
-
-                $ck=($info['groups'] && in_array($id,$info['groups']))?'checked="checked"':'';
-                echo sprintf('<tr><td colspan=2>&nbsp;&nbsp;<label><input type="checkbox" name="groups[]" value="%d" %s>&nbsp;%s</label> (%s)</td></tr>',
-                        $id, $ck, $name, $members);
-            }
-         }
-        ?>
         <tr>
             <th colspan="2">
                 <em><strong><?php echo __('Department Signature'); ?></strong>:
@@ -289,9 +274,92 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         </tr>
     </tbody>
 </table>
+</div>
+<div id="access" class="tab_content" style="display:none">
+   <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th colspan=2>
+                <em><?php echo __('Primary department members have access to this department by default'); ?></em>
+            </th>
+        </tr>
+        <tr>
+            <th width="40%"><?php echo __('Group'); ?></th>
+            <th><?php echo __('Role'); ?></th>
+        </tr>
+    </thead>
+    <tbody>
+        <?php
+        $deptId = $dept ? $dept->getId() : 0;
+        $roles = Role::getRoles();
+        $groups = Group::objects()
+            ->annotate(array(
+                'isenabled'=>new SqlExpr(array(
+                        'flags__hasbit' => Group::FLAG_ENABLED))
+                ))
+            ->order_by('name');
+        foreach ($groups as $group) {
+            $DeptAccess = $group->getDepartmentsAccess();
+            ?>
+         <tr>
+            <td>
+             &nbsp;
+             <label>
+              <?php
+              $ck = ($info['groups'] && in_array($group->getId(), $info['groups'])) ? 'checked="checked"' : '';
+              echo sprintf('%s&nbsp;&nbsp;%s',
+                        sprintf('<input type="checkbox" class="grp-ckb"
+                            name="groups[]" value="%s" %s />',
+                            $group->getId(), $ck),
+                        Format::htmlchars($group->getName()));
+              ?>
+             </label>
+            </td>
+            <td>
+                <?php
+                $_name = 'group'.$group->getId().'_role_id';
+                ?>
+                <select name="<?php echo $_name; ?>">
+                    <option value="0">&mdash; <?php
+                        echo sprintf('%s (%s)',
+                                __('Group Default'),
+                                $group->getRole());
+                        ?>
+                        &mdash;</option>
+                    <?php
+                    foreach ($roles as $rid => $role) {
+                        $sel = '';
+                        if (isset($info[$_name]))
+                            $sel = ($info[$_name] == $rid) ? 'selected="selected"' : '';
+                        elseif ($deptId && isset($DeptAccess[$deptId]))
+                            $sel = ($DeptAccess[$deptId] == $rid) ?  'selected="selected"' : '';
+
+                        echo sprintf('<option value="%d" %s>%s</option>',
+                                $rid, $sel, $role);
+                    } ?>
+                </select>
+                <i class="help-tip icon-question-sign" href="#dept-role"></i>
+            </td>
+         </tr>
+         <?php
+        } ?>
+    </tbody>
+    <tfoot>
+     <tr>
+        <td colspan="2">
+            <?php echo __('Select');?>:&nbsp;
+            <a id="selectAll" href="#grp-ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
+            <a id="selectNone" href="#grp-ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
+            <a id="selectToggle" href="#grp-ckb"><?php echo __('Toggle');?></a>&nbsp;&nbsp;
+        </td>
+     </tr>
+    </tfoot>
+   </table>
+</div>
 <p style="text-align:center">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
     <input type="reset"  name="reset"  value="<?php echo __('Reset');?>">
-    <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="departments.php"'>
+    <input type="button" name="cancel" value="<?php echo __('Cancel');?>"
+        onclick='window.location.href="?"'>
 </p>
 </form>
diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php
index ba2d04707bc23d6c699951a048ad75d6fe07dcc1..20018b8dabde7baa4df3475a0951cd8681dca5f6 100644
--- a/include/staff/departments.inc.php
+++ b/include/staff/departments.inc.php
@@ -1,50 +1,47 @@
 <?php
-if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
+if (!defined('OSTADMININC') || !$thisstaff->isAdmin())
+    die('Access Denied');
 
 $qstr='';
-$sql='SELECT dept.dept_id,dept_name,email.email_id,email.email,email.name as email_name,ispublic,count(staff.staff_id) as users '.
-     ',CONCAT_WS(" ",mgr.firstname,mgr.lastname) as manager,mgr.staff_id as manager_id,dept.created,dept.updated  FROM '.DEPT_TABLE.' dept '.
-     ' LEFT JOIN '.STAFF_TABLE.' mgr ON dept.manager_id=mgr.staff_id '.
-     ' LEFT JOIN '.EMAIL_TABLE.' email ON dept.email_id=email.email_id '.
-     ' LEFT JOIN '.STAFF_TABLE.' staff ON dept.dept_id=staff.dept_id ';
+$sortOptions=array(
+    'name' => 'name',
+    'type' => 'ispublic',
+    'members'=> 'members_count',
+    'email'=> 'email__name',
+    'manager'=>'manager__lastname'
+    );
 
-$sql.=' WHERE 1';
-$sortOptions=array('name'=>'dept.dept_name','type'=>'ispublic','users'=>'users','email'=>'email_name, email.email','manager'=>'manager');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
-//Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
+if ($sort && $sortOptions[$sort]) {
+    $order_column = $sortOptions[$sort];
 }
-$order_column=$order_column?$order_column:'dept.dept_name';
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+$order_column = $order_column ? $order_column : 'name';
+
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) {
+    $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+    $order = 'ASC';
 }
-$order=$order?$order:'ASC';
 
-if($order_column && strpos($order_column,',')){
+if ($order_column && strpos($order_column,',')) {
     $order_column=str_replace(','," $order,",$order_column);
 }
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
-
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = Dept::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('departments.php', $_qstr);
+$showing = $pageNav->showing().' '._N('department', 'departments', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
-
-$query="$sql GROUP BY dept.dept_id ORDER BY $order_by";
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=sprintf(_N('Showing %d department', 'Showing %d departments',
-        $num),$num);
-else
-    $showing=__('No departments found!');
-
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
  <h2><?php echo __('Departments');?></h2>
  </div>
-<div class="pull-left flush-right" style="padding-top:5px;padding-right:5px;">
+<div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;">
     <b><a href="departments.php?a=add" class="Icon newDepartment"><?php echo __('Add New Department');?></a></b></div>
 <div class="clear"></div>
 <form action="departments.php" method="POST" name="depts">
@@ -56,61 +53,76 @@ else
     <thead>
         <tr>
             <th width="7px">&nbsp;</th>
-            <th width="180"><a <?php echo $name_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Name');?></a></th>
+            <th width="200"><a <?php echo $name_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Name');?></a></th>
             <th width="80"><a  <?php echo $type_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=type"><?php echo __('Type');?></a></th>
-            <th width="70"><a  <?php echo $users_sort; ?>href="departments.php?<?php echo $qstr; ?>&sort=users"><?php echo __('Users');?></a></th>
+            <th width="70"><a  <?php echo $members_sort; ?>href="departments.php?<?php echo $qstr; ?>&sort=members"><?php echo __('Members');?></a></th>
             <th width="300"><a  <?php echo $email_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=email"><?php echo __('Email Address');?></a></th>
-            <th width="200"><a  <?php echo $manager_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=manager"><?php echo __('Department Manager');?></a></th>
+            <th width="180"><a  <?php echo $manager_sort; ?> href="departments.php?<?php echo $qstr; ?>&sort=manager"><?php echo __('Manager');?></a></th>
         </tr>
     </thead>
     <tbody>
     <?php
-        $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if($res && db_num_rows($res)):
+        $ids= ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count) {
+            $depts = Dept::objects()
+                ->annotate(array(
+                        'members_count' => SqlAggregate::COUNT('members', true),
+                ))
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_column))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
             $defaultId=$cfg->getDefaultDeptId();
             $defaultEmailId = $cfg->getDefaultEmail()->getId();
             $defaultEmailAddress = (string) $cfg->getDefaultEmail();
-            while ($row = db_fetch_array($res)) {
+            foreach ($depts as $dept) {
+                $id = $dept->getId();
                 $sel=false;
-                if($ids && in_array($row['dept_id'],$ids))
+                if($ids && in_array($dept->getId(), $ids))
                     $sel=true;
 
-                if ($row['email_id'])
-                    $row['email']=$row['email_name']?($row['email_name'].' <'.$row['email'].'>'):$row['email'];
-                elseif($defaultEmailId) {
-                    $row['email_id'] = $defaultEmailId;
-                    $row['email'] = $defaultEmailAddress;
+                if ($dept->email) {
+                    $email = (string) $dept->email;
+                    $emailId = $dept->email->getId();
+                } else {
+                    $emailId = $defaultEmailId;
+                    $email = $defaultEmailAddress;
                 }
 
-                $default=($defaultId==$row['dept_id'])?' <small>'.__('(Default)').'</small>':'';
+                $default= ($defaultId == $dept->getId()) ?' <small>'.__('(Default)').'</small>' : '';
                 ?>
-            <tr id="<?php echo $row['dept_id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['dept_id']; ?>"
-                            <?php echo $sel?'checked="checked"':''; ?>  <?php echo $default?'disabled="disabled"':''; ?> >
+                  <input type="checkbox" class="ckb" name="ids[]"
+                  value="<?php echo $id; ?>"
+                  <?php echo $sel? 'checked="checked"' : ''; ?>
+                  <?php echo $default? 'disabled="disabled"' : ''; ?> >
                 </td>
-                <td><a href="departments.php?id=<?php echo $row['dept_id']; ?>"><?php echo $row['dept_name']; ?></a>&nbsp;<?php echo $default; ?></td>
-                <td><?php echo $row['ispublic']?__('Public'):'<b>'.__('Private').'</b>'; ?></td>
+                <td><a href="departments.php?id=<?php echo $id; ?>"><?php
+                echo $dept->getName(); ?></a>&nbsp;<?php echo $default; ?></td>
+                <td><?php echo $dept->isPublic() ? __('Public') :'<b>'.__('Private').'</b>'; ?></td>
                 <td>&nbsp;&nbsp;
                     <b>
-                    <?php if($row['users']>0) { ?>
-                        <a href="staff.php?did=<?php echo $row['dept_id']; ?>"><?php echo $row['users']; ?></a>
+                    <?php if ($dept->members_count) { ?>
+                        <a href="staff.php?did=<?php echo $id; ?>"><?php echo $dept->members_count; ?></a>
                     <?php }else{ ?> 0
                     <?php } ?>
                     </b>
                 </td>
-                <td><span class="ltr"><a href="emails.php?id=<?php echo $row['email_id']; ?>"><?php
-                    echo Format::htmlchars($row['email']); ?></a></span></td>
-                <td><a href="staff.php?id=<?php echo $row['manager_id']; ?>"><?php echo $row['manager']; ?>&nbsp;</a></td>
+                <td><span class="ltr"><a href="emails.php?id=<?php echo $emailId; ?>"><?php
+                    echo Format::htmlchars($email); ?></a></span></td>
+                <td><a href="staff.php?id=<?php echo $dept->manager_id; ?>"><?php
+                    echo $dept->manager_id ? $dept->manager : ''; ?>&nbsp;</a></td>
             </tr>
             <?php
-            } //end of while.
-        endif; ?>
+            } //end of foreach.
+        } ?>
     <tfoot>
      <tr>
         <td colspan="6">
-            <?php if($res && $num){ ?>
+            <?php
+            if ($count) { ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -123,18 +135,18 @@ else
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
-?>
-<p class="centered" id="actions">
-    <input class="button" type="submit" name="make_public" value="<?php echo __('Make Public');?>" >
-    <input class="button" type="submit" name="make_private" value="<?php echo __('Make Private');?>" >
-    <input class="button" type="submit" name="delete" value="<?php echo __('Delete Dept(s)');?>" >
-</p>
+if ($count): //Show options..
+    echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
+    ?>
+    <p class="centered" id="actions">
+        <input class="button" type="submit" name="delete" value="<?php echo
+        __('Delete');
+        ?>" >
+    </p>
 <?php
 endif;
 ?>
 </form>
-
 <div style="display:none;" class="dialog" id="confirm-action">
     <h3><?php echo __('Please Confirm');?></h3>
     <a class="close" href=""><i class="icon-remove-circle"></i></a>
diff --git a/include/staff/directory.inc.php b/include/staff/directory.inc.php
index 6d127962a2f9d30a9b5ed655333f170cac453ce9..cb212ec6505cc2469f2bf3771e351f138ab6ddf9 100644
--- a/include/staff/directory.inc.php
+++ b/include/staff/directory.inc.php
@@ -1,7 +1,7 @@
 <?php
 if(!defined('OSTSTAFFINC') || !$thisstaff || !$thisstaff->isStaff()) die('Access Denied');
 $qstr='';
-$select='SELECT staff.*,CONCAT_WS(" ",firstname,lastname) as name,dept.dept_name as dept ';
+$select='SELECT staff.*,CONCAT_WS(" ",firstname,lastname) as name,dept.name as dept ';
 $from='FROM '.STAFF_TABLE.' staff '.
       'LEFT JOIN '.DEPT_TABLE.' dept ON(staff.dept_id=dept.dept_id) ';
 $where='WHERE staff.isvisible=1 ';
@@ -28,7 +28,7 @@ if($_REQUEST['did'] && is_numeric($_REQUEST['did'])) {
     $qstr.='&did='.urlencode($_REQUEST['did']);
 }
 
-$sortOptions=array('name'=>'staff.firstname,staff.lastname','email'=>'staff.email','dept'=>'dept.dept_name',
+$sortOptions=array('name'=>'staff.firstname,staff.lastname','email'=>'staff.email','dept'=>'dept.name',
                    'phone'=>'staff.phone','mobile'=>'staff.mobile','ext'=>'phone_ext',
                    'created'=>'staff.created','login'=>'staff.lastlogin');
 $orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
@@ -68,10 +68,10 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ".
         <select name="did" id="did">
              <option value="0">&mdash; <?php echo __('All Departments');?> &mdash;</option>
              <?php
-             $sql='SELECT dept.dept_id, dept.dept_name,count(staff.staff_id) as users  '.
+             $sql='SELECT dept.dept_id, dept.name as dept,count(staff.staff_id) as users  '.
                   'FROM '.DEPT_TABLE.' dept '.
                   'INNER JOIN '.STAFF_TABLE.' staff ON(staff.dept_id=dept.dept_id AND staff.isvisible=1) '.
-                  'GROUP By dept.dept_id HAVING users>0 ORDER BY dept_name';
+                  'GROUP By dept.dept_id HAVING users>0 ORDER BY dept.name';
              if(($res=db_query($sql)) && db_num_rows($res)){
                  while(list($id,$name, $users)=db_fetch_row($res)){
                      $sel=($_REQUEST['did'] && $_REQUEST['did']==$id)?'selected="selected"':'';
diff --git a/include/staff/email.inc.php b/include/staff/email.inc.php
index 9d2e3f89c566b28d2df5eae293b548de28732bf0..e556d39b643f874d21a79e50be6c02fcf9fbf0e3 100644
--- a/include/staff/email.inc.php
+++ b/include/staff/email.inc.php
@@ -84,12 +84,11 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
 			    <option value="0" selected="selected">&mdash; <?php
                 echo __('System Default'); ?> &mdash;</option>
 			    <?php
-			    $sql='SELECT dept_id, dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name';
-			    if(($res=db_query($sql)) && db_num_rows($res)){
-				while(list($id,$name)=db_fetch_row($res)){
-				    $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
-				    echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
-				}
+                if (($depts=Dept::getDepartments())) {
+                    foreach ($depts as $id => $name) {
+				        $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
+				        echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
+				    }
 			    }
 			    ?>
 			</select>
diff --git a/include/staff/emails.inc.php b/include/staff/emails.inc.php
index 6eb1faf3cf23a978e9e8f7ca975a76d9753e7cf8..7b772d3e0f173802cabf564fdad4a35aedc32f6b 100644
--- a/include/staff/emails.inc.php
+++ b/include/staff/emails.inc.php
@@ -2,49 +2,42 @@
 if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
 
 $qstr='';
-$sql='SELECT email.*,dept.dept_name as department,priority_desc as priority '.
-     ' FROM '.EMAIL_TABLE.' email '.
-     ' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=email.dept_id) '.
-     ' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (pri.priority_id=email.priority_id) ';
-$sql.=' WHERE 1';
-$sortOptions=array('email'=>'email.email','dept'=>'department','priority'=>'priority','created'=>'email.created','updated'=>'email.updated');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'email';
-//Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
-}
-$order_column=$order_column?$order_column:'email.email';
+$sortOptions = array(
+        'email' => 'email',
+        'dept' => 'dept__name',
+        'priority' => 'priority__priority_desc',
+        'created' => 'created',
+        'updated' => 'updated');
+
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ?  strtolower($_REQUEST['sort']) : 'email';
+if ($sort && $sortOptions[$sort]) {
+        $order_column = $sortOptions[$sort];
 }
-$order=$order?$order:'ASC';
 
-if($order_column && strpos($order_column,',')){
-    $order_column=str_replace(','," $order,",$order_column);
+$order_column = $order_column ? $order_column : 'email';
+
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])]))
+{
+        $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+        $order = 'ASC';
 }
+
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
-
-$total=db_count('SELECT count(*) FROM '.EMAIL_TABLE.' email ');
-$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
-$pageNav=new Pagenate($total, $page, PAGE_LIMIT);
-$pageNav->setURL('emails.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
-//Ok..lets roll...create the actual query
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = EmailModel::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('emails.php', $_qstr);
+$showing = $pageNav->showing().' '._N('email', 'emails', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
-$query="$sql GROUP BY email.email_id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=$pageNav->showing().' '.__('emails');
-else
-    $showing=__('No emails found!');
 
 $def_dept_id = $cfg->getDefaultDeptId();
 $def_dept_name = $cfg->getDefaultDept()->getName();
 $def_priority = $cfg->getDefaultPriority()->getDesc();
-
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
  <h2><?php echo __('Email Addresses');?></h2>
@@ -70,30 +63,37 @@ $def_priority = $cfg->getDefaultPriority()->getDesc();
     </thead>
     <tbody>
     <?php
-        $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if($res && db_num_rows($res)):
+        $ids = ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count):
             $defaultId=$cfg->getDefaultEmailId();
-            while ($row = db_fetch_array($res)) {
+            $emails = EmailModel::objects()
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_column))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
+
+            foreach ($emails as $email) {
+                $id = $email->getId();
                 $sel=false;
-                if($ids && in_array($row['email_id'],$ids))
+                if ($ids && in_array($email, $ids))
                     $sel=true;
-                $default=($row['email_id']==$defaultId);
-                $email=$row['email'];
-                if($row['name'])
-                    $email=$row['name'].' <'.$row['email'].'>';
+                $default=($id==$defaultId);
                 ?>
-            <tr id="<?php echo $row['email_id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['email_id']; ?>"
-                            <?php echo $sel?'checked="checked"':''; ?>  <?php echo $default?'disabled="disabled"':''; ?>>
+                  <input type="checkbox" class="ckb" name="ids[]"
+                    value="<?php echo $id; ?>"
+                    <?php echo $sel ? 'checked="checked"' : ''; ?>
+                    <?php echo $default?'disabled="disabled"':''; ?>>
                 </td>
-                <td><span class="ltr"><a href="emails.php?id=<?php echo $row['email_id']; ?>"><?php echo Format::htmlchars($email); ?></a></span></td>
-                <td><?php echo $row['priority'] ?: $def_priority; ?></td>
-                <td><a href="departments.php?id=<?php $row['dept_id'] ?: $def_dept_id; ?>"><?php
-                    echo $row['department'] ?: $def_dept_name; ?></a></td>
-                <td>&nbsp;<?php echo Format::date($row['created']); ?></td>
-                <td>&nbsp;<?php echo Format::datetime($row['updated']); ?></td>
+                <td><span class="ltr"><a href="emails.php?id=<?php echo $id; ?>"><?php
+                    echo Format::htmlchars((string) $email); ?></a></span></td>
+                <td><?php echo $email->priority ?: $def_priority; ?></td>
+                <td><a href="departments.php?id=<?php $email->dept_id ?: $def_dept_id; ?>"><?php
+                    echo $email->dept ?: $def_dept_name; ?></a></td>
+                <td>&nbsp;<?php echo Format::date($email->created); ?></td>
+                <td>&nbsp;<?php echo Format::datetime($email->updated); ?></td>
             </tr>
             <?php
             } //end of while.
@@ -101,7 +101,7 @@ $def_priority = $cfg->getDefaultPriority()->getDesc();
     <tfoot>
      <tr>
         <td colspan="6">
-            <?php if($res && $num){ ?>
+            <?php if ($count){ ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -114,7 +114,7 @@ $def_priority = $cfg->getDefaultPriority()->getDesc();
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count):
     echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
diff --git a/include/staff/faq-categories.inc.php b/include/staff/faq-categories.inc.php
index 0019380d022a5e9fd00d7fa87b74785c41854c0b..d66c24470c1475e5d3b05697f1d23ae1ebcfb796 100644
--- a/include/staff/faq-categories.inc.php
+++ b/include/staff/faq-categories.inc.php
@@ -11,7 +11,7 @@ if(!defined('OSTSTAFFINC') || !$thisstaff) die('Access Denied');
             <option value="">&mdash; <?php echo __('All Categories');?> &mdash;</option>
             <?php
             $categories = Category::objects()
-                ->annotate(array('faq_count'=>Aggregate::COUNT('faqs')))
+                ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')))
                 ->filter(array('faq_count__gt'=>0))
                 ->order_by('name');
 print $categories;
@@ -31,7 +31,7 @@ print $categories;
             <option value="">&mdash; <?php echo __('All Help Topics');?> &mdash;</option>
             <?php
             $topics = Topic::objects()
-                ->annotate(array('faq_count'=>Aggregate::COUNT('faqs')))
+                ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')))
                 ->filter(array('faq_count__gt'=>0))
                 ->all();
             usort($topics, function($a, $b) {
@@ -53,8 +53,8 @@ print $categories;
 if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search.
     $faqs = FAQ::objects()
         ->annotate(array(
-            'attachment_count'=>Aggregate::COUNT('attachments'),
-            'topic_count'=>Aggregate::COUNT('topics')
+            'attachment_count'=>SqlAggregate::COUNT('attachments'),
+            'topic_count'=>SqlAggregate::COUNT('topics')
         ))
         ->order_by('question');
 
@@ -89,7 +89,7 @@ if($_REQUEST['q'] || $_REQUEST['cid'] || $_REQUEST['topicId']) { //Search.
     }
 } else { //Category Listing.
     $categories = Category::objects()
-        ->annotate(array('faq_count'=>Aggregate::COUNT('faqs')))
+        ->annotate(array('faq_count'=>SqlAggregate::COUNT('faqs')))
         ->all();
 
     if (count($categories)) {
diff --git a/include/staff/faq-category.inc.php b/include/staff/faq-category.inc.php
index 8ff80af13b9a0be313d561ea17af13a4bf14cd65..f4edf2d9a1f33f088e5d8c48ce23890deeefcaa1 100644
--- a/include/staff/faq-category.inc.php
+++ b/include/staff/faq-category.inc.php
@@ -17,7 +17,7 @@ if(!defined('OSTSTAFFINC') || !$category || !$thisstaff) die('Access Denied');
 <?php echo Format::display($category->getDescription()); ?>
 </div>
 <?php
-if($thisstaff->canManageFAQ()) {
+if($thisstaff->getRole()->canManageFAQ()) {
     echo sprintf('<div class="cat-manage-bar"><a href="categories.php?id=%d" class="Icon editCategory">'.__('Edit Category').'</a>
              <a href="categories.php" class="Icon deleteCategory">'.__('Delete Category').'</a>
              <a href="faq.php?cid=%d&a=add" class="Icon newFAQ">'.__('Add New FAQ').'</a></div>',
diff --git a/include/staff/faq-view.inc.php b/include/staff/faq-view.inc.php
index 7b120f0d21aab98d411e024015ec1892ad26de40..2c20b6dcb793727089e49db063fa4afab3417fed 100644
--- a/include/staff/faq-view.inc.php
+++ b/include/staff/faq-view.inc.php
@@ -79,7 +79,7 @@ $query = http_build_query($query); ?>
         echo __('Print'); ?>
     </a></button>
 <?php
-if ($thisstaff->canManageFAQ()) { ?>
+if ($thisstaff->getRole()->canManageFAQ()) { ?>
     <button>
     <i class="icon-edit"></i>
     <a href="faq.php?id=<?php echo $faq->getId(); ?>&a=edit"><?php
@@ -104,7 +104,7 @@ if ($thisstaff->canManageFAQ()) { ?>
 <hr>
 
 <?php
-if ($thisstaff->canManageFAQ()) { ?>
+if ($thisstaff->getRole()->canManageFAQ()) { ?>
 <form action="faq.php?id=<?php echo  $faq->getId(); ?>" method="post">
     <?php csrf_token(); ?>
     <input type="hidden" name="do" value="manage-faq">
diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php
index 5e32630d014942c0a4d2cad5b6a5629371dac19f..01b404b3fc6115970a46e332797164635813b618 100644
--- a/include/staff/faq.inc.php
+++ b/include/staff/faq.inc.php
@@ -1,5 +1,5 @@
 <?php
-if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->canManageFAQ()) die('Access Denied');
+if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->getRole()->canManageFAQ()) die('Access Denied');
 $info=array();
 $qstr='';
 if($faq){
diff --git a/include/staff/group.inc.php b/include/staff/group.inc.php
index 74cc64cb479f4e450df44b8c60ebf8e3645419ab..d471b791d8da636d5897153fd8e886bcf6332af4 100644
--- a/include/staff/group.inc.php
+++ b/include/staff/group.inc.php
@@ -1,50 +1,58 @@
 <?php
-if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
+
 $info=array();
-$qstr='';
-if($group && $_REQUEST['a']!='add'){
-    $title=__('Update Group');
-    $action='update';
-    $submit_text=__('Save Changes');
-    $info=$group->getInfo();
-    $info['id']=$group->getId();
-    $info['depts']=$group->getDepartments();
+if ($group) {
+    $title = __('Update Group');
+    $action = 'update';
+    $submit_text = __('Save Changes');
+    $info = $group->getInfo();
+    $info['id'] = $group->getId();
+    $info['depts'] = $group->getDepartments();
     $trans['name'] = $group->getTranslateTag('name');
-    $qstr.='&id='.$group->getId();
-}else {
-    $title=__('Add New Group');
-    $action='create';
-    $submit_text=__('Create Group');
-    $info['isactive']=isset($info['isactive'])?$info['isactive']:1;
-    $info['can_create_tickets']=isset($info['can_create_tickets'])?$info['can_create_tickets']:1;
-    $qstr.='&a='.$_REQUEST['a'];
+} else {
+    $title = __('Add New Group');
+    $action = 'add';
+    $submit_text = __('Create Group');
+    $info['isactive'] = isset($info['isactive']) ? $info['isactive'] : 1;
 }
-$info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
+
+$info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $info);
+$roles = Role::getActiveRoles();
+
 ?>
-<form action="groups.php?<?php echo $qstr; ?>" method="post" id="save" name="group">
- <?php csrf_token(); ?>
- <input type="hidden" name="do" value="<?php echo $action; ?>">
- <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
- <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
- <h2><?php echo __('Group Access and Permissions');?></h2>
- <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+<form action="" method="post" id="save">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="<?php echo $action; ?>">
+    <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
+    <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
+<h2> <?php echo $group ?: __('New Group'); ?></h2>
+<br>
+<ul class="tabs">
+    <li class="active"><a href="#group">
+        <i class="icon-file"></i> <?php echo __('Group'); ?></a></li>
+    <li><a href="#departments">
+        <i class="icon-lock"></i> <?php echo __('Departments Access'); ?></a></li>
+</ul>
+<div id="group" class="tab_content">
+    <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
         <tr>
             <th colspan="2">
                 <h4><?php echo $title; ?></h4>
-                <em><strong><?php echo __('Group Information');?></strong>: <?php echo __("Disabled group will limit agents' access. Admins are exempted.");?></em>
+                <em><?php echo __(
+                'Roles are used to define agents\' permissions'
+                ); ?>&nbsp;<i class="help-tip icon-question-sign"
+                href="#roles"></i></em>
             </th>
         </tr>
     </thead>
     <tbody>
         <tr>
-            <td width="180" class="required">
-                <?php echo __('Name');?>:
-            </td>
+            <td width="180" class="required"><?php echo __('Name'); ?>:</td>
             <td>
-                <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>"
+                <input size="50" type="text" name="name" value="<?php echo $info['name']; ?>"
                 data-translate-tag="<?php echo $trans['name']; ?>"/>
-                &nbsp;<span class="error">*&nbsp;<?php echo $errors['name']; ?></span>
+                <span class="error">*&nbsp;<?php echo $errors['name']; ?></span>
             </td>
         </tr>
         <tr>
@@ -52,137 +60,121 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <?php echo __('Status');?>:
             </td>
             <td>
-                <input type="radio" name="isactive" value="1" <?php echo $info['isactive']?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong>
+                <input type="radio" name="isactive" value="1" <?php
+                    echo $info['isactive'] ? 'checked="checked"' : ''; ?>><strong><?php echo __('Active');?></strong>
                 &nbsp;
-                <input type="radio" name="isactive" value="0" <?php echo !$info['isactive']?'checked="checked"':''; ?>><strong><?php echo __('Disabled');?></strong>
+                <input type="radio" name="isactive" value="0" <?php
+                    echo !$info['isactive'] ? 'checked="checked"' : ''; ?>><strong><?php echo __('Disabled');?></strong>
                 &nbsp;<span class="error">*&nbsp;<?php echo $errors['status']; ?></span>
                 <i class="help-tip icon-question-sign" href="#status"></i>
             </td>
         </tr>
         <tr>
-            <th colspan="2">
-                <em><strong><?php echo __('Group Permissions');?></strong>: <?php echo __('Applies to all group members');?>&nbsp;</em>
-            </th>
-        </tr>
-        <tr><td><?php echo __('Can <b>Create</b> Tickets');?></td>
-            <td>
-                <input type="radio" name="can_create_tickets"  value="1"   <?php echo $info['can_create_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_create_tickets"  value="0"   <?php echo !$info['can_create_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to open tickets on behalf of users.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Edit</b> Tickets</td>');?>
-            <td>
-                <input type="radio" name="can_edit_tickets"  value="1"   <?php echo $info['can_edit_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_edit_tickets"  value="0"   <?php echo !$info['can_edit_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to edit tickets.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Post Reply</b>');?></td>
-            <td>
-                <input type="radio" name="can_post_ticket_reply"  value="1"   <?php echo $info['can_post_ticket_reply']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_post_ticket_reply"  value="0"   <?php echo !$info['can_post_ticket_reply']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to post a ticket reply.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Close</b> Tickets');?></td>
-            <td>
-                <input type="radio" name="can_close_tickets"  value="1" <?php echo $info['can_close_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_close_tickets"  value="0" <?php echo !$info['can_close_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to close tickets.  Agents can still post a response.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Assign</b> Tickets');?></td>
-            <td>
-                <input type="radio" name="can_assign_tickets"  value="1" <?php echo $info['can_assign_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_assign_tickets"  value="0" <?php echo !$info['can_assign_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to assign tickets to agents.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Transfer</b> Tickets');?></td>
-            <td>
-                <input type="radio" name="can_transfer_tickets"  value="1" <?php echo $info['can_transfer_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_transfer_tickets"  value="0" <?php echo !$info['can_transfer_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to transfer tickets between departments.');?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can <b>Delete</b> Tickets');?></td>
-            <td>
-                <input type="radio" name="can_delete_tickets"  value="1"   <?php echo $info['can_delete_tickets']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_delete_tickets"  value="0"   <?php echo !$info['can_delete_tickets']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __("Ability to delete tickets (Deleted tickets can't be recovered!)");?></i>
-            </td>
-        </tr>
-        <tr><td><?php echo __('Can Ban Emails');?></td>
-            <td>
-                <input type="radio" name="can_ban_emails"  value="1" <?php echo $info['can_ban_emails']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_ban_emails"  value="0" <?php echo !$info['can_ban_emails']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to add/remove emails from banlist via ticket interface.');?></i>
+            <td width="180" class="required">
+                <?php echo __('Default Role');?>:
             </td>
-        </tr>
-        <tr><td><?php echo __('Can Manage Premade');?></td>
             <td>
-                <input type="radio" name="can_manage_premade"  value="1" <?php echo $info['can_manage_premade']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_manage_premade"  value="0" <?php echo !$info['can_manage_premade']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to add/update/disable/delete canned responses and attachments.');?></i>
+                <select name="role_id">
+                    <option value="0">Select One</option>
+                    <?php
+                    foreach ($roles as $id => $role) {
+                        $sel = ($info['role_id'] == $id) ? 'selected="selected"' : '';
+                        echo sprintf('<option value="%d" %s>%s</option>',
+                                $id, $sel, $role);
+                    } ?>
+                </select>
+                &nbsp;<span class="error">*&nbsp;<?php echo $errors['role_id']; ?></span>
+                <i class="help-tip icon-question-sign" href="#role"></i>
             </td>
         </tr>
-        <tr><td><?php echo __('Can Manage FAQ');?></td>
-            <td>
-                <input type="radio" name="can_manage_faq"  value="1" <?php echo $info['can_manage_faq']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_manage_faq"  value="0" <?php echo !$info['can_manage_faq']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to add/update/disable/delete knowledgebase categories and FAQs.');?></i>
-            </td>
+    </tbody>
+    <tbody>
+        <tr>
+            <th colspan="7">
+                <em><strong><?php echo __('Internal Notes'); ?></strong> </em>
+            </th>
         </tr>
-        <tr><td><?php echo __('Can View Agent Stats');?></td>
-            <td>
-                <input type="radio" name="can_view_staff_stats"  value="1" <?php echo $info['can_view_staff_stats']?'checked="checked"':''; ?> /><?php echo __('Yes');?>
-                &nbsp;&nbsp;
-                <input type="radio" name="can_view_staff_stats"  value="0" <?php echo !$info['can_view_staff_stats']?'checked="checked"':''; ?> /><?php echo __('No');?>
-                &nbsp;&nbsp;<i><?php echo __('Ability to view stats of other agents in allowed departments.');?></i>
+        <tr>
+            <td colspan="7"><textarea name="notes" class="richtext no-bar"
+                rows="6" cols="80"><?php
+                echo $info['notes']; ?></textarea>
             </td>
         </tr>
+    </tbody>
+    </table>
+</div>
+<div id="departments" class="tab_content" style="display:none">
+   <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
         <tr>
-            <th colspan="2">
-                <em><strong><?php echo __('Department Access');?></strong>:
-                <i class="help-tip icon-question-sign" href="#department_access"></i>
-                &nbsp;<a id="selectAll" href="#deptckb"><?php echo __('Select All');?></a>
-                &nbsp;&nbsp;
-                <a id="selectNone" href="#deptckb"><?php echo __('Select None');?></a></em>
+            <th colspan=2>
+                <em><?php echo __('Check departments the group is allowed to access and optionally select an effective role.') ?></em>
             </th>
         </tr>
-        <?php
-        foreach (Dept::getDepartments() as $id=>$name) {
-            $ck=($info['depts'] && in_array($id,$info['depts']))?'checked="checked"':'';
-            echo sprintf('<tr><td colspan=2>&nbsp;&nbsp;<input type="checkbox" class="deptckb" name="depts[]" value="%d" %s> %s</td></tr>',$id,$ck,$name);
-        }
-        ?>
         <tr>
-            <th colspan="2">
-                <em><strong><?php echo __('Admin Notes');?></strong>: <?php echo __('Internal notes viewable by all admins.');?>&nbsp;</em>
-            </th>
+            <th width="40%"><?php echo __('Department'); ?></th>
+            <th><?php echo __('Group Role'); ?></th>
         </tr>
-        <tr>
-            <td colspan=2>
-                <textarea class="richtext no-bar" name="notes" cols="21"
-                    rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea>
+    </thead>
+    <tbody>
+        <?php
+        foreach (Dept::getDepartments() as $deptId => $name) { ?>
+         <tr>
+            <td>
+             &nbsp;
+             <label>
+              <?php
+              $ck = ($info['depts'] && in_array($deptId, $info['depts'])) ? 'checked="checked"' : '';
+              echo sprintf('%s&nbsp;&nbsp;%s',
+                        sprintf('<input type="checkbox" class="dept-ckb"
+                            name="depts[]" value="%s" %s />',
+                            $deptId, $ck),
+                        Format::htmlchars($name));
+              ?>
+             </label>
             </td>
-        </tr>
+            <td>
+                <?php
+                $DeptAccess = $group ? $group->getDepartmentsAccess() : array();
+                $_name = 'dept'.$deptId.'_role_id';
+                ?>
+                <select name="<?php echo $_name; ?>">
+                    <option value="0">&mdash; <?php
+                        echo __('Group Default'); ?> &mdash;</option>
+                    <?php
+                    foreach ($roles as $rid => $role) {
+                        $sel = '';
+                        if (isset($info[$_name]))
+                            $sel = ($info[$_name] == $rid) ? 'selected="selected"' : '';
+                        elseif ($DeptAccess && isset($DeptAccess[$deptId]))
+                            $sel = ($DeptAccess[$deptId] == $rid) ?  'selected="selected"' : '';
+
+                        echo sprintf('<option value="%d" %s>%s</option>',
+                                $rid, $sel, $role);
+                    } ?>
+                </select>
+                <i class="help-tip icon-question-sign" href="#dept-role"></i>
+            </td>
+         </tr>
+         <?php
+        } ?>
     </tbody>
-</table>
-<p style="text-align:center">
+    <tfoot>
+     <tr>
+        <td colspan="2">
+            <?php echo __('Select');?>:&nbsp;
+            <a id="selectAll" href="#dept-ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
+            <a id="selectNone" href="#dept-ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
+            <a id="selectToggle" href="#dept-ckb"><?php echo __('Toggle');?></a>&nbsp;&nbsp;
+        </td>
+     </tr>
+    </tfoot>
+   </table>
+</div>
+<p class="centered">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
-    <input type="reset"  name="reset"  value="<?php echo __('Reset');?>">
-    <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="groups.php"'>
+    <input type="reset"  name="reset"  value="<?php echo __('Reset'); ?>">
+    <input type="button" name="cancel" value="<?php echo __('Cancel'); ?>"
+        onclick='window.location.href="?"'>
 </p>
 </form>
diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php
index 326400daa0ba12932943c96934a7783ffb13af92..ac3f438956466eaa2ba695303e8ddae8152ac076 100644
--- a/include/staff/groups.inc.php
+++ b/include/staff/groups.inc.php
@@ -1,43 +1,44 @@
 <?php
-if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
+if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin())
+    die('Access Denied');
 
-$qstr='';
+$qstr = '';
+$sortOptions = array(
+        'name'   => 'name',
+        'users'  => 'members_count',
+        'depts'  => 'depts_count',
+        'status' => 'isenabled',
+        'created'=> 'created',
+        'updated'=> 'updated');
+
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
 
-$sql='SELECT grp.*,count(DISTINCT staff.staff_id) as users, count(DISTINCT dept.dept_id) as depts '
-     .' FROM '.GROUP_TABLE.' grp '
-     .' LEFT JOIN '.STAFF_TABLE.' staff ON(staff.group_id=grp.group_id) '
-     .' LEFT JOIN '.GROUP_DEPT_TABLE.' dept ON(dept.group_id=grp.group_id) '
-     .' WHERE 1';
-$sortOptions=array('name'=>'grp.group_name','status'=>'grp.group_enabled',
-                   'users'=>'users', 'depts'=>'depts', 'created'=>'grp.created','updated'=>'grp.updated');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
 //Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
+if ($sort && $sortOptions[$sort]) {
+    $order_column = $sortOptions[$sort];
 }
-$order_column=$order_column?$order_column:'grp.group_name';
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+$order_column = $order_column ? $order_column : 'name';
+
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) {
+    $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+    $order = 'ASC';
 }
-$order=$order?$order:'ASC';
 
-if($order_column && strpos($order_column,',')){
+if ($order_column && strpos($order_column,',')) {
     $order_column=str_replace(','," $order,",$order_column);
 }
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
-
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = Group::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('groups.php', $_qstr);
+$showing = $pageNav->showing().' '._N('group', 'groups', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
-$query="$sql GROUP BY grp.group_id ORDER BY $order_by";
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=sprintf(__('Showing 1-%1$d of %2$d groups'), $num, $num);
-else
-    $showing=__('No groups found!');
-
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
  <h2><?php echo __('Agent Groups');?>
@@ -67,31 +68,47 @@ else
     <tbody>
     <?php
         $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if($res && db_num_rows($res)) {
-            while ($row = db_fetch_array($res)) {
+        $ids = ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count) {
+            $groups= Group::objects()
+                ->annotate(array(
+                        'members_count'=>SqlAggregate::COUNT('members', true),
+                        'depts_count'=>SqlAggregate::COUNT('depts', true),
+                        'isenabled'=>new SqlExpr(array(
+                                'flags__hasbit' => Group::FLAG_ENABLED))
+                ))
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_column))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
+
+            foreach ($groups as $group) {
                 $sel=false;
-                if($ids && in_array($row['group_id'],$ids))
+                $id = $group->getId();
+                if($ids && in_array($id, $ids))
                     $sel=true;
                 ?>
-            <tr id="<?php echo $row['group_id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['group_id']; ?>"
-                            <?php echo $sel?'checked="checked"':''; ?>> </td>
-                <td><a href="groups.php?id=<?php echo $row['group_id']; ?>"><?php echo $row['group_name']; ?></a> &nbsp;</td>
-                <td>&nbsp;<?php echo $row['group_enabled']?__('Active'):'<b>'.__('Disabled').'</b>'; ?></td>
+                  <input type="checkbox" class="ckb" name="ids[]"
+                    value="<?php echo $id; ?>"
+                    <?php echo $sel?'checked="checked"':''; ?>> </td>
+                <td><a href="groups.php?id=<?php echo $id; ?>"><?php echo
+                $group->getName(); ?></a> &nbsp;</td>
+                <td>&nbsp;<?php echo $group->isenabled ? __('Active') : '<b>'.__('Disabled').'</b>'; ?></td>
                 <td style="text-align:right;padding-right:30px">&nbsp;&nbsp;
-                    <?php if($row['users']>0) { ?>
-                        <a href="staff.php?gid=<?php echo $row['group_id']; ?>"><?php echo $row['users']; ?></a>
-                    <?php }else{ ?> 0
+                    <?php if ($num=$group->members_count) { ?>
+                        <a href="staff.php?gid=<?php echo $id; ?>"><?php echo $num; ?></a>
+                    <?php } else { ?> 0
                     <?php } ?>
                     &nbsp;
                 </td>
                 <td style="text-align:right;padding-right:30px">&nbsp;&nbsp;
-                    <?php echo $row['depts']; ?>
+                    <?php echo $group->depts_count; ?>
                 </td>
-                <td><?php echo Format::date($row['created']); ?>&nbsp;</td>
-                <td><?php echo Format::datetime($row['updated']); ?>&nbsp;</td>
+                <td><?php echo Format::date($group->getCreateDate()); ?>&nbsp;</td>
+                <td><?php echo Format::datetime($group->getUpdateDate()); ?>&nbsp;</td>
             </tr>
             <?php
             } //end of while.
@@ -99,7 +116,7 @@ else
     <tfoot>
      <tr>
         <td colspan="7">
-            <?php if($res && $num){ ?>
+            <?php if ($count) { ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -112,7 +129,8 @@ else
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count):
+    echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
     <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" >
diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php
index 73db1e02235e16af3f67678d704d41a98c03908d..21dd951f3fa1d6f1ab48343775ab53f79b1c688c 100644
--- a/include/staff/helptopic.inc.php
+++ b/include/staff/helptopic.inc.php
@@ -118,11 +118,11 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"';
                 <select name="dept_id">
                     <option value="0">&mdash; <?php echo __('System Default'); ?> &mdash;</option>
                     <?php
-                    $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' dept ORDER by dept_name';
-                    if(($res=db_query($sql)) && db_num_rows($res)){
-                        while(list($id,$name)=db_fetch_row($res)){
+                    if (($depts=Dept::getDepartments())) {
+                        foreach ($depts as $id => $name) {
                             $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
-                            echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
+                            echo sprintf('<option value="%d" %s>%s</option>',
+                                    $id, $selected, $name);
                         }
                     }
                     ?>
@@ -169,11 +169,10 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"';
                 <select name="priority_id">
                     <option value="">&mdash; <?php echo __('System Default'); ?> &mdash;</option>
                     <?php
-                    $sql='SELECT priority_id,priority_desc FROM '.PRIORITY_TABLE.' pri ORDER by priority_urgency DESC';
-                    if(($res=db_query($sql)) && db_num_rows($res)){
-                        while(list($id,$name)=db_fetch_row($res)){
+                    if (($priorities=Priority::getPriorities())) {
+                        foreach ($priorities as $id => $name) {
                             $selected=($info['priority_id'] && $id==$info['priority_id'])?'selected="selected"':'';
-                            echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
+                            echo sprintf('<option value="%d" %s>%s</option>', $id, $selected, $name);
                         }
                     }
                     ?>
@@ -231,9 +230,9 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"';
                     <option value="0">&mdash; <?php echo __('Unassigned'); ?> &mdash;</option>
                     <?php
                     if (($users=Staff::getStaffMembers())) {
-                        echo sprintf('<OPTGROUP label="%s">', sprintf(__('Agents (%d)'), count($user)));
+                        echo sprintf('<OPTGROUP label="%s">',
+                                sprintf(__('Agents (%d)'), count($users)));
                         foreach ($users as $id => $name) {
-                            $name = new PersonsName($name);
                             $k="s$id";
                             $selected = ($info['assign']==$k || $info['staff_id']==$id)?'selected="selected"':'';
                             ?>
@@ -243,15 +242,12 @@ if ($info['form_id'] == Topic::FORM_USE_PARENT) echo 'selected="selected"';
                         }
                         echo '</OPTGROUP>';
                     }
-                    $sql='SELECT team_id, name, isenabled FROM '.TEAM_TABLE.' ORDER BY name';
-                    if(($res=db_query($sql)) && ($cteams = db_num_rows($res))) {
-                        echo sprintf('<OPTGROUP label="%s">', sprintf(__('Teams (%d)'), $cteams));
-                        while (list($id, $name, $isenabled) = db_fetch_row($res)){
+                    if (($teams=Team::getTeams())) {
+                        echo sprintf('<OPTGROUP label="%s">',
+                                sprintf(__('Teams (%d)'), count($teams)));
+                        foreach ($teams as $id => $name) {
                             $k="t$id";
-                            $selected = ($info['assign']==$k || $info['team_id']==$id)?'selected="selected"':'';
-
-                            if (!$isenabled)
-                                $name .= ' '.__('(disabled)');
+                            $selected = ($info['assign']==$k || $info['team_id']==$id) ? 'selected="selected"' : '';
                             ?>
                             <option value="<?php echo $k; ?>"<?php echo $selected; ?>><?php echo $name; ?></option>
                         <?php
diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php
index 1c9d3fd649344453cd9d32282f39ca257a60d2d2..681efbbe0acbc34ba12ff908e862a8242b5a5032 100644
--- a/include/staff/helptopics.inc.php
+++ b/include/staff/helptopics.inc.php
@@ -1,31 +1,14 @@
 <?php
-if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
+if (!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
 
-$sql='SELECT topic.* '
-    .', dept.dept_name as department '
-    .', priority_desc as priority '
-    .' FROM '.TOPIC_TABLE.' topic '
-    .' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.dept_id=topic.dept_id) '
-    .' LEFT JOIN '.TICKET_PRIORITY_TABLE.' pri ON (pri.priority_id=topic.priority_id) ';
-$sql.=' WHERE 1';
-$order_by = '`sort`';
 
-$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
-//Ok..lets roll...create the actual query
-$query="$sql ORDER BY $order_by";
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=sprintf(_N('Showing %d help topic', 'Showing %d help topics', $num), $num);
-else
-    $showing=__('No help topics found!');
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = Topic::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$pageNav->setURL('helptopics.php', $_qstr);
+$showing = $pageNav->showing().' '._N('help topic', 'help topics', $count);
 
-// Get the full names and filter for this page
-$topics = array();
-while ($row = db_fetch_array($res))
-    $topics[] = $row;
-
-foreach ($topics as &$t)
-    $t['name'] = Topic::getTopicName($t['topic_id']);
+$order_by = ($cfg->getTopicSortMode() == 'm') ? 'sort' : 'topic';
 
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
@@ -67,52 +50,67 @@ foreach ($topics as &$t)
     <tbody class="<?php if ($cfg->getTopicSortMode() == 'm') echo 'sortable-rows'; ?>"
         data-sort="sort-">
     <?php
-        $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if (count($topics)):
+        $ids= ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count) {
+            $topics = Topic::objects()
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_by))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
+
             $defaultDept = $cfg->getDefaultDept();
             $defaultPriority = $cfg->getDefaultPriority();
             $sort = 0;
-            foreach($topics as $row) {
+            foreach($topics as $topic) {
+                $id = $topic->getId();
                 $sort++; // Track initial order for transition
                 $sel=false;
-                if($ids && in_array($row['topic_id'],$ids))
+                if ($ids && in_array($id, $ids))
                     $sel=true;
 
-                if (!$row['dept_id'] && $defaultDept) {
-                    $row['dept_id'] = $defaultDept->getId();
-                    $row['department'] = (string) $defaultDept;
+                if ($topic->dept_id) {
+                    $deptId = $topic->dept_id;
+                    $dept = (string) $topic->dept;
+                } elseif ($defaultDept) {
+                    $deptId = $defaultDept->getId();
+                    $dept = (string) $defaultDept;
+                } else {
+                    $deptId = 0;
+                    $dept = '';
                 }
-
-                if (!$row['priority'] && $defaultPriority)
-                    $row['priority'] = (string) $defaultPriority;
-
+                $priority = $team->priority ?: $defaultPriority;
                 ?>
-            <tr id="<?php echo $row['topic_id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="hidden" name="sort-<?php echo $row['topic_id']; ?>" value="<?php
-                        echo $row['sort'] ?: $sort; ?>"/>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['topic_id']; ?>"
-                            <?php echo $sel?'checked="checked"':''; ?>>
+                  <input type="hidden" name="sort-<?php echo $id; ?>" value="<?php
+                        echo $topic->sort ?: $sort; ?>"/>
+                  <input type="checkbox" class="ckb" name="ids[]"
+                    value="<?php echo $id; ?>" <?php
+                    echo $sel ? 'checked="checked"' : ''; ?>>
                 </td>
                 <td>
-<?php if ($cfg->getTopicSortMode() == 'm') { ?>
-                    <i class="icon-sort"></i>
-<?php } ?>
-<a href="helptopics.php?id=<?php echo $row['topic_id']; ?>"><?php echo $row['name']; ?></a>&nbsp;</td>
-                <td><?php echo $row['isactive']?__('Active'):'<b>'.__('Disabled').'</b>'; ?></td>
-                <td><?php echo $row['ispublic']?__('Public'):'<b>'.__('Private').'</b>'; ?></td>
-                <td><?php echo $row['priority']; ?></td>
-                <td><a href="departments.php?id=<?php echo $row['dept_id']; ?>"><?php echo $row['department']; ?></a></td>
-                <td>&nbsp;<?php echo Format::datetime($row['updated']); ?></td>
+                    <?php
+                    if ($cfg->getTopicSortMode() == 'm') { ?>
+                        <i class="icon-sort"></i>
+                    <?php } ?>
+                    <a href="helptopics.php?id=<?php echo $id; ?>"><?php
+                    echo Topic::getTopicName($id); ?></a>&nbsp;
+                </td>
+                <td><?php echo $topic->isactive ? __('Active') : '<b>'.__('Disabled').'</b>'; ?></td>
+                <td><?php echo $topic->ispublic ? __('Public') : '<b>'.__('Private').'</b>'; ?></td>
+                <td><?php echo $priority; ?></td>
+                <td><a href="departments.php?id=<?php echo $deptId;
+                ?>"><?php echo $dept; ?></a></td>
+                <td>&nbsp;<?php echo Format::datetime($team->updated); ?></td>
             </tr>
             <?php
-            } //end of while.
-        endif; ?>
+            } //end of foreach.
+        }?>
     <tfoot>
      <tr>
         <td colspan="7">
-            <?php if($res && $num){ ?>
+            <?php if ($count) { ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -125,7 +123,8 @@ foreach ($topics as &$t)
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count): //Show options..
+     echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
 <?php if ($cfg->getTopicSortMode() != 'a') { ?>
diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php
index 6fbd8af00f883083023792023ccb8f896dc6f848..5f364a768592f2c1fb5cf3b61bac752eb019392e 100644
--- a/include/staff/pages.inc.php
+++ b/include/staff/pages.inc.php
@@ -3,7 +3,7 @@ if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
 
 $pages = Page::objects()
     ->filter(array('type__in'=>array('other','landing','thank-you','offline')))
-    ->annotate(array('topics'=>Aggregate::count('topics')));
+    ->annotate(array('topics'=>SqlAggregate::COUNT('topics')));
 $qstr='';
 $sortOptions=array(
         'name'=>'name', 'status'=>'isactive',
diff --git a/include/staff/role.inc.php b/include/staff/role.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..49e1fff6a49fe46fc5d8e9dbe51c1e207db6711d
--- /dev/null
+++ b/include/staff/role.inc.php
@@ -0,0 +1,118 @@
+<?php
+
+$info=array();
+if ($role) {
+    $title = __('Update Role');
+    $action = 'update';
+    $submit_text = __('Save Changes');
+    $info = $role->getInfo();
+    $newcount=2;
+} else {
+    $title = __('Add New Role');
+    $action = 'add';
+    $submit_text = __('Add Role');
+    $newcount=4;
+}
+
+$info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $info);
+
+?>
+<form action="" method="post" id="save">
+    <?php csrf_token(); ?>
+    <input type="hidden" name="do" value="<?php echo $action; ?>">
+    <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
+    <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
+<h2> <?php echo $role ?: __('New Role'); ?></h2>
+<br>
+<ul class="tabs">
+    <li class="active"><a href="#definition">
+        <i class="icon-file"></i> <?php echo __('Definition'); ?></a></li>
+    <li><a href="#permissions">
+        <i class="icon-lock"></i> <?php echo __('Permissions'); ?></a></li>
+</ul>
+<div id="definition" class="tab_content">
+    <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th colspan="2">
+                <h4><?php echo $title; ?></h4>
+                <em><?php echo __(
+                'Roles are used to define agents\' permissions'
+                ); ?>&nbsp;<i class="help-tip icon-question-sign"
+                href="#roles"></i></em>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+        <tr>
+            <td width="180" class="required"><?php echo __('Name'); ?>:</td>
+            <td>
+                <input size="50" type="text" name="name" value="<?php echo
+                $info['name']; ?>"/>
+                <span class="error">*&nbsp;<?php echo $errors['name']; ?></span>
+            </td>
+        </tr>
+    </tbody>
+    <tbody>
+        <tr>
+            <th colspan="7">
+                <em><strong><?php echo __('Internal Notes'); ?></strong> </em>
+            </th>
+        </tr>
+        <tr>
+            <td colspan="7"><textarea name="notes" class="richtext no-bar"
+                rows="6" cols="80"><?php
+                echo $info['notes']; ?></textarea>
+            </td>
+        </tr>
+    </tbody>
+    </table>
+</div>
+<div id="permissions" class="tab_content" style="display:none">
+   <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th>
+                <em><?php echo __('Check all permissions applicable to this role.') ?></em>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+        <?php
+
+        $setting = $role ? $role->getPermissionInfo() : array();
+        foreach (RolePermission::allPermissions() as $g => $perms) { ?>
+         <tr><th><?php
+             echo Format::htmlchars(__($g)); ?></th></tr>
+         <?php
+         foreach($perms as $k => $v)  { ?>
+          <tr>
+            <td>
+              <label>
+              <?php
+              echo sprintf('<input type="checkbox" name="perms[]" value="%s" %s />',
+                    $k,
+                    (isset($setting[$k]) && $setting[$k]) ?  'checked="checked"' : '');
+              ?>
+              &nbsp;&nbsp;
+              <?php
+                echo sprintf('%s - <em>%s</em>',
+                      Format::htmlchars(__($v[0])),
+                    Format::htmlchars(__($v[1])));
+              ?>
+             </label>
+            </td>
+          </tr>
+          <?php
+         }
+        } ?>
+    </tbody>
+   </table>
+</div>
+<p class="centered">
+    <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
+    <input type="reset"  name="reset"  value="<?php echo __('Reset'); ?>">
+    <input type="button" name="cancel" value="<?php echo __('Cancel'); ?>"
+        onclick='window.location.href="?"'>
+</p>
+</form>
diff --git a/include/staff/roles.inc.php b/include/staff/roles.inc.php
new file mode 100644
index 0000000000000000000000000000000000000000..17723f19df1f776006a3a36858faf10ade560b28
--- /dev/null
+++ b/include/staff/roles.inc.php
@@ -0,0 +1,125 @@
+<div class="pull-left" style="width:700;padding-top:5px;">
+ <h2><?php echo __('Roles'); ?></h2>
+</div>
+<div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;">
+ <b><a href="roles.php?a=add" class="Icon list-add"><?php
+ echo __('Add New Role'); ?></a></b></div>
+<div class="clear"></div>
+
+<?php
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = Role::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$pageNav->setURL('roles.php');
+$showing=$pageNav->showing().' '._N('role', 'roles', $count);
+
+?>
+<form action="roles.php" method="POST" name="roles">
+<?php csrf_token(); ?>
+<input type="hidden" name="do" value="mass_process" >
+<input type="hidden" id="action" name="a" value="" >
+<table class="list" border="0" cellspacing="1" cellpadding="0" width="940">
+    <caption><?php echo $showing; ?></caption>
+    <thead>
+        <tr>
+            <th width="7">&nbsp;</th>
+            <th><?php echo __('Name'); ?></th>
+            <th width="100"><?php echo __('Status'); ?></th>
+            <th width="200"><?php echo __('Created On') ?></th>
+            <th width="250"><?php echo __('Last Updated'); ?></th>
+        </tr>
+    </thead>
+    <tbody>
+    <?php foreach (Role::objects()->order_by('name')
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart()) as $role) {
+            $id = $role->getId();
+            $sel = false;
+            if ($ids && in_array($id, $ids))
+                $sel = true; ?>
+        <tr>
+            <td>
+                <?php
+                if ($role->isDeleteable()) { ?>
+                <input width="7" type="checkbox" class="ckb" name="ids[]"
+                value="<?php echo $id; ?>"
+                    <?php echo $sel?'checked="checked"':''; ?>>
+                <?php
+                } else {
+                    echo '&nbsp;';
+                }
+                ?>
+            </td>
+            <td><a href="?id=<?php echo $id; ?>"><?php echo
+            $role->getName(); ?></a></td>
+            <td>&nbsp;<?php echo $role->isEnabled() ? __('Active') :
+            '<b>'.__('Disabled').'</b>'; ?></td>
+            <td><?php echo Format::date($role->getCreateDate()); ?></td>
+            <td><?php echo Format::datetime($role->getUpdateDate()); ?></td>
+        </tr>
+    <?php }
+    ?>
+    </tbody>
+    <tfoot>
+     <tr>
+        <td colspan="4">
+            <?php if($count){ ?>
+            <?php echo __('Select'); ?>:&nbsp;
+            <a id="selectAll" href="#ckb"><?php echo __('All'); ?></a>&nbsp;&nbsp;
+            <a id="selectNone" href="#ckb"><?php echo __('None'); ?></a>&nbsp;&nbsp;
+            <a id="selectToggle" href="#ckb"><?php echo __('Toggle'); ?></a>&nbsp;&nbsp;
+            <?php } else {
+                echo sprintf(__('No roles defined yet &mdash; %s add one %s!'),
+                    '<a href="roles.php?a=add">','</a>');
+            } ?>
+        </td>
+     </tr>
+    </tfoot>
+</table>
+<?php
+if ($count) //Show options..
+    echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
+?>
+
+<p class="centered" id="actions">
+    <input class="button" type="submit" name="enable" value="<?php echo
+    __('Enable'); ?>">
+    &nbsp;&nbsp;
+    <input class="button" type="submit" name="disable" value="<?php echo
+    __('Disable'); ?>">
+    &nbsp;&nbsp;
+    <input class="button" type="submit" name="delete" value="<?php echo
+    __('Delete'); ?>">
+</p>
+</form>
+
+<div style="display:none;" class="dialog" id="confirm-action">
+    <h3><?php echo __('Please Confirm'); ?></h3>
+    <a class="close" href=""><i class="icon-remove-circle"></i></a>
+    <hr/>
+    <p class="confirm-action" style="display:none;" id="enable-confirm">
+        <?php echo sprintf(__('Are you sure want to <b>enable</b> %s?'),
+            _N('selected role', 'selected roles', 2));?>
+    </p>
+    <p class="confirm-action" style="display:none;" id="disable-confirm">
+        <?php echo sprintf(__('Are you sure want to <b>disable</b> %s?'),
+            _N('selected role', 'selected roles', 2));?>
+    </p>
+    <p class="confirm-action" style="display:none;" id="delete-confirm">
+        <font color="red"><strong><?php echo sprintf(
+        __('Are you sure you want to DELETE %s?'),
+        _N('selected role', 'selected roles', 2)); ?></strong></font>
+        <br><br><?php echo __('Deleted roles CANNOT be recovered.'); ?>
+    </p>
+    <div><?php echo __('Please confirm to continue.'); ?></div>
+    <hr style="margin-top:1em"/>
+    <p class="full-width">
+        <span class="buttons pull-left">
+            <input type="button" value="<?php echo __('No, Cancel'); ?>" class="close">
+        </span>
+        <span class="buttons pull-right">
+            <input type="button" value="<?php echo __('Yes, Do it!'); ?>" class="confirm">
+        </span>
+    </p>
+    <div class="clear"></div>
+</div>
diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php
index a095fbf6ab63ef1ddb4bb98caf7ee369fd8bf5f0..956d1555c3a0ba30df719fd496a54cefddb3533f 100644
--- a/include/staff/settings-system.inc.php
+++ b/include/staff/settings-system.inc.php
@@ -50,9 +50,8 @@ $gmtime = Misc::gmtime();
                 <select name="default_dept_id">
                     <option value="">&mdash; <?php echo __('Select Default Department');?> &mdash;</option>
                     <?php
-                    $sql='SELECT dept_id,dept_name FROM '.DEPT_TABLE.' WHERE ispublic=1';
-                    if(($res=db_query($sql)) && db_num_rows($res)){
-                        while (list($id, $name) = db_fetch_row($res)){
+                    if (($depts=Dept::getPublicDepartments())) {
+                        foreach ($depts as $id => $name) {
                             $selected = ($config['default_dept_id']==$id)?'selected="selected"':''; ?>
                             <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?> <?php echo __('Dept');?></option>
                         <?php
@@ -110,12 +109,12 @@ $gmtime = Misc::gmtime();
             <td width="180"><?php echo __('Default Name Formatting'); ?>:</td>
             <td>
                 <select name="name_format">
-<?php foreach (PersonsName::allFormats() as $n=>$f) {
-    list($desc, $func) = $f;
-    $selected = ($config['name_format'] == $n) ? 'selected="selected"' : ''; ?>
-                    <option value="<?php echo $n; ?>" <?php echo $selected;
-                        ?>><?php echo __($desc); ?></option>
-<?php } ?>
+                <?php foreach (PersonsName::allFormats() as $n=>$f) {
+                    list($desc, $func) = $f;
+                    $selected = ($config['name_format'] == $n) ? 'selected="selected"' : ''; ?>
+                                    <option value="<?php echo $n; ?>" <?php echo $selected;
+                                        ?>><?php echo __($desc); ?></option>
+                <?php } ?>
                 </select>
                 <i class="help-tip icon-question-sign" href="#default_name_formatting"></i>
             </td>
@@ -131,24 +130,30 @@ $gmtime = Misc::gmtime();
             <td>
                 <select name="default_locale">
                     <option value=""><?php echo __('Use Language Preference'); ?></option>
-<?php foreach (Internationalization::allLocales() as $code=>$name) { ?>
+                    <?php
+                    foreach (Internationalization::allLocales() as $code=>$name) { ?>
                     <option value="<?php echo $code; ?>" <?php
                         if ($code == $config['default_locale'])
                             echo 'selected="selected"';
                     ?>><?php echo $name; ?></option>
-<?php } ?>
+
+                    <?php
+                    } ?>
                 </select>
             </td>
         </tr>
         <tr><td width="220" class="required"><?php echo __('Default Time Zone');?>:</td>
             <td>
                 <select name="default_timezone" id="timezone-dropdown">
-<?php foreach (DateTimeZone::listIdentifiers() as $zone) { ?>
+                <?php
+                foreach (DateTimeZone::listIdentifiers() as $zone) { ?>
                     <option value="<?php echo $zone; ?>" <?php
                     if ($config['default_timezone'] == $zone)
                         echo 'selected="selected"';
                     ?>><?php echo str_replace('/',' / ',$zone); ?></option>
-<?php } ?>
+
+                <?php
+                } ?>
                 </select>
                 <button class="action-button" onclick="javascript:
     $('head').append($('<script>').attr('src', '<?php
diff --git a/include/staff/slaplans.inc.php b/include/staff/slaplans.inc.php
index 1eaf4b24e1d0150a869455c23e2c19d160fc4d7a..3565ea7605554c3e828071386e68e60458d5ac60 100644
--- a/include/staff/slaplans.inc.php
+++ b/include/staff/slaplans.inc.php
@@ -2,45 +2,41 @@
 if(!defined('OSTADMININC') || !$thisstaff->isAdmin()) die('Access Denied');
 
 $qstr='';
-$sql='SELECT * FROM '.SLA_TABLE.' sla WHERE 1';
-$sortOptions=array('name'=>'sla.name','status'=>'sla.isactive','period'=>'sla.grace_period','date'=>'sla.created','updated'=>'sla.updated');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
-//Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
+$sortOptions=array(
+        'name' => 'name',
+        'status' => 'isactive',
+        'period' => 'grace_period',
+        'created' => 'created',
+        'updated' => 'updated'
+        );
+
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
+if ($sort && $sortOptions[$sort]) {
+    $order_column = $sortOptions[$sort];
 }
-$order_column=$order_column?$order_column:'sla.name';
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+$order_column = $order_column ? $order_column : 'name';
+
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) {
+    $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+    $order = 'ASC';
 }
-$order=$order?$order:'ASC';
 
-if($order_column && strpos($order_column,',')){
+if ($order_column && strpos($order_column,',')) {
     $order_column=str_replace(','," $order,",$order_column);
 }
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
-
-$total=db_count('SELECT count(*) FROM '.SLA_TABLE.' sla ');
-$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
-$pageNav=new Pagenate($total, $page, PAGE_LIMIT);
-$pageNav->setURL('slas.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
-//Ok..lets roll...create the actual query
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = SLA::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('slas.php', $_qstr);
+$showing = $pageNav->showing().' '._N('SLA plan', 'SLA plans', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
-$query="$sql ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=$pageNav->showing().' '._N('SLA plan',
-        'SLA plans' /* SLA is abbreviation for Service Level Agreement */,
-        $total);
-else
-    $showing=__('No SLA plans found!' /* SLA is abbreviation for Service Level Agreement */);
-
 ?>
-
 <div class="pull-left" style="width:700px;padding-top:5px;">
  <h2><?php echo __('Service Level Agreements');?></h2>
 </div>
@@ -66,38 +62,46 @@ else
     <tbody>
     <?php
         $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if($res && db_num_rows($res)):
+        $ids = ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count) {
+            $slas = SLA::objects()
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_column))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
+
             $defaultId = $cfg->getDefaultSLAId();
-            while ($row = db_fetch_array($res)) {
+            foreach ($slas as $sla) {
                 $sel=false;
-                if($ids && in_array($row['id'],$ids))
+                $id = $sla->getId();
+                if($ids && in_array($id, $ids))
                     $sel=true;
 
                 $default = '';
-                if ($row['id'] == $defaultId)
+                if ($id == $defaultId)
                     $default = '<small><em>(Default)</em></small>';
                 ?>
-            <tr id="<?php echo $row['id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['id']; ?>"
-                    <?php echo $sel?'checked="checked"':''; ?>>
+                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $id; ?>"
+                    <?php echo $sel ? 'checked="checked"' :'' ; ?>>
                 </td>
-                <td>&nbsp;<a href="slas.php?id=<?php echo $row['id'];
-                    ?>"><?php echo Format::htmlchars($row['name']);
+                <td>&nbsp;<a href="slas.php?id=<?php echo $id;
+                    ?>"><?php echo Format::htmlchars($sla->getName());
                     ?></a>&nbsp;<?php echo $default; ?></td>
-                <td><?php echo $row['isactive']?__('Active'):'<b>'.__('Disabled').'</b>'; ?></td>
-                <td style="text-align:right;padding-right:35px;"><?php echo $row['grace_period']; ?>&nbsp;</td>
-                <td>&nbsp;<?php echo Format::date($row['created']); ?></td>
-                <td>&nbsp;<?php echo Format::datetime($row['updated']); ?></td>
+                <td><?php echo $sla->isActive() ? __('Active') : '<b>'.__('Disabled').'</b>'; ?></td>
+                <td style="text-align:right;padding-right:35px;"><?php echo $sla->getGracePeriod(); ?>&nbsp;</td>
+                <td>&nbsp;<?php echo Format::date($sla->getCreateDate()); ?></td>
+                <td>&nbsp;<?php echo Format::datetime($sla->getUpdateDate()); ?></td>
             </tr>
             <?php
-            } //end of while.
-        endif; ?>
+            } //end of foreach.
+        } ?>
     <tfoot>
      <tr>
         <td colspan="6">
-            <?php if($res && $num){ ?>
+            <?php if ($count) { ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -110,7 +114,7 @@ else
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count): //Show options..
     echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php
index 665797a0cc6e8343cfb6949853dae92c68b19c89..1bc7ca1d675f14a7be2c5346d14f173c9a1e3949 100644
--- a/include/staff/staff.inc.php
+++ b/include/staff/staff.inc.php
@@ -234,7 +234,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <select name="group_id" id="group_id">
                     <option value="0">&mdash; <?php echo __('Select Group');?> &mdash;</option>
                     <?php
-                    foreach (Group::getGroupNames() as $id=>$name) {
+                    foreach (Group::getGroups() as $id=>$name) {
                         $sel=($info['group_id']==$id)?'selected="selected"':'';
                         echo sprintf('<option value="%d" %s>%s</option>',
                             $id, $sel, $name);
diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php
index 9ea2607a6db6099cdef1d28ae85ce8ac321eefe9..fe312a01f043c3918cfd9d547dee5d5f397e99fe 100644
--- a/include/staff/staffmembers.inc.php
+++ b/include/staff/staffmembers.inc.php
@@ -1,74 +1,92 @@
 <?php
-if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
+if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin())
+    die('Access Denied');
+
 $qstr='';
-$select='SELECT staff.*,CONCAT_WS(" ",firstname,lastname) as name, grp.group_name, dept.dept_name as dept,count(m.team_id) as teams ';
-$from='FROM '.STAFF_TABLE.' staff '.
-      'LEFT JOIN '.GROUP_TABLE.' grp ON(staff.group_id=grp.group_id) '.
-      'LEFT JOIN '.DEPT_TABLE.' dept ON(staff.dept_id=dept.dept_id) '.
-      'LEFT JOIN '.TEAM_MEMBER_TABLE.' m ON(m.staff_id=staff.staff_id) ';
-$where='WHERE 1 ';
-
-if($_REQUEST['did'] && is_numeric($_REQUEST['did'])) {
-    $where.=' AND staff.dept_id='.db_input($_REQUEST['did']);
-    $qstr.='&did='.urlencode($_REQUEST['did']);
-}
+$sortOptions = array(
+        'name' => 'lastname',
+        'username' => 'username',
+        'status' => 'isactive',
+        'group' => 'group__name',
+        'dept' => 'dept__name',
+        'created' => 'created',
+        'login' => 'lastlogin'
+        );
 
-if($_REQUEST['gid'] && is_numeric($_REQUEST['gid'])) {
-    $where.=' AND staff.group_id='.db_input($_REQUEST['gid']);
-    $qstr.='&gid='.urlencode($_REQUEST['gid']);
-}
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
 
-if($_REQUEST['tid'] && is_numeric($_REQUEST['tid'])) {
-    $where.=' AND m.team_id='.db_input($_REQUEST['tid']);
-    $qstr.='&tid='.urlencode($_REQUEST['tid']);
+if ($sort && $sortOptions[$sort]) {
+    $order_column = $sortOptions[$sort];
 }
 
-$sortOptions=array('name'=>'staff.firstname,staff.lastname','username'=>'staff.username','status'=>'isactive',
-                   'group'=>'grp.group_name','dept'=>'dept.dept_name','created'=>'staff.created','login'=>'staff.lastlogin');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
-//Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
-}
-$order_column=$order_column?$order_column:'staff.firstname,staff.lastname';
+$order_column = $order_column ? $order_column : 'lastname';
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) {
+    $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+    $order = 'ASC';
 }
 
-$order=$order?$order:'ASC';
-if($order_column && strpos($order_column,',')){
+if ($order_column && strpos($order_column,',')) {
     $order_column=str_replace(','," $order,",$order_column);
 }
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
 
-$total=db_count('SELECT count(DISTINCT staff.staff_id) '.$from.' '.$where);
-$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
-$pageNav=new Pagenate($total,$page,PAGE_LIMIT);
-$pageNav->setURL('staff.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']));
-//Ok..lets roll...create the actual query
+//Filers
+$filters = array();
+if ($_REQUEST['did'] && is_numeric($_REQUEST['did'])) {
+    $filters += array('dept_id' => $_REQUEST['did']);
+    $qstr.='&did='.urlencode($_REQUEST['did']);
+}
+
+if ($_REQUEST['gid'] && is_numeric($_REQUEST['gid'])) {
+    $filters += array('group_id' => $_REQUEST['gid']);
+    $qstr.='&gid='.urlencode($_REQUEST['gid']);
+}
+
+if ($_REQUEST['tid'] && is_numeric($_REQUEST['tid'])) {
+    $filters += array('teams__team_id' => $_REQUEST['tid']);
+    $qstr.='&tid='.urlencode($_REQUEST['tid']);
+}
+
+//agents objects
+$agents = Staff::objects()
+    ->annotate(array(
+            'teams_count'=>SqlAggregate::COUNT('teams', true),
+    ))
+    ->order_by(sprintf('%s%s',
+                strcasecmp($order, 'DESC') ? '' : '-',
+                $order_column));
+
+if ($filters)
+    $agents->filter($filters);
+
+// paginate
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = $agents->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('staff.php', $_qstr);
+$showing = $pageNav->showing().' '._N('agent', 'agents', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
-$query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit();
-//echo $query;
+
+// add limits.
+$agents->limit($pageNav->getLimit())->offset($pageNav->getStart());
 ?>
 <h2><?php echo __('Agents');?></h2>
+
 <div class="pull-left" style="width:700px;">
     <form action="staff.php" method="GET" name="filter">
      <input type="hidden" name="a" value="filter" >
         <select name="did" id="did">
              <option value="0">&mdash; <?php echo __('All Department');?> &mdash;</option>
              <?php
-             $sql='SELECT dept.dept_id, dept.dept_name,count(staff.staff_id) as users  '.
-                  'FROM '.DEPT_TABLE.' dept '.
-                  'INNER JOIN '.STAFF_TABLE.' staff ON(staff.dept_id=dept.dept_id) '.
-                  'GROUP By dept.dept_id HAVING users>0 ORDER BY dept_name';
-             if(($res=db_query($sql)) && db_num_rows($res)){
-                 while(list($id,$name, $users)=db_fetch_row($res)){
+             if (($depts=Dept::getDepartments())) {
+                 foreach ($depts as $id => $name) {
                      $sel=($_REQUEST['did'] && $_REQUEST['did']==$id)?'selected="selected"':'';
-                     echo sprintf('<option value="%d" %s>%s (%s)</option>',$id,$sel,$name,$users);
+                     echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name);
                  }
              }
              ?>
@@ -76,14 +94,10 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ".
         <select name="gid" id="gid">
             <option value="0">&mdash; <?php echo __('All Groups');?> &mdash;</option>
              <?php
-             $sql='SELECT grp.group_id, group_name,count(staff.staff_id) as users '.
-                  'FROM '.GROUP_TABLE.' grp '.
-                  'INNER JOIN '.STAFF_TABLE.' staff ON(staff.group_id=grp.group_id) '.
-                  'GROUP BY grp.group_id ORDER BY group_name';
-             if(($res=db_query($sql)) && db_num_rows($res)){
-                 while(list($id,$name,$users)=db_fetch_row($res)){
+             if (($groups=Group::getGroups())) {
+                 foreach ($groups as $id => $name) {
                      $sel=($_REQUEST['gid'] && $_REQUEST['gid']==$id)?'selected="selected"':'';
-                     echo sprintf('<option value="%d" %s>%s (%s)</option>',$id,$sel,$name,$users);
+                     echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name);
                  }
              }
              ?>
@@ -91,13 +105,10 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ".
         <select name="tid" id="tid">
             <option value="0">&mdash; <?php echo __('All Teams');?> &mdash;</option>
              <?php
-             $sql='SELECT team.team_id, team.name, count(member.staff_id) as users FROM '.TEAM_TABLE.' team '.
-                  'INNER JOIN '.TEAM_MEMBER_TABLE.' member ON(member.team_id=team.team_id) '.
-                  'GROUP BY team.team_id ORDER BY team.name';
-             if(($res=db_query($sql)) && db_num_rows($res)){
-                 while(list($id,$name,$users)=db_fetch_row($res)){
+             if (($teams=Team::getTeams())) {
+                 foreach ($teams as $id => $name) {
                      $sel=($_REQUEST['tid'] && $_REQUEST['tid']==$id)?'selected="selected"':'';
-                     echo sprintf('<option value="%d" %s>%s (%s)</option>',$id,$sel,$name,$users);
+                     echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name);
                  }
              }
              ?>
@@ -108,13 +119,6 @@ $query="$select $from $where GROUP BY staff.staff_id ORDER BY $order_by LIMIT ".
  </div>
 <div class="pull-right flush-right" style="padding-right:5px;"><b><a href="staff.php?a=add" class="Icon newstaff"><?php echo __('Add New Agent');?></a></b></div>
 <div class="clear"></div>
-<?php
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=$pageNav->showing() . ' ' . _N('agent', 'agents', $num);
-else
-    $showing=__('No agents found!');
-?>
 <form action="staff.php" method="POST" name="staff" >
  <?php csrf_token(); ?>
  <input type="hidden" name="do" value="mass_process" >
@@ -135,31 +139,38 @@ else
     </thead>
     <tbody>
     <?php
-        if($res && db_num_rows($res)):
-            $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-            while ($row = db_fetch_array($res)) {
+        if ($count):
+            $ids = ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+            foreach ($agents as $agent) {
+                $id = $agent->getId();
                 $sel=false;
-                if($ids && in_array($row['staff_id'],$ids))
+                if ($ids && in_array($id, $ids))
                     $sel=true;
                 ?>
-               <tr id="<?php echo $row['staff_id']; ?>">
+               <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['staff_id']; ?>" <?php echo $sel?'checked="checked"':''; ?> >
-                <td><a href="staff.php?id=<?php echo $row['staff_id']; ?>"><?php echo Format::htmlchars($row['name']); ?></a>&nbsp;</td>
-                <td><?php echo $row['username']; ?></td>
-                <td><?php echo $row['isactive']?__('Active'):'<b>'.__('Locked').'</b>'; ?>&nbsp;<?php echo $row['onvacation']?'<small>(<i>'.__('vacation').'</i>)</small>':''; ?></td>
-                <td><a href="groups.php?id=<?php echo $row['group_id']; ?>"><?php echo Format::htmlchars($row['group_name']); ?></a></td>
-                <td><a href="departments.php?id=<?php echo $row['dept_id']; ?>"><?php echo Format::htmlchars($row['dept']); ?></a></td>
-                <td><?php echo Format::date($row['created']); ?></td>
-                <td><?php echo Format::datetime($row['lastlogin']); ?>&nbsp;</td>
+                  <input type="checkbox" class="ckb" name="ids[]"
+                  value="<?php echo $id; ?>" <?php echo $sel ? 'checked="checked"' : ''; ?> >
+                <td><a href="staff.php?id=<?php echo $id; ?>"><?php echo
+                Format::htmlchars($agent->getName()); ?></a>&nbsp;</td>
+                <td><?php echo $agent->getUserName(); ?></td>
+                <td><?php echo $agent->isActive() ? __('Active') :'<b>'.__('Locked').'</b>'; ?>&nbsp;<?php
+                    echo $agent->onvacation ? '<small>(<i>'.__('vacation').'</i>)</small>' : ''; ?></td>
+                <td><a href="groups.php?id=<?php echo $agent->group_id; ?>"><?php
+                    echo Format::htmlchars('FIXME'/*$agent->group->getName()*/); ?></a></td>
+                <td><a href="departments.php?id=<?php echo
+                    $agent->getDeptId(); ?>"><?php
+                    echo Format::htmlchars((string) $agent->dept); ?></a></td>
+                <td><?php echo Format::date($agent->created); ?></td>
+                <td><?php echo Format::datetime($agent->lastlogin); ?>&nbsp;</td>
                </tr>
             <?php
-            } //end of while.
+            } //end of foreach
         endif; ?>
     <tfoot>
      <tr>
         <td colspan="8">
-            <?php if($res && $num){ ?>
+            <?php if ($count) { ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -172,7 +183,7 @@ else
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count): //Show options..
     echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php
index 0d5fdd2d37e231408761e92d70adcd41b9dd531b..ec651205c6a9bf6e4529de325ba2c921e125c47d 100644
--- a/include/staff/team.inc.php
+++ b/include/staff/team.inc.php
@@ -1,8 +1,8 @@
 <?php
 if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
-$info=array();
+$info=$members=array();
 $qstr='';
-if($team && $_REQUEST['a']!='add'){
+if ($team && $_REQUEST['a']!='add') {
     //Editing Team
     $title=__('Update Team');
     $action='update';
@@ -11,7 +11,8 @@ if($team && $_REQUEST['a']!='add'){
     $info['id']=$team->getId();
     $trans['name'] = $team->getTranslateTag('name');
     $qstr.='&id='.$team->getId();
-}else {
+    $members = $team->getMembers();
+} else {
     $title=__('Add New Team');
     $action='create';
     $submit_text=__('Create Team');
@@ -19,16 +20,29 @@ if($team && $_REQUEST['a']!='add'){
     $info['noalerts']=0;
     $qstr.='&a='.$_REQUEST['a'];
 }
-$info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
+
+$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info);
 ?>
 <form action="teams.php?<?php echo $qstr; ?>" method="post" id="save">
  <?php csrf_token(); ?>
  <input type="hidden" name="do" value="<?php echo $action; ?>">
  <input type="hidden" name="a" value="<?php echo Format::htmlchars($_REQUEST['a']); ?>">
  <input type="hidden" name="id" value="<?php echo $info['id']; ?>">
- <h2><?php echo __('Team');?>
+ <h2><?php echo __('Team');?>&nbsp;
     <i class="help-tip icon-question-sign" href="#teams"></i>
     </h2>
+<br>
+<ul class="tabs">
+    <li class="active"><a href="#team">
+        <i class="icon-file"></i> <?php echo __('Team'); ?></a></li>
+    <?php
+    if ($members) { ?>
+    <li><a href="#members">
+        <i class="icon-group"></i> <?php echo __('Members'); ?></a></li>
+    <?php
+    } ?>
+</ul>
+<div id="team" class="tab_content">
  <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
     <thead>
         <tr>
@@ -71,9 +85,8 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <span>
                 <select name="lead_id">
                     <option value="0">&mdash; <?php echo __('None');?> &mdash;</option>
-                    <option value="" disabled="disabled"><?php echo __('Select Team Lead (Optional)');?></option>
                     <?php
-                    if($team && ($members=$team->getMembers())){
+                    if ($members) {
                         foreach($members as $k=>$staff){
                             $selected=($info['lead_id'] && $staff->getId()==$info['lead_id'])?'selected="selected"':'';
                             echo sprintf('<option value="%d" %s>%s</option>',$staff->getId(),$selected,$staff->getName());
@@ -96,25 +109,6 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
                 <i class="help-tip icon-question-sign" href="#assignment_alert"></i>
             </td>
         </tr>
-        <?php
-        if($team && ($members=$team->getMembers())){ ?>
-        <tr>
-            <th colspan="2">
-                <em><strong><?php echo __('Team Members'); ?></strong>:
-                <i class="help-tip icon-question-sign" href="#members"></i>
-</em>
-            </th>
-        </tr>
-        <?php
-            foreach($members as $k=>$staff){
-                echo sprintf('<tr><td colspan=2><span style="width:350px;padding-left:5px; display:block;" class="pull-left">
-                            <b><a href="staff.php?id=%d">%s</a></span></b>
-                            &nbsp;<input type="checkbox" name="remove[]" value="%d"><i>'.__('Remove').'</i></td></tr>',
-                          $staff->getId(),$staff->getName(),$staff->getId());
-
-
-            }
-        } ?>
         <tr>
             <th colspan="2">
                 <em><strong><?php echo __('Admin Notes');?></strong>: <?php echo __('Internal notes viewable by all admins.');?>&nbsp;</em>
@@ -128,9 +122,39 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
         </tr>
     </tbody>
 </table>
+</div>
+<?php
+if ($members) { ?>
+<div id="members" class="tab_content" style="display:none">
+   <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
+    <thead>
+        <tr>
+            <th>
+                <em><?php echo __('Agents who are members of this team'); ?><i
+                    class="help-tip icon-question-sign" href="#members"></i></em>
+            </th>
+        </tr>
+    </thead>
+    <tbody>
+    <?php
+        foreach($members as $k=>$staff) {
+            echo sprintf('<tr><td colspan=2><span style="width:350px;padding-left:5px; display:block;" class="pull-left">
+                        <b><a href="staff.php?id=%d">%s</a></span></b>
+                        &nbsp;<input type="checkbox" name="remove[]" value="%d"><i>'.__('Remove').'</i></td></tr>',
+                        $staff->getId() ,
+                        $staff->getName(),
+                        $staff->getId());
+
+        }
+     ?>
+    </tbody>
+   </table>
+</div>
+<?php
+} ?>
 <p style="text-align:center">
     <input type="submit" name="submit" value="<?php echo $submit_text; ?>">
     <input type="reset"  name="reset"  value="<?php echo __('Reset');?>">
-    <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="teams.php"'>
+    <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="?"'>
 </p>
 </form>
diff --git a/include/staff/teams.inc.php b/include/staff/teams.inc.php
index d05c371d7a3e20ad36543d6a21e072b20b8273f9..22c08c2073dbf428d406df8320620d6cc7383615 100644
--- a/include/staff/teams.inc.php
+++ b/include/staff/teams.inc.php
@@ -2,40 +2,44 @@
 if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied');
 
 $qstr='';
-$sql='SELECT team.*,count(m.staff_id) as members,CONCAT_WS(" ",lead.firstname,lead.lastname) as team_lead '.
-     ' FROM '.TEAM_TABLE.' team '.
-     ' LEFT JOIN '.TEAM_MEMBER_TABLE.' m ON(m.team_id=team.team_id) '.
-     ' LEFT JOIN '.STAFF_TABLE.' lead ON(lead.staff_id=team.lead_id) ';
-$sql.=' WHERE 1';
-$sortOptions=array('name'=>'team.name','status'=>'team.isenabled','members'=>'members','lead'=>'team_lead','created'=>'team.created');
-$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
-$sort=($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])])?strtolower($_REQUEST['sort']):'name';
+$sortOptions=array(
+        'name' => 'name',
+        'status' => 'isenabled',
+        'members' => 'members_count',
+        'lead' => 'lead__lastname',
+        'created' => 'created',
+        'updated' => 'updated',
+        );
+
+$orderWays = array('DESC'=>'DESC', 'ASC'=>'ASC');
+$sort = ($_REQUEST['sort'] && $sortOptions[strtolower($_REQUEST['sort'])]) ? strtolower($_REQUEST['sort']) : 'name';
+
 //Sorting options...
-if($sort && $sortOptions[$sort]) {
-    $order_column =$sortOptions[$sort];
+if ($sort && $sortOptions[$sort]) {
+    $order_column = $sortOptions[$sort];
 }
-$order_column=$order_column?$order_column:'team.name';
 
-if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) {
-    $order=$orderWays[strtoupper($_REQUEST['order'])];
+$order_column = $order_column ? $order_column : 'name';
+
+if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) {
+    $order = $orderWays[strtoupper($_REQUEST['order'])];
+} else {
+    $order = 'ASC';
 }
-$order=$order?$order:'ASC';
 
-if($order_column && strpos($order_column,',')){
+if ($order_column && strpos($order_column,',')) {
     $order_column=str_replace(','," $order,",$order_column);
 }
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($order).'" ';
-$order_by="$order_column $order ";
-
+$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1;
+$count = Team::objects()->count();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$_qstr = $qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order']);
+$pageNav->setURL('teams.php', $_qstr);
+$showing = $pageNav->showing().' '._N('team', 'teams', $count);
 $qstr.='&order='.($order=='DESC'?'ASC':'DESC');
 
-$query="$sql GROUP BY team.team_id ORDER BY $order_by";
-$res=db_query($query);
-if($res && ($num=db_num_rows($res)))
-    $showing=sprintf(__('Showing 1-%1$d of %2$d teams'), $num, $num);
-else
-    $showing=__('No teams found!');
 
 ?>
 <div class="pull-left" style="width:700px;padding-top:5px;">
@@ -65,38 +69,52 @@ else
     </thead>
     <tbody>
     <?php
-        $total=0;
-        $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null;
-        if($res && db_num_rows($res)):
-            while ($row = db_fetch_array($res)) {
+        $ids= ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null;
+        if ($count) {
+            $teams = Team::objects()
+                ->annotate(array(
+                        'members_count'=>SqlAggregate::COUNT('members', true),
+                ))
+                ->order_by(sprintf('%s%s',
+                            strcasecmp($order, 'DESC') ? '' : '-',
+                            $order_column))
+                ->limit($pageNav->getLimit())
+                ->offset($pageNav->getStart());
+
+            foreach ($teams as $team) {
+                $id = $team->getId();
                 $sel=false;
-                if($ids && in_array($row['team_id'],$ids))
+                if ($ids && in_array($id, $ids))
                     $sel=true;
                 ?>
-            <tr id="<?php echo $row['team_id']; ?>">
+            <tr id="<?php echo $id; ?>">
                 <td width=7px>
-                  <input type="checkbox" class="ckb" name="ids[]" value="<?php echo $row['team_id']; ?>"
-                            <?php echo $sel?'checked="checked"':''; ?>> </td>
-                <td><a href="teams.php?id=<?php echo $row['team_id']; ?>"><?php echo $row['name']; ?></a> &nbsp;</td>
-                <td>&nbsp;<?php echo $row['isenabled']?__('Active'):'<b>'.__('Disabled').'</b>'; ?></td>
+                  <input type="checkbox" class="ckb" name="ids[]"
+                  value="<?php echo $id; ?>"
+                            <?php echo $sel ? 'checked="checked"' : ''; ?>> </td>
+                <td><a href="teams.php?id=<?php echo $id; ?>"><?php echo
+                $team->getName(); ?></a> &nbsp;</td>
+                <td>&nbsp;<?php echo $team->isActive() ? __('Active') : '<b>'.__('Disabled').'</b>'; ?></td>
                 <td style="text-align:right;padding-right:25px">&nbsp;&nbsp;
-                    <?php if($row['members']>0) { ?>
-                        <a href="staff.php?tid=<?php echo $row['team_id']; ?>"><?php echo $row['members']; ?></a>
-                    <?php }else{ ?> 0
+                    <?php if ($team->members_count > 0) { ?>
+                        <a href="staff.php?tid=<?php echo $id; ?>"><?php
+                            echo $team->members_count; ?></a>
+                    <?php } else { ?> 0
                     <?php } ?>
                     &nbsp;
                 </td>
-                <td><a href="staff.php?id=<?php echo $row['lead_id']; ?>"><?php echo $row['team_lead']; ?>&nbsp;</a></td>
-                <td><?php echo Format::date($row['created']); ?>&nbsp;</td>
-                <td><?php echo Format::datetime($row['updated']); ?>&nbsp;</td>
+                <td><a href="staff.php?id=<?php
+                    echo $team->getLeadId(); ?>"><?php echo $team->lead ?: ''; ?>&nbsp;</a></td>
+                <td><?php echo Format::date($team->created); ?>&nbsp;</td>
+                <td><?php echo Format::datetime($team->updated); ?>&nbsp;</td>
             </tr>
             <?php
-            } //end of while.
-        endif; ?>
+            } //end of foreach
+        }?>
     <tfoot>
      <tr>
         <td colspan="7">
-            <?php if($res && $num){ ?>
+            <?php if ($count){ ?>
             <?php echo __('Select');?>:&nbsp;
             <a id="selectAll" href="#ckb"><?php echo __('All');?></a>&nbsp;&nbsp;
             <a id="selectNone" href="#ckb"><?php echo __('None');?></a>&nbsp;&nbsp;
@@ -109,7 +127,8 @@ else
     </tfoot>
 </table>
 <?php
-if($res && $num): //Show options..
+if ($count): //Show options..
+     echo '<div>&nbsp;'.__('Page').':'.$pageNav->getPageLinks().'&nbsp;</div>';
 ?>
 <p class="centered" id="actions">
     <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" >
diff --git a/include/staff/templates/ticket-preview.tmpl.php b/include/staff/templates/ticket-preview.tmpl.php
index 4d6e301961aa4bdccc56b78e01577ad91eb94759..55276c6d62b9d01aad30773a532d672295eb805a 100644
--- a/include/staff/templates/ticket-preview.tmpl.php
+++ b/include/staff/templates/ticket-preview.tmpl.php
@@ -6,6 +6,7 @@
 
 $staff=$ticket->getStaff();
 $lock=$ticket->getLock();
+$role=$thisstaff->getRole($ticket->getDeptId());
 $error=$msg=$warn=null;
 
 if($lock && $lock->getStaffId()==$thisstaff->getId())
@@ -154,15 +155,15 @@ if($ticket->getNumNotes())
 if($ticket->isOpen())
     $options[]=array('action'=>__('Reply'),'url'=>"tickets.php?id=$tid#reply");
 
-if($thisstaff->canAssignTickets())
+if ($role->canAssignTickets())
     $options[]=array('action'=>($ticket->isAssigned()?__('Reassign'):__('Assign')),'url'=>"tickets.php?id=$tid#assign");
 
-if($thisstaff->canTransferTickets())
+if ($role->canTransferTickets())
     $options[]=array('action'=>__('Transfer'),'url'=>"tickets.php?id=$tid#transfer");
 
 $options[]=array('action'=>__('Post Note'),'url'=>"tickets.php?id=$tid#note");
 
-if($thisstaff->canEditTickets())
+if ($role->canEditTickets())
     $options[]=array('action'=>__('Edit Ticket'),'url'=>"tickets.php?id=$tid&a=edit");
 
 if($options) {
diff --git a/include/staff/templates/tickets.tmpl.php b/include/staff/templates/tickets.tmpl.php
index 67f59864dd13692813ea64850ae5a87842397bed..31872def97e03e8c9b94616ce57587d964bf4a6e 100644
--- a/include/staff/templates/tickets.tmpl.php
+++ b/include/staff/templates/tickets.tmpl.php
@@ -1,7 +1,7 @@
 <?php
 
 $select ='SELECT ticket.ticket_id,ticket.`number`,ticket.dept_id,ticket.staff_id,ticket.team_id, ticket.user_id '
-        .' ,dept.dept_name,status.name as status,ticket.source,ticket.isoverdue,ticket.isanswered,ticket.created '
+        .' ,dept.name as department, status.name as status,ticket.source,ticket.isoverdue,ticket.isanswered,ticket.created '
         .' ,CAST(GREATEST(IFNULL(ticket.lastmessage, 0), IFNULL(ticket.reopened, 0), ticket.created) as datetime) as effective_date '
         .' ,CONCAT_WS(" ", staff.firstname, staff.lastname) as staff, team.name as team '
         .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned '
@@ -156,7 +156,7 @@ if ($results) { ?>
             </td>
             <?php
             if ($user) { ?>
-            <td><?php echo Format::truncate($row['dept_name'], 40); ?></td>
+            <td><?php echo Format::truncate($row['department'], 40); ?></td>
             <td>&nbsp;<?php echo $assigned; ?></td>
             <?php
             } else { ?>
diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php
index ca63955bbf3970a0cfb6bf2b5e82613f3c5ff48e..5b464cdca0a2b2997e37125b4b810026ffaae02e 100644
--- a/include/staff/ticket-edit.inc.php
+++ b/include/staff/ticket-edit.inc.php
@@ -1,5 +1,10 @@
 <?php
-if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->canEditTickets() || !$ticket) die('Access Denied');
+if (!defined('OSTSCPINC')
+        || !$thisstaff
+        || !$ticket
+        || !($role=$thisstaff->getRole($ticket->getDeptId()))
+        || !$role->canEditTickets())
+    die('Access Denied');
 
 $info=Format::htmlchars(($errors && $_POST)?$_POST:$ticket->getUpdateInfo());
 if ($_POST)
diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php
index 0c0c6dfe2b61199b0ac658ef82cc2588910ec0a5..9af71ea08c13084ff2aa55c4e25d8f6c87ac4ddc 100644
--- a/include/staff/ticket-open.inc.php
+++ b/include/staff/ticket-open.inc.php
@@ -272,7 +272,7 @@ if ($_POST)
         <tbody>
         <?php
         //is the user allowed to post replies??
-        if($thisstaff->canPostReply()) { ?>
+        if($thisstaff->getRole()->canPostReply()) { ?>
         <tr>
             <th colspan="2">
                 <em><strong><?php echo __('Response');?></strong>: <?php echo __('Optional response to the above issue.');?></em>
diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php
index 207d573c5da4c312a1f7ba9448167cfb2a7dec72..f3ab38354b3c3b186fa0f04260df8e3342046fb3 100644
--- a/include/staff/ticket-view.inc.php
+++ b/include/staff/ticket-view.inc.php
@@ -14,6 +14,7 @@ if($cfg->getLockTime() && !$ticket->acquireLock($thisstaff->getId(),$cfg->getLoc
 
 //Get the goodies.
 $dept  = $ticket->getDept();  //Dept
+$role  = $thisstaff->getRole($dept);
 $staff = $ticket->getStaff(); //Assigned or closed by..
 $user  = $ticket->getOwner(); //Ticket User (EndUser)
 $team  = $ticket->getTeam();  //Assigned team.
@@ -59,8 +60,8 @@ if($ticket->isOverdue())
         </td>
         <td width="auto" class="flush-right has_bottom_border">
             <?php
-            if ($thisstaff->canBanEmails()
-                    || $thisstaff->canEditTickets()
+            if ($role->canBanEmails()
+                    || $role->canEditTickets()
                     || ($dept && $dept->isManager($thisstaff))) { ?>
             <span class="action-button pull-right" data-dropdown="#action-dropdown-more">
                 <i class="icon-caret-down pull-right"></i>
@@ -71,12 +72,12 @@ if($ticket->isOverdue())
             // Status change options
             echo TicketStatus::status_options();
 
-            if ($thisstaff->canEditTickets()) { ?>
+            if ($role->canEditTickets()) { ?>
                 <a class="action-button pull-right" href="tickets.php?id=<?php echo $ticket->getId(); ?>&a=edit"><i class="icon-edit"></i> <?php
                     echo __('Edit'); ?></a>
             <?php
             }
-            if ($ticket->isOpen() && !$ticket->isAssigned() && $thisstaff->canAssignTickets()) {?>
+            if ($ticket->isOpen() && !$ticket->isAssigned() && $role->canAssignTickets()) {?>
                 <a id="ticket-claim" class="action-button pull-right confirm-action" href="#claim"><i class="icon-user"></i> <?php
                     echo __('Claim'); ?></a>
 
@@ -98,13 +99,13 @@ if($ticket->isOverdue())
             <div id="action-dropdown-more" class="action-dropdown anchor-right">
               <ul>
                 <?php
-                 if($thisstaff->canEditTickets()) { ?>
+                 if ($role->canEditTickets()) { ?>
                     <li><a class="change-user" href="#tickets/<?php
                     echo $ticket->getId(); ?>/change-user"><i class="icon-user"></i> <?php
                     echo __('Change Owner'); ?></a></li>
                 <?php
                  }
-                 if($thisstaff->canDeleteTickets()) {
+                 if ($role->canDeleteTickets()) {
                      ?>
                     <li><a class="ticket-action" href="#tickets/<?php
                     echo $ticket->getId(); ?>/status/delete"
@@ -142,7 +143,7 @@ if($ticket->isOverdue())
                     return false"
                     ><i class="icon-paste"></i> <?php echo __('Manage Forms'); ?></a></li>
 
-<?php           if($thisstaff->canBanEmails()) {
+<?php           if ($role->canBanEmails()) {
                      if(!$emailBanned) {?>
                         <li><a class="confirm-action" id="ticket-banemail"
                             href="#banemail"><i class="icon-ban-circle"></i> <?php echo sprintf(
@@ -446,24 +447,24 @@ $tcount+= $ticket->getNumNotes();
 <div id="response_options">
     <ul class="tabs">
         <?php
-        if($thisstaff->canPostReply()) { ?>
+        if ($role->canPostReply()) { ?>
         <li><a id="reply_tab" href="#reply"><?php echo __('Post Reply');?></a></li>
         <?php
         } ?>
         <li><a id="note_tab" href="#note"><?php echo __('Post Internal Note');?></a></li>
         <?php
-        if($thisstaff->canTransferTickets()) { ?>
+        if ($role->canTransferTickets()) { ?>
         <li><a id="transfer_tab" href="#transfer"><?php echo __('Department Transfer');?></a></li>
         <?php
         }
 
-        if($thisstaff->canAssignTickets()) { ?>
+        if ($role->canAssignTickets()) { ?>
         <li><a id="assign_tab" href="#assign"><?php echo $ticket->isAssigned()?__('Reassign Ticket'):__('Assign Ticket'); ?></a></li>
         <?php
         } ?>
     </ul>
     <?php
-    if($thisstaff->canPostReply()) { ?>
+    if ($role->canPostReply()) { ?>
     <form id="reply" class="tab_content" action="tickets.php?id=<?php echo $ticket->getId(); ?>#reply" name="reply" method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
         <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>">
@@ -621,7 +622,7 @@ print $response_form->getField('attachments')->render();
                     <?php
                     $statusId = $info['reply_status_id'] ?: $ticket->getStatusId();
                     $states = array('open');
-                    if ($thisstaff->canCloseTickets() && !$outstanding)
+                    if ($role->canCloseTickets() && !$outstanding)
                         $states = array_merge($states, array('closed'));
 
                     foreach (TicketStatusList::getStatuses(
@@ -704,7 +705,7 @@ print $note_form->getField('attachments')->render();
                         <?php
                         $statusId = $info['note_status_id'] ?: $ticket->getStatusId();
                         $states = array('open');
-                        if ($thisstaff->canCloseTickets())
+                        if ($role->canCloseTickets())
                             $states = array_merge($states, array('closed'));
                         foreach (TicketStatusList::getStatuses(
                                     array('states' => $states)) as $s) {
@@ -730,7 +731,7 @@ print $note_form->getField('attachments')->render();
        </p>
    </form>
     <?php
-    if($thisstaff->canTransferTickets()) { ?>
+    if ($role->canTransferTickets()) { ?>
     <form id="transfer" class="tab_content" action="tickets.php?id=<?php echo $ticket->getId(); ?>#transfer" name="transfer" method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
         <input type="hidden" name="ticket_id" value="<?php echo $ticket->getId(); ?>">
@@ -790,7 +791,7 @@ print $note_form->getField('attachments')->render();
     <?php
     } ?>
     <?php
-    if($thisstaff->canAssignTickets()) { ?>
+    if ($role->canAssignTickets()) { ?>
     <form id="assign" class="tab_content" action="tickets.php?id=<?php echo $ticket->getId(); ?>#assign" name="assign" method="post" enctype="multipart/form-data">
         <?php csrf_token(); ?>
         <input type="hidden" name="id" value="<?php echo $ticket->getId(); ?>">
diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php
index 5f852956dd1f570195bf3efb7f6d480df7ace91d..a1591aa36ea3f763601b59a3b824ee34c095bc9a 100644
--- a/include/staff/tickets.inc.php
+++ b/include/staff/tickets.inc.php
@@ -75,7 +75,7 @@ $tickets->filter(Q::any($visibility));
 
 // Select pertinent columns
 // ------------------------------------------------------------
-$tickets->values('lock__lock_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__name', 'status__state', 'dept_id', 'dept__dept_name', 'user__name', 'lastupdate');
+$tickets->values('lock__lock_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__name', 'status__state', 'dept_id', 'dept__name', 'user__name', 'lastupdate');
 
 // Apply requested quick filter
 
@@ -240,7 +240,7 @@ $_SESSION[':Q:tickets'] = $tickets;
                     $flag='overdue';
 
                 $lc='';
-                $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__dept_name']);
+                $dept = Dept::getLocalById($T['dept_id'], 'name', $T['dept__name']);
                 if($showassigned) {
                     if($T['staff_id'])
                         $lc=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate((string) new PersonsName($T['staff__firstname'], $T['staff__lastname']),40));
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 776ffc839a61c0b88c0387008deb5ea18c453bce..ffd1538e853ca6f5780cc554c6aaf40d9b013416 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-1ee831c854fe9f35115a3e672916bb91
+c7c828356c88b462ba2e3e1437dca0df
diff --git a/include/upgrader/streams/core/1ee831c8-c7c82835.cleanup.sql b/include/upgrader/streams/core/1ee831c8-c7c82835.cleanup.sql
new file mode 100644
index 0000000000000000000000000000000000000000..0355df4757646be61acf5e727ed2761f233c38a1
--- /dev/null
+++ b/include/upgrader/streams/core/1ee831c8-c7c82835.cleanup.sql
@@ -0,0 +1,17 @@
+-- drop old permissions from group table
+ALTER TABLE `%TABLE_PREFIX%group`
+    DROP `group_enabled`,
+    DROP `can_create_tickets`,
+    DROP `can_edit_tickets`,
+    DROP `can_post_ticket_reply`,
+    DROP `can_delete_tickets`,
+    DROP `can_close_tickets`,
+    DROP `can_assign_tickets`,
+    DROP `can_transfer_tickets`,
+    DROP `can_ban_emails`,
+    DROP `can_manage_premade`,
+    DROP `can_manage_faq`,
+    DROP `can_view_staff_stats`;
+
+-- drop useless updated column
+ALTER TABLE  `%TABLE_PREFIX%team_member` DROP  `updated`;
diff --git a/include/upgrader/streams/core/1ee831c8-c7c82835.patch.sql b/include/upgrader/streams/core/1ee831c8-c7c82835.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..4c886287491690966973a793386ffc19262b6e74
--- /dev/null
+++ b/include/upgrader/streams/core/1ee831c8-c7c82835.patch.sql
@@ -0,0 +1,41 @@
+/**
+ * @signature c7c828356c88b462ba2e3e1437dca0df
+ * @version v1.9.6
+ * @title Add role-based access
+ *
+ * This patch adds support for role based access to group and departments
+ *
+ */
+
+CREATE TABLE `%TABLE_PREFIX%role` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `flags` int(10) unsigned NOT NULL DEFAULT '1',
+  `name` varchar(64) DEFAULT NULL,
+  `notes` text,
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) DEFAULT CHARSET=utf8;
+
+ALTER TABLE  `%TABLE_PREFIX%group_dept_access`
+    ADD  `role_id` INT UNSIGNED NOT NULL DEFAULT  '0';
+
+ALTER TABLE  `%TABLE_PREFIX%groups`
+    ADD  `role_id` INT UNSIGNED NOT NULL DEFAULT  '0' AFTER  `group_id` ,
+    ADD  `flags` INT UNSIGNED NOT NULL DEFAULT  '1' AFTER  `role_id`,
+    CHANGE  `group_name`  `name` VARCHAR(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT  '',
+    CHANGE  `group_id`  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
+    ADD INDEX (`role_id`);
+
+RENAME TABLE  `%TABLE_PREFIX%groups` TO  `%TABLE_PREFIX%group`;
+
+-- department changes
+ALTER TABLE  `%TABLE_PREFIX%department`
+    CHANGE  `dept_id`  `id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT,
+    CHANGE  `dept_signature`  `signature` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+    CHANGE  `dept_name`  `name` VARCHAR( 128 ) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT  '';
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `schema_signature`='c7c828356c88b462ba2e3e1437dca0df';
diff --git a/include/upgrader/streams/core/1ee831c8-c7c82835.task.php b/include/upgrader/streams/core/1ee831c8-c7c82835.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..09bf7a6def9a4abc5f762c59d0b0fe8a16e2a3f4
--- /dev/null
+++ b/include/upgrader/streams/core/1ee831c8-c7c82835.task.php
@@ -0,0 +1,43 @@
+<?php
+class GroupRoles extends MigrationTask {
+
+    var $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' => 'kb.premade',
+            'can_manage_faq' => 'kb.faq',
+            'can_view_staff_stats' => 'stats.agents');
+
+    function run($max_time) {
+        global $cfg;
+        // Select existing groups and create roles matching the current
+        // settings
+        foreach (Group::objects() as $group) {
+            $ht=array(
+                    'flags=1',
+                    'name' => sprintf('%s %s', $group->getName(),
+                        __('Role')),
+                    'notes' => $group->getName()
+                    );
+            $perms = array();
+            foreach (self::$pmap as  $k => $v) {
+                if ($group->{$k})
+                    $perms[] = $v;
+            }
+
+            $ht['permissions'] = $perms;
+
+            $role = Role::__create($ht);
+            $group->role_id =  $role->getId();
+            $group->save();
+        }
+    }
+}
+
+return 'GroupRoles';
diff --git a/scp/canned.php b/scp/canned.php
index 5de2f87b50e128d08c615275f9c6e2e25b5d14e8..d6a9ffa9af4858d4a0c8585d09daf4575ea85007 100644
--- a/scp/canned.php
+++ b/scp/canned.php
@@ -17,7 +17,7 @@ require('staff.inc.php');
 include_once(INCLUDE_DIR.'class.canned.php');
 
 /* check permission */
-if(!$thisstaff || !$thisstaff->canManageCannedResponses()) {
+if(!$thisstaff || !$thisstaff->getRole()->canManageCannedResponses()) {
     header('Location: kb.php');
     exit;
 }
@@ -35,7 +35,7 @@ $canned_form = new Form(array(
    )),
 ));
 
-if($_POST && $thisstaff->canManageCannedResponses()) {
+if($_POST && $thisstaff->getRole()->canManageCannedResponses()) {
     switch(strtolower($_POST['do'])) {
         case 'update':
             if(!$canned) {
diff --git a/scp/categories.php b/scp/categories.php
index 422ec184135fe7a1dc6af185d02aa3a8b6a135cb..059e52d8064cfd12b6df0167e17ae299bcd2eede 100644
--- a/scp/categories.php
+++ b/scp/categories.php
@@ -17,7 +17,7 @@ require('staff.inc.php');
 include_once(INCLUDE_DIR.'class.category.php');
 
 /* check permission */
-if(!$thisstaff || !$thisstaff->canManageFAQ()) {
+if(!$thisstaff || !$thisstaff->getRole()->canManageFAQ()) {
     header('Location: kb.php');
     exit;
 }
diff --git a/scp/css/scp.css b/scp/css/scp.css
index b97e1cc2bcfdc37b4a840095165b790d043311dc..add15fde893acb7e58be9e9a6f639e2492f61424 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -489,8 +489,12 @@ table.list tbody tr:hover td, table.list tbody tr.highlight td {  background: #F
 table.list tbody tr:hover td.nohover, table.list tbody tr.highlight td.nohover {}
 
 
-table.list tfoot td {
+table tfoot td {
     background:#eee;
+    padding: 1px;
+}
+
+table.list tfoot td {
     padding: 2px;
 }
 
@@ -1952,7 +1956,7 @@ tr.disabled th {
 
 .tab_content {
     position: relative;
-    padding: 10px;
+    padding: 5px 0;
 }
 .left-tabs {
     margin-left: 48px;
diff --git a/scp/faq.php b/scp/faq.php
index 196b0862b1a2f7bb1e0b89e58bc099c40e634b22..322420fd796e389b8a7f1b19b5203ae1332db4e0 100644
--- a/scp/faq.php
+++ b/scp/faq.php
@@ -153,11 +153,11 @@ else {
 $inc='faq-categories.inc.php'; //FAQs landing page.
 if($faq) {
     $inc='faq-view.inc.php';
-    if($_REQUEST['a']=='edit' && $thisstaff->canManageFAQ())
+    if($_REQUEST['a']=='edit' && $thisstaff->getRole()->canManageFAQ())
         $inc='faq.inc.php';
     elseif ($_REQUEST['a'] == 'print')
         return $faq->printPdf();
-}elseif($_REQUEST['a']=='add' && $thisstaff->canManageFAQ()) {
+}elseif($_REQUEST['a']=='add' && $thisstaff->getRole()->canManageFAQ()) {
     $inc='faq.inc.php';
 } elseif($category && $_REQUEST['a']!='search') {
     $inc='faq-category.inc.php';
diff --git a/scp/groups.php b/scp/groups.php
index 8f273e1b11db8bf69320f2f94f4d209f0f9d5af0..494824bb108c2c83b5477ab56ed95d073f772361 100644
--- a/scp/groups.php
+++ b/scp/groups.php
@@ -22,39 +22,52 @@ if($_REQUEST['id'] && !($group=Group::lookup($_REQUEST['id'])))
 if($_POST){
     switch(strtolower($_POST['do'])){
         case 'update':
-            if(!$group){
+            if (!$group) {
                 $errors['err']=sprintf(__('%s: Unknown or invalid'), __('group'));
-            }elseif($group->update($_POST,$errors)){
+            } elseif (!$_POST['isactive']
+                    && ($thisstaff->getGroupId() == $group->getId())) {
+                $errors['err'] = sprintf(
+                        __("As an admin, you cannot %s a group you belong to - you might lockout all admins!"),
+                        __('disable'));
+            } elseif ($group->update($_POST, $errors)) {
                 $msg=sprintf(__('Successfully updated %s'),
                     __('this group'));
-            }elseif(!$errors['err']){
+            } elseif (!$errors['err']) {
                 $errors['err']=sprintf(__('Unable to update %s. Correct error(s) below and try again!'),
                     __('this group'));
             }
             break;
-        case 'create':
-            $group = Group::create();
-            if(($group->update($_POST,$errors))){
+        case 'add':
+            $_group = Group::create();
+            if (($_group->update($_POST,$errors))) {
                 $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['name']));
                 $_REQUEST['a']=null;
-            }elseif(!$errors['err']){
+            } elseif(!$errors['err']) {
                 $errors['err']=sprintf(__('Unable to add %s. Correct error(s) below and try again.'),
                     __('this group'));
             }
             break;
         case 'mass_process':
-            if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) {
+            $action = strtolower($_POST['a']);
+            if (!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) {
                 $errors['err'] = sprintf(__('You must select at least %s.'), __('one group'));
-            } elseif(in_array($thisstaff->getGroupId(), $_POST['ids'])) {
-                $errors['err'] = __("As an admin, you cannot disable/delete a group you belong to - you might lockout all admins!");
+            } elseif(in_array($thisstaff->getGroupId(), $_POST['ids'])
+                    && in_array($action, array('disable', 'delete'))) {
+                $errors['err'] = sprintf(
+                        __("As an admin, you cannot %s a group you belong to - you might lockout all admins!"),
+                        __('disable or delete'));
             } else {
-                $count=count($_POST['ids']);
-                switch(strtolower($_POST['a'])) {
+                $count = count($_POST['ids']);
+                switch($action) {
                     case 'enable':
-                        $sql='UPDATE '.GROUP_TABLE.' SET group_enabled=1, updated=NOW() '
-                            .' WHERE group_id IN ('.implode(',', db_input($_POST['ids'])).')';
-
-                        if(db_query($sql) && ($num=db_affected_rows())){
+                        $num = Group::objects()->filter(array(
+                            'id__in' => $_POST['ids']
+                        ))->update(array(
+                            'flags'=> SqlExpression::bitor(
+                                new SqlField('flags'),
+                                Group::FLAG_ENABLED)
+                        ));
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully activated %s'),
                                     _N('selected group', 'selected groups', $count));
@@ -67,9 +80,15 @@ if($_POST){
                         }
                         break;
                     case 'disable':
-                        $sql='UPDATE '.GROUP_TABLE.' SET group_enabled=0, updated=NOW() '
-                            .' WHERE group_id IN ('.implode(',', db_input($_POST['ids'])).')';
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        $num = Group::objects()->filter(array(
+                            'id__in' => $_POST['ids']
+                        ))->update(array(
+                            'flags'=> SqlExpression::bitand(
+                                new SqlField('flags'),
+                                (~Group::FLAG_ENABLED))
+                        ));
+
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully disabled %s'),
                                     _N('selected group', 'selected groups', $count));
diff --git a/scp/roles.php b/scp/roles.php
new file mode 100644
index 0000000000000000000000000000000000000000..43c097f5c09c96400feae5b41d02ac8c240053fd
--- /dev/null
+++ b/scp/roles.php
@@ -0,0 +1,139 @@
+<?php
+/*********************************************************************
+    roles.php
+
+    Agent's roles
+
+    Peter Rotich <peter@osticket.com>
+    Copyright (c)  2014 osTicket
+    http://www.osticket.com
+
+    Released under the GNU General Public License WITHOUT ANY WARRANTY.
+    See LICENSE.TXT for details.
+
+    vim: expandtab sw=4 ts=4 sts=4:
+**********************************************************************/
+
+require('admin.inc.php');
+
+$errors = array();
+$role=null;
+if ($_REQUEST['id'] && !($role = Role::lookup($_REQUEST['id'])))
+    $errors['err'] = sprintf(__('%s: Unknown or invalid ID.'),
+        __('Role'));
+
+if ($_POST) {
+    switch (strtolower($_POST['do'])) {
+    case 'update':
+        if (!$role) {
+            $errors['err'] = sprintf(__('%s: Unknown or invalid ID.'),
+                    __('Role'));
+        } elseif ($role->update($_POST, $errors)) {
+            $msg = __('Role updated successfully');
+        } elseif ($errors) {
+            $errors['err'] = $errors['err'] ?:
+                sprintf(__('Unable to update %s. Correct error(s) below and try again!'),
+                    __('this role'));
+        } else {
+            $errors['err'] = sprintf(__('Unable to update %s.'), __('this role'))
+                    .' '.__('Internal error occurred');
+        }
+        break;
+    case 'add':
+        $_role = Role::create();
+        if ($_role->update($_POST, $errors)) {
+            $msg = sprintf(__('Successfully added %s'),
+                    __('role'));
+        } elseif ($errors) {
+            $errors['err'] = $errors['err'] ?:
+                sprintf(__('Unable to add %s. Correct error(s) below and try again.'),
+                    __('role'));
+        } else {
+            $errors['err'] = sprintf(__('Unable to add %s.'), __('role'))
+                    .' '.__('Internal error occurred');
+        }
+        break;
+    case 'mass_process':
+        if (!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) {
+            $errors['err'] = sprintf(__('You must select at least %s'),
+                    __('one role'));
+        } else {
+            $count = count($_POST['ids']);
+            switch(strtolower($_POST['a'])) {
+            case 'enable':
+                $num = Role::objects()->filter(array(
+                    'id__in' => $_POST['ids']
+                ))->update(array(
+                    'flags'=> SqlExpression::bitor(
+                        new SqlField('flags'),
+                        Role::FLAG_ENABLED)
+                ));
+                if ($num) {
+                    if($num==$count)
+                        $msg = sprintf(__('Successfully enabled %s'),
+                            _N('selected role', 'selected roles', $count));
+                    else
+                        $warn = sprintf(__('%1$d of %2$d %3$s enabled'), $num, $count,
+                            _N('selected role', 'selected roles', $count));
+                } else {
+                    $errors['err'] = sprintf(__('Unable to enable %s'),
+                        _N('selected role', 'selected roles', $count));
+                }
+                break;
+            case 'disable':
+                $num = Role::objects()->filter(array(
+                    'id__in' => $_POST['ids']
+                ))->update(array(
+                    'flags'=> SqlExpression::bitand(
+                        new SqlField('flags'),
+                        (~Role::FLAG_ENABLED))
+                ));
+
+                if ($num) {
+                    if($num==$count)
+                        $msg = sprintf(__('Successfully disabled %s'),
+                            _N('selected role', 'selected roles', $count));
+                    else
+                        $warn = sprintf(__('%1$d of %2$d %3$s disabled'), $num, $count,
+                            _N('selected role', 'selected roles', $count));
+                } else {
+                    $errors['err'] = sprintf(__('Unable to disable %s'),
+                        _N('selected role', 'selected roles', $count));
+                }
+                break;
+            case 'delete':
+                $i=0;
+                foreach ($_POST['ids'] as $k=>$v) {
+                    if (($r=Role::lookup($v)) && $r->isDeleteable() && $r->delete())
+                        $i++;
+                }
+                if ($i && $i==$count)
+                    $msg = sprintf(__('Successfully deleted %s'),
+                            _N('selected role', 'selected roles', $count));
+                elseif ($i > 0)
+                    $warn = sprintf(__('%1$d of %2$d %3$s deleted'), $i, $count,
+                            _N('selected role', 'selected roles', $count));
+                elseif (!$errors['err'])
+                    $errors['err'] = sprintf(__('Unable to delete %s — they may be in use.'),
+                            _N('selected role', 'selected roles', $count));
+                break;
+            default:
+                $errors['err'] =  __('Unknown action');
+            }
+        }
+        break;
+    }
+}
+
+$page='roles.inc.php';
+if($role || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'], 'add'))) {
+    $page='role.inc.php';
+    $ost->addExtraHeader('<meta name="tip-namespace" content="agents.role" />',
+        "$('#content').data('tipNamespace', 'agents.role');");
+}
+
+$nav->setTabActive('staff');
+require(STAFFINC_DIR.'header.inc.php');
+require(STAFFINC_DIR.$page);
+include(STAFFINC_DIR.'footer.inc.php');
+?>
diff --git a/scp/slas.php b/scp/slas.php
index b47c73092e21344b8b81dceba0f3f2036a1ad697..f57ba70e7266952253b442695cb56dc6c8e128aa 100644
--- a/scp/slas.php
+++ b/scp/slas.php
@@ -36,11 +36,12 @@ if($_POST){
             }
             break;
         case 'add':
-            if(($id=SLA::create($_POST,$errors))){
+            $_sla = SLA::create();
+            if (($_sla->update($_POST, $errors))) {
                 $msg=sprintf(__('Successfully added %s'),
                     __('a SLA plan'));
                 $_REQUEST['a']=null;
-            }elseif(!$errors['err']){
+            } elseif (!$errors['err']) {
                 $errors['err']=sprintf(__('Unable to add %s. Correct error(s) below and try again.'),
                     __('this SLA plan'));
             }
@@ -53,10 +54,12 @@ if($_POST){
                 $count=count($_POST['ids']);
                 switch(strtolower($_POST['a'])) {
                     case 'enable':
-                        $sql='UPDATE '.SLA_TABLE.' SET isactive=1 '
-                            .' WHERE id IN ('.implode(',', db_input($_POST['ids'])).')';
-
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        $num = SLA::objects()->filter(array(
+                            'id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isactive' => 1
+                        ));
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully enabled %s'),
                                     _N('selected SLA plan', 'selected SLA plans', $count));
@@ -69,9 +72,13 @@ if($_POST){
                         }
                         break;
                     case 'disable':
-                        $sql='UPDATE '.SLA_TABLE.' SET isactive=0 '
-                            .' WHERE id IN ('.implode(',', db_input($_POST['ids'])).')';
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        $num = SLA::objects()->filter(array(
+                            'id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isactive' => 0
+                        ));
+
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully disabled %s'),
                                     _N('selected SLA plan', 'selected SLA plans', $count));
@@ -85,7 +92,7 @@ if($_POST){
                         break;
                     case 'delete':
                         $i=0;
-                        foreach($_POST['ids'] as $k=>$v) {
+                        foreach ($_POST['ids'] as $k => $v) {
                             if (($p=SLA::lookup($v))
                                 && $p->getId() != $cfg->getDefaultSLAId()
                                 && $p->delete())
diff --git a/scp/staff.php b/scp/staff.php
index 384af7df0771efdeafd05a8684aa03114950b899..17a106e93c41cbe16da59755ffb0b7fdefd9563b 100644
--- a/scp/staff.php
+++ b/scp/staff.php
@@ -52,10 +52,13 @@ if($_POST){
                 $count=count($_POST['ids']);
                 switch(strtolower($_POST['a'])) {
                     case 'enable':
-                        $sql='UPDATE '.STAFF_TABLE.' SET isactive=1 '
-                            .' WHERE staff_id IN ('.implode(',', db_input($_POST['ids'])).')';
+                        $num = Staff::objects()->filter(array(
+                            'staff_id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isactive' => 1
+                        ));
 
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf('Successfully activated %s',
                                     _N('selected agent', 'selected agents', $count));
@@ -68,10 +71,13 @@ if($_POST){
                         }
                         break;
                     case 'disable':
-                        $sql='UPDATE '.STAFF_TABLE.' SET isactive=0 '
-                            .' WHERE staff_id IN ('.implode(',', db_input($_POST['ids'])).') AND staff_id!='.db_input($thisstaff->getId());
+                        $num = Staff::objects()->filter(array(
+                            'staff_id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isactive' => 0
+                        ));
 
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf('Successfully disabled %s',
                                     _N('selected agent', 'selected agents', $count));
@@ -84,6 +90,7 @@ if($_POST){
                         }
                         break;
                     case 'delete':
+                        $i = 0;
                         foreach($_POST['ids'] as $k=>$v) {
                             if($v!=$thisstaff->getId() && ($s=Staff::lookup($v)) && $s->delete())
                                 $i++;
diff --git a/scp/teams.php b/scp/teams.php
index 42fb2cbdd1bd8d38b6b215f4220db5c395558ff8..28e72adbf8e850a6c59a3f85b846514b2222f2d2 100644
--- a/scp/teams.php
+++ b/scp/teams.php
@@ -33,8 +33,8 @@ if($_POST){
             }
             break;
         case 'create':
-            $team = Team::create();
-            if ($team->update($_POST,$errors)) {
+            $_team = Team::create();
+            if (($_team->update($_POST, $errors))){
                 $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['team']));
                 $_REQUEST['a']=null;
             }elseif(!$errors['err']){
@@ -49,10 +49,13 @@ if($_POST){
                 $count=count($_POST['ids']);
                 switch(strtolower($_POST['a'])) {
                     case 'enable':
-                        $sql='UPDATE '.TEAM_TABLE.' SET isenabled=1 '
-                            .' WHERE team_id IN ('.implode(',', db_input($_POST['ids'])).')';
+                        $num = Team::objects()->filter(array(
+                            'team_id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isenabled' => 1
+                        ));
 
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully activated %s'),
                                     _N('selected team', 'selected teams', $count));
@@ -65,10 +68,13 @@ if($_POST){
                         }
                         break;
                     case 'disable':
-                        $sql='UPDATE '.TEAM_TABLE.' SET isenabled=0 '
-                            .' WHERE team_id IN ('.implode(',', db_input($_POST['ids'])).')';
+                        $num = Team::objects()->filter(array(
+                            'team_id__in' => $_POST['ids']
+                        ))->update(array(
+                            'isenabled' => 0
+                        ));
 
-                        if(db_query($sql) && ($num=db_affected_rows())) {
+                        if ($num) {
                             if($num==$count)
                                 $msg = sprintf(__('Successfully disabled %s'),
                                     _N('selected team', 'selected teams', $count));
diff --git a/scp/tickets.php b/scp/tickets.php
index 36f075bc15c125cbb4ab2332ab9a7f1e340c8e80..f65cfd4c189bfd828d4531c5a4d1a7c25c80c9b2 100644
--- a/scp/tickets.php
+++ b/scp/tickets.php
@@ -58,9 +58,10 @@ if($_POST && !$errors):
         //More coffee please.
         $errors=array();
         $lock=$ticket->getLock(); //Ticket lock if any
+        $role = $thistaff->getRole($ticket->getDeptId);
         switch(strtolower($_POST['a'])):
         case 'reply':
-            if(!$thisstaff->canPostReply())
+            if(!$role || !$role->canPostReply())
                 $errors['err'] = __('Action denied. Contact admin for access');
             else {
 
@@ -108,7 +109,7 @@ if($_POST && !$errors):
             break;
         case 'transfer': /** Transfer ticket **/
             //Check permission
-            if(!$thisstaff->canTransferTickets())
+            if(!$role->canTransferTickets())
                 $errors['err']=$errors['transfer'] = __('Action Denied. You are not allowed to transfer tickets.');
             else {
 
@@ -141,7 +142,7 @@ if($_POST && !$errors):
             break;
         case 'assign':
 
-             if(!$thisstaff->canAssignTickets())
+             if(!$role->canAssignTickets())
                  $errors['err']=$errors['assign'] = __('Action Denied. You are not allowed to assign/reassign tickets.');
              else {
 
@@ -212,7 +213,7 @@ if($_POST && !$errors):
             break;
         case 'edit':
         case 'update':
-            if(!$ticket || !$thisstaff->canEditTickets())
+            if(!$ticket || !$role->canEditTickets())
                 $errors['err']=__('Permission Denied. You are not allowed to edit tickets');
             elseif($ticket->update($_POST,$errors)) {
                 $msg=__('Ticket updated successfully');
@@ -240,7 +241,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'claim':
-                    if(!$thisstaff->canAssignTickets()) {
+                    if(!$role->canAssignTickets()) {
                         $errors['err'] = __('Permission Denied. You are not allowed to assign/claim tickets.');
                     } elseif(!$ticket->isOpen()) {
                         $errors['err'] = __('Only open tickets can be assigned');
@@ -286,7 +287,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'banemail':
-                    if(!$thisstaff->canBanEmails()) {
+                    if(!$role->canBanEmails()) {
                         $errors['err']=__('Permission Denied. You are not allowed to ban emails');
                     } elseif(BanList::includes($ticket->getEmail())) {
                         $errors['err']=__('Email already in banlist');
@@ -297,7 +298,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'unbanemail':
-                    if(!$thisstaff->canBanEmails()) {
+                    if(!$role->canBanEmails()) {
                         $errors['err'] = __('Permission Denied. You are not allowed to remove emails from banlist.');
                     } elseif(Banlist::remove($ticket->getEmail())) {
                         $msg = __('Email removed from banlist');
@@ -308,7 +309,7 @@ if($_POST && !$errors):
                     }
                     break;
                 case 'changeuser':
-                    if (!$thisstaff->canEditTickets()) {
+                    if (!$role->canEditTickets()) {
                         $errors['err']=__('Permission Denied. You are not allowed to edit tickets');
                     } elseif (!$_POST['user_id'] || !($user=User::lookup($_POST['user_id']))) {
                         $errors['err'] = __('Unknown user selected');
@@ -471,7 +472,8 @@ if($ticket) {
     $ost->setPageTitle(sprintf(__('Ticket #%s'),$ticket->getNumber()));
     $nav->setActiveSubMenu(-1);
     $inc = 'ticket-view.inc.php';
-    if($_REQUEST['a']=='edit' && $thisstaff->canEditTickets()) {
+    if ($_REQUEST['a']=='edit'
+            && $thisstaff->getRole($ticket->getDeptId())->canEditTickets()) {
         $inc = 'ticket-edit.inc.php';
         if (!$forms) $forms=DynamicFormEntry::forTicket($ticket->getId());
         // Auto add new fields to the entries
diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php
index 0c7ea1f5b2a7029062e40969e3312cccd4a8be08..7831cb65d953118e04d98edec18aac9708a30512 100644
--- a/setup/inc/class.installer.php
+++ b/setup/inc/class.installer.php
@@ -159,25 +159,22 @@ class Installer extends SetupWizard {
 
             Signal::send('system.install', $this);
 
-            $sql='SELECT `id` FROM '.TABLE_PREFIX.'sla ORDER BY `id` LIMIT 1';
+            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'sla` ORDER BY `id` LIMIT 1';
             $sla_id_1 = db_result(db_query($sql, false));
 
-            $sql='SELECT `dept_id` FROM '.TABLE_PREFIX.'department ORDER BY `dept_id` LIMIT 1';
+            $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';
+            $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 `group_id` FROM '.TABLE_PREFIX.'groups ORDER BY `group_id` LIMIT 1';
+            $sql='SELECT `id` FROM `'.TABLE_PREFIX.'group` ORDER BY `id` LIMIT 1';
             $group_id_1 = db_result(db_query($sql, false));
 
-            $sql='SELECT `value` FROM '.TABLE_PREFIX.'config WHERE namespace=\'core\' and `key`=\'default_timezone_id\' LIMIT 1';
-            $default_timezone = db_result(db_query($sql, false));
-
             //Create admin user.
             $sql='INSERT INTO '.TABLE_PREFIX.'staff SET created=NOW() '
                 .", isactive=1, isadmin=1, group_id='$group_id_1', dept_id='$dept_id_1'"
-                .", timezone_id='$default_timezone', max_page_size=25"
+                .', max_page_size=25'
                 .', email='.db_input($vars['admin_email'])
                 .', firstname='.db_input($vars['fname'])
                 .', lastname='.db_input($vars['lname'])
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index f32ff6d74962ad72ed72886ef9fcefb145d1e039..e35b6a364b6e6de4ee4af0ff4ed01eda630f50ee 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -197,22 +197,22 @@ CREATE TABLE `%TABLE_PREFIX%list_items` (
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%department`;
 CREATE TABLE `%TABLE_PREFIX%department` (
-  `dept_id` int(11) unsigned NOT NULL auto_increment,
+  `id` int(11) unsigned NOT NULL auto_increment,
   `tpl_id` int(10) unsigned NOT NULL default '0',
   `sla_id` int(10) unsigned NOT NULL default '0',
   `email_id` int(10) unsigned NOT NULL default '0',
   `autoresp_email_id` int(10) unsigned NOT NULL default '0',
   `manager_id` int(10) unsigned NOT NULL default '0',
-  `dept_name` varchar(128) NOT NULL default '',
-  `dept_signature` text NOT NULL,
+  `name` varchar(128) NOT NULL default '',
+  `signature` text NOT NULL,
   `ispublic` tinyint(1) unsigned NOT NULL default '1',
   `group_membership` tinyint(1) NOT NULL default '0',
   `ticket_auto_response` tinyint(1) NOT NULL default '1',
   `message_auto_response` tinyint(1) NOT NULL default '0',
   `updated` datetime NOT NULL,
   `created` datetime NOT NULL,
-  PRIMARY KEY  (`dept_id`),
-  UNIQUE KEY `dept_name` (`dept_name`),
+  PRIMARY KEY  (`id`),
+  UNIQUE KEY `name` (`name`),
   KEY `manager_id` (`manager_id`),
   KEY `autoresp_email_id` (`autoresp_email_id`),
   KEY `tpl_id` (`tpl_id`)
@@ -388,35 +388,39 @@ CREATE TABLE `%TABLE_PREFIX%file_chunk` (
   PRIMARY KEY (`file_id`, `chunk_id`)
 ) DEFAULT CHARSET=utf8;
 
-DROP TABLE IF EXISTS `%TABLE_PREFIX%groups`;
-CREATE TABLE `%TABLE_PREFIX%groups` (
-  `group_id` int(10) unsigned NOT NULL auto_increment,
-  `group_enabled` tinyint(1) unsigned NOT NULL default '1',
-  `group_name` varchar(50) NOT NULL default '',
-  `can_create_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `can_edit_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `can_post_ticket_reply` tinyint( 1 ) unsigned NOT NULL DEFAULT  '1',
-  `can_delete_tickets` tinyint(1) unsigned NOT NULL default '0',
-  `can_close_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `can_assign_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `can_transfer_tickets` tinyint(1) unsigned NOT NULL default '1',
-  `can_ban_emails` tinyint(1) unsigned NOT NULL default '0',
-  `can_manage_premade` tinyint(1) unsigned NOT NULL default '0',
-  `can_manage_faq` tinyint(1) unsigned NOT NULL default '0',
-  `can_view_staff_stats` tinyint( 1 ) unsigned NOT NULL DEFAULT  '0',
+DROP TABLE IF EXISTS `%TABLE_PREFIX%group`;
+CREATE TABLE `%TABLE_PREFIX%group` (
+  `id` int(10) unsigned NOT NULL auto_increment,
+  `role_id` int(11) unsigned NOT NULL,
+  `flags` int(11) unsigned NOT NULL default '1',
+  `name` varchar(120) NOT NULL default '',
   `notes` text,
   `created` datetime NOT NULL,
   `updated` datetime NOT NULL,
-  PRIMARY KEY  (`group_id`),
-  KEY `group_active` (`group_enabled`)
+  PRIMARY KEY  (`id`),
+  KEY `role_id` (`role_id`)
+) DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%role`;
+CREATE TABLE `%TABLE_PREFIX%role` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `flags` int(10) unsigned NOT NULL DEFAULT '1',
+  `name` varchar(64) DEFAULT NULL,
+  `notes` text,
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%group_dept_access`;
 CREATE TABLE `%TABLE_PREFIX%group_dept_access` (
   `group_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',
   UNIQUE KEY `group_dept` (`group_id`,`dept_id`),
-  KEY `dept_id`  (`dept_id`)
+  KEY `dept_id`  (`dept_id`),
+  KEY `role_id`  (`role_id`)
 ) DEFAULT CHARSET=utf8;
 
 DROP TABLE IF EXISTS `%TABLE_PREFIX%help_topic`;
diff --git a/setup/test/tests/stubs.php b/setup/test/tests/stubs.php
index 5e3904e62d1ba00ccf14b1537d851bb8a81f6d6c..dbd057d515db677b957cd3d8345ad26872ecebd8 100644
--- a/setup/test/tests/stubs.php
+++ b/setup/test/tests/stubs.php
@@ -30,6 +30,7 @@ class mysqli_stmt {
 class mysqli_result {
     function free() {}
     function free_result() {}
+    function fetch_fields() {}
 }
 
 class ReflectionClass {
@@ -122,4 +123,35 @@ class IntlBreakIterator {
 class SqlFunction {
     static function NOW() {}
 }
+
+class SqlExpression {
+    static function plus() {}
+    static function times() {}
+    static function bitor() {}
+    static function bitand() {}
+}
+
+class SqlInterval {
+    static function MINUTE() {}
+    static function DAY() {}
+}
+
+class SqlAggregate {
+    static function COUNT() {}
+}
+
+class Q {
+    static function ANY() {}
+}
+
+class IntlDateFormatter {
+    function setPattern() {}
+    function getPattern() {}
+    function parse() {}
+}
+
+class ResourceBundle {
+    function getLocales() {}
+}
+
 ?>