diff --git a/bootstrap.php b/bootstrap.php index c7ba0a41575eab6fc47ed3b566359303ab4f1795..6e245a142cb2310166abf2ba65f7a2a0dbe07780 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -80,8 +80,7 @@ class Bootstrap { define('TEAM_TABLE',$prefix.'team'); define('TEAM_MEMBER_TABLE',$prefix.'team_member'); define('DEPT_TABLE',$prefix.'department'); - define('GROUP_TABLE', $prefix.'group'); - define('GROUP_DEPT_TABLE', $prefix.'group_dept_access'); + define('STAFF_DEPT_TABLE', $prefix.'staff_dept_access'); define('ROLE_TABLE', $prefix.'role'); define('FAQ_TABLE',$prefix.'faq'); diff --git a/css/thread.css b/css/thread.css index 41c327a089b234544e5a3ff47d726d0a730b177c..bb02ba597a963606ad29f56d8a742013d77638fd 100644 --- a/css/thread.css +++ b/css/thread.css @@ -396,7 +396,7 @@ .thread-body blockquote, .thread-body pre { font-size: 14px; - line-height: 1.4rem; + line-height: 1.5rem; } /* Adjust plain/text messages posted as <pre> in the thread body to show in diff --git a/include/ajax.admin.php b/include/ajax.admin.php new file mode 100644 index 0000000000000000000000000000000000000000..e5139d8da599486a06c77e60bffc7c29961fa49e --- /dev/null +++ b/include/ajax.admin.php @@ -0,0 +1,193 @@ +<?php + +require_once(INCLUDE_DIR . 'class.dept.php'); +require_once(INCLUDE_DIR . 'class.role.php'); +require_once(INCLUDE_DIR . 'class.team.php'); + +class AdminAjaxAPI extends AjaxController { + + /** + * Ajax: GET /admin/add/department + * + * Uses a dialog to add a new department + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an administrator + */ + function addDepartment() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new DepartmentQuickAddForm($_POST); + + if ($_POST && $form->isValid()) { + $dept = Dept::create(); + $errors = array(); + $vars = $form->getClean(); + $vars += array( + 'group_membership' => Dept::ALERTS_DEPT_AND_GROUPS, + ); + if ($dept->update($vars, $errors)) { + Http::response(201, $this->encode(array( + 'id' => $dept->id, + 'name' => $dept->name, + ), 'application/json')); + } + foreach ($errors as $name=>$desc) + if ($F = $form->getField($name)) + $F->addError($desc); + } + + $title = __("Add New Department"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } + + /** + * Ajax: GET /admin/add/team + * + * Uses a dialog to add a new team + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an adminitrator + */ + function addTeam() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new TeamQuickAddForm($_POST); + + if ($_POST && $form->isValid()) { + $team = Team::create(); + $errors = array(); + $vars = $form->getClean(); + $vars += array( + 'isenabled' => true, + ); + if ($team->update($vars, $errors)) { + Http::response(201, $this->encode(array( + 'id' => $team->getId(), + 'name' => $team->name, + ), 'application/json')); + } + foreach ($errors as $name=>$desc) + if ($F = $form->getField($name)) + $F->addError($desc); + } + + $title = __("Add New Team"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } + + /** + * Ajax: GET /admin/add/role + * + * Uses a dialog to add a new role + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an adminitrator + */ + function addRole() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new RoleQuickAddForm($_POST); + + if ($_POST && $form->isValid()) { + $role = Role::create(); + $errors = array(); + $vars = $form->getClean(); + if ($role->update($vars, $errors)) { + Http::response(201, $this->encode(array( + 'id' => $role->getId(), + 'name' => $role->name, + ), 'application/json')); + } + foreach ($errors as $name=>$desc) + if ($F = $form->getField($name)) + $F->addError($desc); + } + + $title = __("Add New Role"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add-role.tmpl.php'; + } + + function getRolePerms($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if (!($role = Role::lookup($id))) + Http::response(404, 'No such role'); + + return $this->encode($role->getPermissionInfo()); + } + + function addStaff() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new StaffQuickAddForm($_POST); + + if ($_POST && $form->isValid()) { + $staff = Staff::create(); + $errors = array(); + if ($staff->update($form->getClean(), $errors)) { + Http::response(201, $this->encode(array( + 'id' => $staff->getId(), + 'name' => (string) $staff->getName(), + ), 'application/json')); + } + foreach ($errors as $name=>$desc) { + if ($F = $form->getField($name)) { + $F->addError($desc); + unset($errors[$name]); + } + } + $errors['err'] = implode(", ", $errors); + } + + $title = __("Add New Agent"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } +} diff --git a/include/ajax.kbase.php b/include/ajax.kbase.php index 30575ac22b05ee80bcf3aa07d1e52ed9fe5d3b0a..10266e242d680d7fb231831dab3c95c54ef05f9c 100644 --- a/include/ajax.kbase.php +++ b/include/ajax.kbase.php @@ -55,7 +55,7 @@ class KbaseAjaxAPI extends AjaxController { $faq->getId(), $faq->getNumAttachments()); if($thisstaff - && $thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) { + && $thisstaff->hasPerm(FAQ::PERM_MANAGE)) { $resp.=sprintf(' | <a href="faq.php?id=%d&a=edit">'.__('Edit').'</a>',$faq->getId()); } diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php index b6a4002feac751e5a2bbf1a3a6ac34f38ee2db45..c4d73187bf6d2c85c2bf1dc4a7c0f6650174e233 100644 --- a/include/ajax.orgs.php +++ b/include/ajax.orgs.php @@ -54,7 +54,7 @@ class OrgsAjaxAPI extends AjaxController { if(!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(Organization::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif(!($org = Organization::lookup($id))) Http::response(404, 'Unknown organization'); @@ -74,7 +74,7 @@ class OrgsAjaxAPI extends AjaxController { if(!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(Organization::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif(!($org = Organization::lookup($id))) Http::response(404, 'Unknown organization'); @@ -101,7 +101,7 @@ class OrgsAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(Organization::PERM_DELETE)) + elseif (!$thisstaff->hasPerm(Organization::PERM_DELETE)) Http::response(403, 'Permission Denied'); elseif (!($org = Organization::lookup($id))) Http::response(404, 'Unknown organization'); @@ -122,7 +122,7 @@ class OrgsAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(User::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif (!($org = Organization::lookup($id))) Http::response(404, 'Unknown organization'); @@ -145,7 +145,7 @@ class OrgsAjaxAPI extends AjaxController { Format::htmlchars($user->getName())); } else { //Creating new user $form = UserForm::getUserForm()->getForm($_POST); - $can_create = $thisstaff->getRole()->hasPerm(User::PERM_CREATE); + $can_create = $thisstaff->hasPerm(User::PERM_CREATE); if (!($user = User::fromForm($form, $can_create))) $info['error'] = __('Error adding user - try again!'); } @@ -184,7 +184,7 @@ class OrgsAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(Organization::PERM_CREATE)) + elseif (!$thisstaff->hasPerm(Organization::PERM_CREATE)) Http::response(403, 'Permission Denied'); elseif (!($org = Organization::lookup($org_id))) Http::response(404, 'No such organization'); @@ -211,7 +211,7 @@ class OrgsAjaxAPI extends AjaxController { function addOrg() { global $thisstaff; - if (!$thisstaff->getRole()->hasPerm(Organization::PERM_CREATE)) + if (!$thisstaff->hasPerm(Organization::PERM_CREATE)) Http::response(403, 'Permission Denied'); $info = array(); @@ -285,7 +285,7 @@ class OrgsAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, "Login required"); - elseif (!$thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(Organization::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif (!($org = Organization::lookup($org_id))) Http::response(404, "No such ticket"); diff --git a/include/ajax.staff.php b/include/ajax.staff.php new file mode 100644 index 0000000000000000000000000000000000000000..2967eb354f122a471e336d0b8d088d31cd8c2733 --- /dev/null +++ b/include/ajax.staff.php @@ -0,0 +1,188 @@ +<?php + +require_once(INCLUDE_DIR . 'class.staff.php'); + +class StaffAjaxAPI extends AjaxController { + + /** + * Ajax: GET /staff/<id>/set-password + * + * Uses a dialog to add a new department + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an administrator + * 404 - No such agent exists + */ + function setPassword($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if ($id && !($staff = Staff::lookup($id))) + Http::response(404, 'No such agent'); + + $form = new PasswordResetForm($_POST); + if (!$_POST && isset($_SESSION['new-agent-passwd'])) + $form->data($_SESSION['new-agent-passwd']); + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + if ($id == 0) { + // Stash in the session later when creating the user + $_SESSION['new-agent-passwd'] = $clean; + Http::response(201, 'Carry on'); + } + try { + if ($clean['welcome_email']) { + $staff->sendResetEmail(); + } + else { + $staff->setPassword($clean['passwd1'], null); + if ($clean['change_passwd']) + $staff->change_passwd = 1; + } + if ($staff->save()) + Http::response(201, 'Successfully updated'); + } + catch (BadPassword $ex) { + $passwd1 = $form->getField('passwd1'); + $passwd1->addError($ex->getMessage()); + } + catch (PasswordUpdateFailed $ex) { + // TODO: Add a warning banner or crash the update + } + } + + $title = __("Set Agent Password"); + $verb = $id == 0 ? __('Set') : __('Update'); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } + + function changePassword($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$id || $thisstaff->getId() != $id) + Http::response(404, 'No such agent'); + + $form = new PasswordChangeForm($_POST); + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + try { + $thisstaff->setPassword($clean['passwd1'], $clean['current']); + if ($thisstaff->save()) + Http::response(201, 'Successfully updated'); + } + catch (BadPassword $ex) { + $passwd1 = $form->getField('passwd1'); + $passwd1->addError($ex->getMessage()); + } + catch (PasswordUpdateFailed $ex) { + // TODO: Add a warning banner or crash the update + } + } + + $title = __("Change Password"); + $verb = __('Update'); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } + + function getAgentPerms($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if (!($staff = Staff::lookup($id))) + Http::response(404, 'No such agent'); + + return $this->encode($staff->getPermissionInfo()); + } + + function resetPermissions() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new ResetAgentPermissionsForm($_POST); + + if (@is_array($_GET['ids'])) { + $perms = new RolePermission(); + $selected = Staff::objects()->filter(array('staff_id__in' => $_GET['ids'])); + foreach ($selected as $staff) + // XXX: This maybe should be intersection rather than union + $perms->merge($staff->getPermission()); + $form->getField('perms')->setValue($perms->getInfo()); + } + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + Http::response(201, $this->encode(array('perms' => $clean['perms']))); + } + + $title = __("Reset Agent Permissions"); + $verb = __("Continue"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/reset-agent-permissions.tmpl.php'; + } + + function changeDepartment() { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + + $form = new ChangeDepartmentForm($_POST); + + // Preselect reasonable dept and role based on the current settings + // of the received staff ids + if (@is_array($_GET['ids'])) { + $dept_id = null; + $role_id = null; + $selected = Staff::objects()->filter(array('staff_id__in' => $_GET['ids'])); + foreach ($selected as $staff) { + if (!isset($dept_id)) { + $dept_id = $staff->dept_id; + $role_id = $staff->role_id; + } + elseif ($dept_id != $staff->dept_id) + $dept_id = 0; + elseif ($role_id != $staff->role_id) + $role_id = 0; + } + $form->getField('dept_id')->setValue($dept_id); + $form->getField('role_id')->setValue($role_id); + } + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + Http::response(201, $this->encode($clean)); + } + + $title = __("Change Primary Department"); + $verb = __("Continue"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/quick-add.tmpl.php'; + } +} diff --git a/include/ajax.users.php b/include/ajax.users.php index e00e811a93dffae117d092cf037405e1cd1b2d1f..4b3c64fd2ffa67e7b2b6cda925eb1e1b3ff6add9 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -110,7 +110,7 @@ class UsersAjaxAPI extends AjaxController { if(!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(User::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif(!($user = User::lookup($id))) Http::response(404, 'Unknown user'); @@ -128,7 +128,7 @@ class UsersAjaxAPI extends AjaxController { if(!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(User::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif(!($user = User::lookup($id))) Http::response(404, 'Unknown user'); @@ -146,7 +146,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) + elseif (!$thisstaff->hasPerm(User::PERM_MANAGE)) Http::response(403, 'Permission Denied'); elseif (!($user = User::lookup($id))) Http::response(404, 'Unknown user'); @@ -175,7 +175,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) + elseif (!$thisstaff->hasPerm(User::PERM_MANAGE)) Http::response(403, 'Permission Denied'); elseif (!($user = User::lookup($id))) Http::response(404, 'Unknown user'); @@ -209,7 +209,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_DELETE)) + elseif (!$thisstaff->hasPerm(User::PERM_DELETE)) Http::response(403, 'Permission Denied'); elseif (!($user = User::lookup($id))) Http::response(404, 'Unknown user'); @@ -257,7 +257,7 @@ class UsersAjaxAPI extends AjaxController { $info['lookup'] = 'local'; if ($_POST) { - if (!$thisstaff->getRole()->hasPerm(User::PERM_CREATE)) + if (!$thisstaff->hasPerm(User::PERM_CREATE)) Http::response(403, 'Permission Denied'); $info['title'] = __('Add New User'); @@ -276,7 +276,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_CREATE)) + elseif (!$thisstaff->hasPerm(User::PERM_CREATE)) Http::response(403, 'Permission Denied'); elseif (!$bk || !$id) Http::response(422, 'Backend and user id required'); @@ -299,7 +299,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, 'Login Required'); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_CREATE)) + elseif (!$thisstaff->hasPerm(User::PERM_CREATE)) Http::response(403, 'Permission Denied'); $info = array( @@ -340,7 +340,7 @@ class UsersAjaxAPI extends AjaxController { global $thisstaff; if (!$info or !$info['title']) { - if ($thisstaff->getRole()->hasPerm(User::PERM_CREATE)) + if ($thisstaff->hasPerm(User::PERM_CREATE)) $info += array('title' => __('Lookup or create a user')); else $info += array('title' => __('Lookup a user')); @@ -445,7 +445,7 @@ class UsersAjaxAPI extends AjaxController { if (!$thisstaff) Http::response(403, "Login required"); - elseif (!$thisstaff->getRole()->hasPerm(User::PERM_EDIT)) + elseif (!$thisstaff->hasPerm(User::PERM_EDIT)) Http::response(403, 'Permission Denied'); elseif (!($user = User::lookup($user_id))) Http::response(404, "No such user"); diff --git a/include/class.canned.php b/include/class.canned.php index 9ea3a66cb82bf82b4526cda1c7b2108862b0e3f9..affa59d9fca329791522140d50f16890bcfbd18f 100644 --- a/include/class.canned.php +++ b/include/class.canned.php @@ -15,25 +15,6 @@ **********************************************************************/ include_once(INCLUDE_DIR.'class.file.php'); -class CannedModel { - - const PERM_MANAGE = 'canned.manage'; - - static protected $perms = array( - self::PERM_MANAGE => array( - 'title' => - /* @trans */ 'Premade', - 'desc' => - /* @trans */ 'Ability to add/update/disable/delete canned responses') - ); - - static function getPermissions() { - return self::$perms; - } -} - -RolePermission::register( /* @trans */ 'Knowledgebase', CannedModel::getPermissions()); - class Canned extends VerySimpleModel { static $meta = array( @@ -52,6 +33,20 @@ extends VerySimpleModel { ), ); + const PERM_MANAGE = 'canned.manage'; + + static protected $perms = array( + self::PERM_MANAGE => array( + 'title' => + /* @trans */ 'Premade', + 'desc' => + /* @trans */ 'Ability to add/update/disable/delete canned responses') + ); + + static function getPermissions() { + return self::$perms; + } + function getId(){ return $this->canned_id; } @@ -297,4 +292,6 @@ extends VerySimpleModel { return true; } } +RolePermission::register( /* @trans */ 'Knowledgebase', Canned::getPermissions()); + ?> diff --git a/include/class.client.php b/include/class.client.php index bbd6270c5f7ee781f58f2335ea29e20c895bb687..92b58b1ef761ec0006e2996a99897b38b6051413 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -253,13 +253,13 @@ class EndUser extends BaseAuthenticatedUser { if (!($stats=$this->getTicketStats())) return 0; - return $stats['org-open']+$stats['org-closed']; + return $stats['org']['open']+$stats['org']['closed']; } function getNumOpenOrganizationTickets() { - return ($stats=$this->getTicketStats())?$stats['org-open']:0; + return ($stats=$this->getTicketStats())?$stats['org']['open']:0; } function getNumClosedOrganizationTickets() { - return ($stats=$this->getTicketStats())?$stats['org-closed']:0; + return ($stats=$this->getTicketStats())?$stats['org']['closed']:0; } function getAccount() { @@ -278,17 +278,31 @@ class EndUser extends BaseAuthenticatedUser { private function getStats() { $basic = Ticket::objects() ->annotate(array('count' => SqlAggregate::COUNT('ticket_id'))) - ->values('status__state', 'topic_id') - ->filter(Q::any(array( - 'user_id' => $this->getId(), - 'thread__collaborators__user_id' => $this->getId(), - ))); + ->values('status__state', 'topic_id', 'user__org_id'); + + $q = new Q(); $q->union(); + if ($this->getOrgId()) + $q->add(array('user__org_id' => $this->getOrgId())); + + // Share tickets among the organization for owners only + $owners = clone $basic; + $q->add(array('user_id' => $this->getId())); + $owners->filter($q); + + $collabs = clone $basic; + $collabs->filter(array('thread__collaborators__user_id' => $this->getId())); + + // TODO: Implement UNION ALL support in the ORM $stats = array('open' => 0, 'closed' => 0, 'topics' => array()); - foreach ($basic as $row) { - $stats[$row['status__state']] += $row['count']; - if ($row['topic_id']) - $stats['topics'][$row['topic_id']] += $row['count']; + foreach (array($owners, $collabs) as $rs) { + foreach ($rs as $row) { + $stats[$row['status__state']] += $row['count']; + if ($row['topic_id']) + $stats['topics'][$row['topic_id']] += $row['count']; + if ($row['user__org_id']) + $stats['org'][$row['status__state']] += $row['count']; + } } return $stats; } diff --git a/include/class.dept.php b/include/class.dept.php index 2950f82793fdf699acff6a40649165769c7ac875..9723c8925721c7e83b338271683c6dab10e49d91 100644 --- a/include/class.dept.php +++ b/include/class.dept.php @@ -42,10 +42,10 @@ implements TemplateVariable { 'list' => true, 'reverse' => 'Staff.dept', ), - 'groups' => array( + 'extended' => array( 'null' => true, 'list' => true, - 'reverse' => 'GroupDeptAccess.dept' + 'reverse' => 'StaffDeptAccess.dept' ), ), ); @@ -87,6 +87,13 @@ implements TemplateVariable { ); } + function getVar($tag) { + switch ($tag) { + case 'members': + return new UserList($this->getMembers()->all()); + } + } + function getId() { return $this->id; } @@ -152,23 +159,25 @@ implements TemplateVariable { if (!$this->_members || $criteria) { $members = Staff::objects() ->distinct('staff_id') + ->constrain(array( + // Ensure that joining through dept_access is only relevant + // for this department, so that the `alerts` annotation + // can work properly + 'dept_access' => new Q(array('dept_access__dept_id' => $this->getId())) + )) ->filter(Q::any(array( 'dept_id' => $this->getId(), - new Q(array( - 'group__depts__dept_id' => $this->getId(), - 'group__depts__dept__group_membership' => self::ALERTS_DEPT_AND_GROUPS, - )), - 'staff_id' => $this->manager_id + 'staff_id' => $this->manager_id, + 'dept_access__dept_id' => $this->getId(), ))); - if ($criteria && $criteria['available']) + // TODO: Consider moving this into ::getAvailableMembers + if ($criteria && $criteria['available']) { $members->filter(array( - 'group__flags__hasbit' => Group::FLAG_ENABLED, 'isactive' => 1, 'onvacation' => 0, )); - - $members->distinct('staff_id'); + } switch ($cfg->getAgentNameFormat()) { case 'last': case 'lastfirst': @@ -181,11 +190,11 @@ implements TemplateVariable { } if ($criteria) - return $members->all(); + return $members; - $this->_members = $members->all(); + $this->_members = $members; } - return new UserList($this->_members); + return $this->_members; } function getAvailableMembers() { @@ -198,7 +207,13 @@ implements TemplateVariable { $rv = array(); } else { - $rv = $this->getAvailableMembers(); + $rv = clone $this->getAvailableMembers(); + $rv->filter(Q::any(array( + // Ensure "Alerts" is enabled — must be a primary member or + // have alerts enabled on your membership. + 'dept_id' => $this->getId(), + 'dept_access__flags__hasbit' => StaffDeptAccess::FLAG_ALERTS, + ))); } return $rv; } @@ -314,58 +329,6 @@ implements TemplateVariable { return $this->getHashtable(); } - function getAllowedGroups() { - - if (!isset($this->_groupids)) { - $this->_groupids = array(); - $groups = GroupDeptAccess::objects() - ->filter(array('dept_id' => $this->getId())) - ->values_flat('group_id'); - - foreach ($groups as $row) - $this->_groupids[] = $row[0]; - } - - return $this->_groupids; - } - - function updateGroups($groups_ids, $vars) { - - // 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, $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 ($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(); - } - } - - } - - function updateSettings($vars) { - $this->updateGroups($vars['groups'] ?: array(), $vars); - $this->path = $this->getFullPath(); - $this->save(); - return true; - } - function delete() { global $cfg; @@ -382,19 +345,30 @@ implements TemplateVariable { if (parent::delete()) { // DO SOME HOUSE CLEANING //Move tickets to default Dept. TODO: Move one ticket at a time and send alerts + log notes. - db_query('UPDATE '.TICKET_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id)); + Ticket::objects() + ->filter(array('dept_id' => $id)) + ->update(array('dept_id' => $cfg->getDefaultDeptId())); + //Move Dept members: This should never happen..since delete should be issued only to empty Depts...but check it anyways - db_query('UPDATE '.STAFF_TABLE.' SET dept_id='.db_input($cfg->getDefaultDeptId()).' WHERE dept_id='.db_input($id)); + Staff::objects() + ->filter(array('dept_id' => $id)) + ->update(array('dept_id' => $cfg->getDefaultDeptId())); // Clear any settings using dept to default back to system default - db_query('UPDATE '.TOPIC_TABLE.' SET dept_id=0 WHERE dept_id='.db_input($id)); - db_query('UPDATE '.EMAIL_TABLE.' SET dept_id=0 WHERE dept_id='.db_input($id)); + Topic::objects() + ->filter(array('dept_id' => $id)) + ->delete(); + Email::objects() + ->filter(array('dept_id' => $id)) + ->delete(); + db_query('UPDATE '.FILTER_TABLE.' SET dept_id=0 WHERE dept_id='.db_input($id)); - //Delete group access - db_query('DELETE FROM '.GROUP_DEPT_TABLE.' WHERE dept_id='.db_input($id)); + // Delete extended access entries + StaffDeptAccess::objects() + ->filter(array('dept_id' => $id)) + ->delete(); } - return true; } @@ -557,13 +531,14 @@ implements TemplateVariable { function update($vars, &$errors) { global $cfg; - if (isset($this->id) && $this->getId() != $vars['id']) + $id = $this->id; + if ($id && $id != $vars['id']) $errors['err']=__('Missing or invalid Dept ID (internal error).'); if (!$vars['name']) { $errors['name']=__('Name required'); - } elseif (($did=static::getIdByName($vars['name'], $vars['pid'])) - && (!isset($this->id) || $did!=$this->getId())) { + } elseif (($did = static::getIdByName($vars['name'], $vars['pid'])) + && $did != $this->id) { $errors['name']=__('Department already exists'); } @@ -573,11 +548,20 @@ implements TemplateVariable { if ($vars['pid'] && !($p = static::lookup($vars['pid']))) $errors['pid'] = __('Department selection is required'); + // Format access update as [array(dept_id, role_id, alerts?)] + $access = array(); + if (isset($vars['members'])) { + foreach (@$vars['members'] as $staff_id) { + $access[] = array($staff_id, $vars['member_role'][$staff_id], + @$vars['member_alerts'][$staff_id]); + } + } + $this->updateAccess($access, $errors); + if ($errors) return false; $this->pid = $vars['pid'] ?: 0; - $this->updated = SqlFunction::NOW(); $this->ispublic = isset($vars['ispublic'])?$vars['ispublic']:0; $this->email_id = isset($vars['email_id'])?$vars['email_id']:0; $this->tpl_id = isset($vars['tpl_id'])?$vars['tpl_id']:0; @@ -590,9 +574,17 @@ implements TemplateVariable { $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; $this->flags = isset($vars['assign_members_only']) ? self::FLAG_ASSIGN_MEMBERS_ONLY : 0; + $this->path = $this->getFullPath(); - if ($this->save()) - return $this->updateSettings($vars); + $wasnew = $this->__new__; + if ($this->save() && $this->extended->saveAll()) { + if ($wasnew) { + // The ID wasn't available until after the commit + $this->path = $this->getFullPath(); + $this->save(); + } + return true; + } if (isset($this->id)) $errors['err']=sprintf(__('Unable to update %s.'), __('this department')) @@ -604,23 +596,91 @@ implements TemplateVariable { return false; } + function updateAccess($access, &$errors) { + reset($access); + $dropped = array(); + foreach ($this->extended as $DA) + $dropped[$DA->staff_id] = 1; + while (list(, list($staff_id, $role_id, $alerts)) = each($access)) { + unset($dropped[$staff_id]); + if (!$role_id || !Role::lookup($role_id)) + $errors['members'][$staff_id] = __('Select a valid role'); + if (!$staff_id || !Staff::lookup($staff_id)) + $errors['members'][$staff_id] = __('No such agent'); + $da = $this->extended->findFirst(array('staff_id' => $staff_id)); + if (!isset($da)) { + $da = StaffDeptAccess::create(array( + 'staff_id' => $staff_id, 'role_id' => $role_id + )); + $this->extended->add($da); + } + else { + $da->role_id = $role_id; + } + $da->setAlerts($alerts); + } + if (!$errors && $dropped) { + $this->extended + ->filter(array('staff_id__in' => array_keys($dropped))) + ->delete(); + $this->extended->reset(); + } + return !$errors; + } } -class GroupDeptAccess extends VerySimpleModel { - static $meta = array( - 'table' => GROUP_DEPT_TABLE, - 'pk' => array('dept_id', 'group_id'), - 'joins' => array( - 'dept' => array( - 'constraint' => array('dept_id' => 'Dept.id'), - ), - 'group' => array( - 'constraint' => array('group_id' => 'Group.id'), - ), - 'role' => array( - 'constraint' => array('role_id' => 'Role.id'), - ), - ), - ); +class DepartmentQuickAddForm +extends Form { + function getFields() { + if ($this->fields) + return $this->fields; + + return $this->fields = array( + 'pid' => new ChoiceField(array( + 'label' => '', + 'default' => 0, + 'choices' => + array(0 => '— '.__('Top-Level Department').' —') + + Dept::getDepartments() + )), + 'name' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __('Name'), + 'classes' => 'span12', + 'autofocus' => true, + 'length' => 128, + ), + )), + 'email_id' => new ChoiceField(array( + 'label' => __('Email Mailbox'), + 'default' => 0, + 'choices' => + array(0 => '— '.__('System Default').' —') + + Email::getAddresses(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'private' => new BooleanField(array( + 'configuration' => array( + 'classes' => 'form footer', + 'desc' => __('This department is for internal use'), + ), + )), + ); + } + + function getClean() { + $clean = parent::getClean(); + + $clean['ispublic'] = !$clean['private']; + unset($clean['private']); + + return $clean; + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } } -?> diff --git a/include/class.export.php b/include/class.export.php index 68b907282853cbf55b5ebde39f367230905d4ebc..d12dd3bf4762ca9456c56ae31a270bf249264549 100644 --- a/include/class.export.php +++ b/include/class.export.php @@ -341,7 +341,7 @@ class DatabaseExporter { var $options; var $tables = array(CONFIG_TABLE, SYSLOG_TABLE, FILE_TABLE, FILE_CHUNK_TABLE, STAFF_TABLE, DEPT_TABLE, TOPIC_TABLE, GROUP_TABLE, - GROUP_DEPT_TABLE, TEAM_TABLE, TEAM_MEMBER_TABLE, FAQ_TABLE, + STAFF_DEPT_TABLE, TEAM_TABLE, TEAM_MEMBER_TABLE, FAQ_TABLE, FAQ_TOPIC_TABLE, FAQ_CATEGORY_TABLE, DRAFT_TABLE, CANNED_TABLE, TICKET_TABLE, ATTACHMENT_TABLE, THREAD_TABLE, THREAD_ENTRY_TABLE, THREAD_ENTRY_EMAIL_TABLE, diff --git a/include/class.faq.php b/include/class.faq.php index a551beefaba06f1a653745cf63f1f4de4899c474..361518d282bdeefa6d59b9cd6ad00c136cb4acc7 100644 --- a/include/class.faq.php +++ b/include/class.faq.php @@ -155,11 +155,6 @@ class FAQ extends VerySimpleModel { return $this->save(); } - function logView() { - $this->views++; - $this->save(); - } - function printPdf() { global $thisstaff; require_once(INCLUDE_DIR.'class.pdf.php'); @@ -380,7 +375,7 @@ class FAQ extends VerySimpleModel { static function getFeatured() { return self::objects() ->filter(array('ispublished__in'=>array(1,2), 'category__ispublic'=>1)) - ->order_by('-ispublished','-views'); + ->order_by('-ispublished'); } static function findIdByQuestion($question) { diff --git a/include/class.filter_action.php b/include/class.filter_action.php index c9815d137125cba4d32cf1c704d917adae3680c9..4cdac2e7a636ca4eecdb0f4b1a59186bd9bb423d 100644 --- a/include/class.filter_action.php +++ b/include/class.filter_action.php @@ -281,8 +281,18 @@ class FA_RouteDepartment extends TriggerAction { function getConfigurationOptions() { return array( 'dept_id' => new ChoiceField(array( - 'configuration' => array('prompt' => __('Unchanged')), - 'choices' => Dept::getDepartments(), + 'configuration' => array( + 'prompt' => __('Unchanged'), + 'data' => array('quick-add' => 'department'), + ), + 'choices' => array_merge( + Dept::getDepartments(), + array(':new:' => '— '.__('Add New').' —') + ), + 'validators' => function($self) { + if ($self->getClean() === ':new:') + $self->addError(__('Select a department')); + } )), ); } @@ -353,8 +363,18 @@ class FA_AssignTeam extends TriggerAction { $choices = Team::getTeams(); return array( 'team_id' => new ChoiceField(array( - 'configuration' => array('prompt' => __('Unchanged')), - 'choices' => $choices, + 'configuration' => array( + 'prompt' => __('Unchanged'), + 'data' => array('quick-add' => 'team'), + ), + 'choices' => array_merge( + $choices, + array(':new:' => '— '.__('Add New').' —') + ), + 'validators' => function($self) { + if ($self->getClean() === ':new:') + $self->addError(__('Select a team')); + } )), ); } diff --git a/include/class.format.php b/include/class.format.php index 903256a6f52e983ceda3a93b7395953587f113e3..85c54d234e02fb02f0aa2cb8bc91437a3127240b 100644 --- a/include/class.format.php +++ b/include/class.format.php @@ -724,36 +724,6 @@ class Format { // Normalize text input :: remove diacritics and such $text = normalizer_normalize($text, Normalizer::FORM_C); } - else { - // As a lightweight compatiblity, use a lightweight C - // normalizer with diacritic removal, thanks - // http://ahinea.com/en/tech/accented-translate.html - $tr = array( - "ä" => "a", "ñ" => "n", "ö" => "o", "ü" => "u", "ÿ" => "y" - ); - $text = strtr($text, $tr); - } - // Decompose compatible versions of characters (ä => ae) - $tr = array( - "ß" => "ss", "Æ" => "AE", "æ" => "ae", "IJ" => "IJ", - "ij" => "ij", "Œ" => "OE", "œ" => "oe", "Ð" => "D", - "Đ" => "D", "ð" => "d", "đ" => "d", "Ħ" => "H", "ħ" => "h", - "ı" => "i", "ĸ" => "k", "Ŀ" => "L", "Ł" => "L", "ŀ" => "l", - "ł" => "l", "Ŋ" => "N", "ʼn" => "n", "ŋ" => "n", "Ø" => "O", - "ø" => "o", "ſ" => "s", "Þ" => "T", "Ŧ" => "T", "þ" => "t", - "ŧ" => "t", "ä" => "ae", "ö" => "oe", "ü" => "ue", - "Ä" => "AE", "Ö" => "OE", "Ü" => "UE", - ); - $text = strtr($text, $tr); - - // Drop separated diacritics - $text = preg_replace('/\p{M}/u', '', $text); - - // Drop extraneous whitespace - $text = preg_replace('/(\s)\s+/u', '$1', $text); - - // Drop leading and trailing whitespace - $text = trim($text); if (false && class_exists('IntlBreakIterator')) { // Split by word boundaries @@ -772,12 +742,20 @@ class Format { // http://www.unicode.org/reports/tr29/#Word_Boundaries // Punt for now + + // Drop extraneous whitespace + $text = preg_replace('/(\s)\s+/u', '$1', $text); + + // Drop leading and trailing whitespace + $text = trim($text); } return $text; } function relativeTime($to, $from=false, $granularity=1) { - $timestamp = $to ?: Misc::gmtime(); + if (!$to) + return false; + $timestamp = $to; if (gettype($timestamp) === 'string') $timestamp = strtotime($timestamp); $from = $from ?: Misc::gmtime(); @@ -893,9 +871,6 @@ implements TemplateVariable { function getVar($what) { global $cfg; - if (method_exists($this, 'get' . ucfirst($what))) - return call_user_func(array($this, 'get'.ucfirst($what))); - // TODO: Rebase date format so that locale is discovered HERE. switch ($what) { diff --git a/include/class.forms.php b/include/class.forms.php index 3c8c2bab55219e1d4db2a313a883fbbac2dfe665..b47444e793caa0ed6a9eefc649d602f36e257e64 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -19,7 +19,7 @@ * data for a ticket */ class Form { - + static $renderer = 'GridFluidLayout'; static $id = 0; var $fields = array(); @@ -99,12 +99,7 @@ class Form { function isValid($include=false) { if (!isset($this->_errors)) { $this->_errors = array(); - $this->getClean(); - // Validate the whole form so that errors can be added to the - // individual fields and collected below. - foreach ($this->validators as $V) { - $V($this); - } + $this->validate($this->getClean()); foreach ($this->getFields() as $field) if ($field->errors() && (!$include || $include($field))) $this->_errors[$field->get('id')] = $field->errors(); @@ -112,6 +107,14 @@ class Form { return !$this->_errors; } + function validate($clean_data) { + // Validate the whole form so that errors can be added to the + // individual fields and collected below. + foreach ($this->validators as $V) { + $V($this); + } + } + function getClean() { if (!$this->_clean) { $this->_clean = array(); @@ -163,10 +166,22 @@ class Form { if (isset($options['instructions'])) $this->instructions = $options['instructions']; $form = $this; + $template = $options['template'] ?: 'dynamic-form.tmpl.php'; if ($staff) - include(STAFFINC_DIR . 'templates/dynamic-form.tmpl.php'); + include(STAFFINC_DIR . 'templates/' . $template); else - include(CLIENTINC_DIR . 'templates/dynamic-form.tmpl.php'); + include(CLIENTINC_DIR . 'templates/' . $template); + echo $this->getMedia(); + } + + function getLayout() { + $rc = @$options['renderer'] ?: static::$renderer; + return new $rc($title, $options); + } + + function asTable($options=array()) { + return $this->getLayout()->asTable($this); + // XXX: Media can't go in a table echo $this->getMedia(); } @@ -300,12 +315,153 @@ class Form { * */ class SimpleForm extends Form { - function __construct($fields=array(), $source=null, $options=array()) { parent::__construct($source, $options); $this->setFields($fields); } +} +abstract class AbstractForm extends Form { + function __construct($source=null, $options=array()) { + parent::__construct($source, $options); + $this->setFields($this->buildFields()); + } + /** + * Fetch the fields defined for this form. This method is only called + * once. + */ + abstract function buildFields(); +} + +/** + * Container class to represent the connection between the form fields and the + * rendered state of the form. + */ +interface FormRenderer { + // Render the form fields into a table + function asTable($form); + // Render the form fields into divs + function asBlock($form); +} + +abstract class FormLayout { + static $default_cell_layout = 'Cell'; + + function getLayout($field) { + $layout = $field->get('layout') ?: static::$default_cell_layout; + if (is_string($layout)) + $layout = new $layout(); + return $layout; + } +} + +class GridFluidLayout +extends FormLayout +implements FormRenderer { + function asTable($form) { + ob_start(); +?> + <table class="<?php echo 'grid form' ?>"> + <colgroup width="8.333333%"><col span="12"/></colgroup> + <caption><?php echo Format::htmlchars($form->getTitle()); ?> + <div><small><?php echo Format::viewableImages($form->getInstructions()); ?></small></div> + </caption> +<?php + $row_size = 12; + $cols = $row = 0; + foreach ($form->getFields() as $f) { + $layout = $this->getLayout($f); + $size = $layout->getWidth() ?: 12; + if ($offs = $layout->getOffset()) { + $size += $offs; + } + if ($cols < $size || $layout->isBreakForced()) { + if ($row) echo '</tr>'; + echo '<tr>'; + $cols = $row_size; + $row++; + } + // Render the cell + $cols -= $size; + $attrs = array('colspan' => $size, 'rowspan' => $layout->getHeight(), + 'style' => '"'.$layout->getOption('style').'"'); + if ($offs) { ?> + <td colspan="<?php echo $offset; ?>"></td> <?php + } + ?> + <td class="cell" <?php echo Format::array_implode('=', ' ', array_filter($attrs)); ?> + data-field-id="<?php echo $f->get('id'); ?>"> + <fieldset class="field <?php if (!$f->isVisible()) echo 'hidden'; ?>" + id="field<?php echo $f->getWidget()->id; ?>" + data-field-id="<?php echo $f->get('id'); ?>"> +<?php if ($label = $f->get('label')) { ?> + <label class="<?php if ($f->isRequired()) echo 'required'; ?>" + for="<?php echo $f->getWidget()->id; ?>"> + <?php echo Format::htmlchars($label); ?>: + </label> +<?php } + if ($f->get('hint')) { ?> + <div class="field-hint-text"> + <?php echo Format::htmlchars($f->get('hint')); ?> + </div> +<?php } + $f->render(); + if ($f->errors()) + foreach ($f->errors() as $e) + echo sprintf('<div class="error">%s</div>', Format::htmlchars($e)); +?> + </fieldset> + </td> + <?php + } + if ($row) + echo '</tr>'; + + echo '</tbody></table>'; + + return ob_get_clean(); + } + + function asBlock($form) {} +} + +/** + * Basic container for field and form layouts. By default every cell takes + * a whole output row and does not imply any sort of width. + */ +class Cell { + function isBreakForced() { return true; } + function getWidth() { return false; } + function getHeight() { return 1; } + function getOffset() { return 0; } + function getOption($prop) { return false; } +} + +/** + * Fluid grid layout, meaning each cell renders to the right of the previous + * cell (for left-to-right layouts). A width in columns can be specified for + * each cell along with an offset from the previous cell. A height of columns + * along with an optional break is supported. + */ +class GridFluidCell +extends Cell { + var $span; + var $options; + + function __construct($span, $options=array()) { + $this->span = $span; + $this->options = $options + array( + 'rows' => 1, # rowspan + 'offset' => 0, # skip some columns + 'break' => false, # start on a new row + ); + } + + function isBreakForced() { return $this->options['break']; } + function getWidth() { return $this->span; } + function getHeight() { return $this->options['rows']; } + function getOffset() { return $this->options['offset']; } + function getOption($prop) { return $this->options[$prop]; } } require_once(INCLUDE_DIR . "class.json.php"); @@ -417,11 +573,11 @@ class FormField { $vs, array($this, $this->_clean)); } - if ($this->isVisible()) - $this->validateEntry($this->_clean); - if (!isset($this->_clean) && ($d = $this->get('default'))) $this->_clean = $d; + + if ($this->isVisible()) + $this->validateEntry($this->_clean); } return $this->_clean; } @@ -493,7 +649,6 @@ class FormField { * field is visible and should be considered for validation */ function isVisible() { - $config = $this->getConfiguration(); if ($this->get('visibility') instanceof VisibilityConstraint) { return $this->get('visibility')->isVisible($this); } @@ -815,6 +970,11 @@ class FormField { function getAnswer() { return $this->answer; } function setAnswer($ans) { $this->answer = $ans; } + function setValue($value) { + $this->reset(); + $this->getWidget()->value = $value; + } + function getFormName() { if (is_numeric($this->get('id'))) return substr(md5( @@ -2913,9 +3073,16 @@ class ChoicesWidget extends Widget { if (!is_array($values)) $values = $have_def ? array($def_key => $choices[$def_key]) : array(); + if (isset($config['classes'])) + $classes = 'class="'.$config['classes'].'"'; ?> <select name="<?php echo $this->name; ?>[]" + <?php echo implode(' ', array_filter(array($classes))); ?> id="<?php echo $this->id; ?>" + <?php if (isset($config['data'])) + foreach ($config['data'] as $D=>$V) + echo ' data-'.$D.'="'.Format::htmlchars($V).'"'; + ?> data-placeholder="<?php echo $prompt; ?>" <?php if ($config['multiselect']) echo ' multiple="multiple"'; ?>> @@ -3007,6 +3174,116 @@ class ChoicesWidget extends Widget { } } +/** + * A widget for the ChoiceField which will render a list of radio boxes or + * checkboxes depending on the value of $config['multiple']. Complex choices + * are also supported and will be rendered as divs. + */ +class BoxChoicesWidget extends Widget { + function render($options=array()) { + $this->emitChoices($this->field->getChoices()); + } + + function emitChoices($choices) { + static $uid = 1; + + if (!isset($this->value)) + $this->value = $this->field->get('default'); + $config = $this->field->getConfiguration(); + $type = $config['multiple'] ? 'checkbox' : 'radio'; + if (isset($config['classes'])) + $classes = 'class="'.$config['classes'].'"'; + + foreach ($choices as $k => $v) { + if (is_array($v)) { + $this->renderSectionBreak($k); + $this->emitChoices($v); + continue; + } + $id = sprintf("%s-%s", $this->id, $uid++); +?> + <label <?php echo $classes; ?> + for="<?php echo $id; ?>" style="display:block;"> + <input id="<?php echo $id; ?>" style="vertical-align:top;" + type="<?php echo $type; ?>" name="<?php echo $this->name; ?>[]" <?php + if ($this->value[$k]) echo 'checked="checked"'; ?> value="<?php + echo Format::htmlchars($k); ?>"/> + <?php + if ($v) { ?> + <em style="display:inline-block;vertical-align:baseline;width:90%;width:calc(100% - 25px);"><?php + echo Format::viewableImages($v); ?></em> +<?php } ?> + </label> +<?php } + } + + function renderSectionBreak($label) { ?> + <div><?php echo Format::htmlchars($label); ?></div> +<?php + } + + function getValue() { + $data = $this->field->getSource(); + if (count($data)) { + if (!isset($data[$this->name])) + return array(); + return $this->collectValues($data[$this->name], $this->field->getChoices()); + } + return parent::getValue(); + } + + function collectValues($data, $choices) { + $value = array(); + foreach ($choices as $k => $v) { + if (is_array($v)) + $value = array_merge($value, $this->collectValues($data, $v)); + elseif (@in_array($k, $data)) + $value[$k] = $v; + } + return $value; + } +} + +/** + * An extension to the BoxChoicesWidget which will render complex choices in + * tabs. + */ +class TabbedBoxChoicesWidget extends BoxChoicesWidget { + function render($options=array()) { + $tabs = array(); + foreach ($this->field->getChoices() as $label=>$group) { + if (is_array($group)) { + $tabs[$label] = $group; + } + else { + $this->emitChoices(array($label=>$group)); + } + } + if ($tabs) { + ?> + <div> + <ul class="alt tabs"> +<?php $i = 0; + foreach ($tabs as $label => $group) { + $active = $i++ == 0; ?> + <li <?php if ($active) echo 'class="active"'; + ?>><a href="#<?php echo sprintf('%s-%s', $this->name, Format::slugify($label)); + ?>"><?php echo Format::htmlchars($label); ?></a></li> +<?php } ?> + </ul> +<?php $i = 0; + foreach ($tabs as $label => $group) { + $first = $i++ == 0; ?> + <div class="tab_content <?php if (!$first) echo 'hidden'; ?>" id="<?php + echo sprintf('%s-%s', $this->name, Format::slugify($label));?>"> +<?php $this->emitChoices($group); ?> + </div> +<?php } ?> + </div> +<?php } + } +} + class CheckboxWidget extends Widget { function __construct($field) { parent::__construct($field); @@ -3017,24 +3294,29 @@ class CheckboxWidget extends Widget { $config = $this->field->getConfiguration(); if (!isset($this->value)) $this->value = $this->field->get('default'); + if (isset($config['classes'])) + $classes = 'class="'.$config['classes'].'"'; ?> - <input id="<?php echo $this->id; ?>" style="vertical-align:top;" + <div <?php echo implode(' ', array_filter(array($classes))); ?>> + <label class="checkbox"> + <input id="<?php echo $this->id; ?>" type="checkbox" name="<?php echo $this->name; ?>[]" <?php if ($this->value) echo 'checked="checked"'; ?> value="<?php echo $this->field->get('id'); ?>"/> <?php if ($config['desc']) { echo Format::viewableImages($config['desc']); - } + } ?> + </label> + </div> +<?php } function getValue() { $data = $this->field->getSource(); if (count($data)) { if (!isset($data[$this->name])) - // Indeterminite. Likely false, but consider current field - // value - return null; + return false; return @in_array($this->field->get('id'), $data[$this->name]); } return parent::getValue(); @@ -3353,7 +3635,8 @@ class FreeTextField extends FormField { class FreeTextWidget extends Widget { function render($options=array()) { $config = $this->field->getConfiguration(); - ?><div class="thread-body" style="padding:0"><?php + $class = $config['classes'] ?: 'thread-body' + ?><div class="<?php echo $class; ?>" style="padding:0"><?php if ($label = $this->field->getLocal('label')) { ?> <h3><?php echo Format::htmlchars($label); @@ -3591,10 +3874,6 @@ class AssignmentForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { @@ -3686,10 +3965,6 @@ class TransferForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { diff --git a/include/class.group.php b/include/class.group.php deleted file mode 100644 index c8b8e6ef76c36ab51faf0fc1d4e88f75d37f48f3..0000000000000000000000000000000000000000 --- a/include/class.group.php +++ /dev/null @@ -1,314 +0,0 @@ -<?php -/********************************************************************* - class.group.php - - User Group - Everything about a group! - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ - -class Group extends VerySimpleModel { - - static $meta = array( - 'table' => GROUP_TABLE, - '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') - ), - ), - ); - - const FLAG_ENABLED = 0X0001; - - var $departments; - - function getHashtable() { - $base = $this->ht; - $base['name'] = $base['name']; - $base['isactive'] = $base['flags']; - return $base; - } - - function getInfo() { - return $this->getHashtable(); - } - - 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 getCreateDate() { - return $this->created; - } - - function getUpdateDate() { - return $this->updated; - } - - function getNumMembers() { - return $this->members ? $this->members->count() : 0; - } - - function isEnabled() { - return ($this->get('flags') & self::FLAG_ENABLED !== 0); - } - - function isActive(){ - return $this->isEnabled(); - } - - function getTranslateTag($subtag) { - return _H(sprintf('group.%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('group.%s.%s', $subtag, $id)); - $T = CustomDataTranslation::translate($tag); - return $T != $tag ? $T : $default; - } - - //Get members of the group. - function getMembers() { - - if (!$this->members) { - $this->members = Staff::objects() - ->filter(array('group_id'=>$this->getId())) - ->order_by('lastname', 'firstname') - ->all(); - } - return $this->members; - } - - //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', 'role_id') as $gda - ) { - $this->departments[$gda[0]] = $gda[1]; - } - } - - return $this->departments; - } - - 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)) { - unset($dept_ids[$idx]); - $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, - 'role_id' => $roleId ?: 0 - ))->save(); - } - } - return true; - } - - function delete() { - - // Can't delete with members - if ($this->getNumMembers()) - return false; - - if (!parent::delete()) - return false; - - // Remove dept access entries - GroupDeptAccess::objects() - ->filter(array('group_id'=>$this->getId())) - ->delete(); - - return true; - } - - function __toString() { - return $this->getName(); - } - - function save($refetch=false) { - if ($this->dirty) { - $this->updated = SqlFunction::NOW(); - } - return parent::save($refetch || $this->dirty); - } - - function update($vars,&$errors) { - if (isset($this->id) && $this->getId() != $vars['id']) - $errors['err'] = __('Missing or invalid group ID'); - - if (!$vars['name']) { - $errors['name'] = __('Group name required'); - } elseif(strlen($vars['name'])<3) { - $errors['name'] = __('Group name must be at least 3 chars.'); - } elseif (($gid=static::getIdByName($vars['name'])) - && (!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->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(), $vars); - - if (isset($this->id)) { - $errors['err']=sprintf(__('Unable to update %s.'), __('this group')) - .' '.__('Internal error occurred'); - } - else { - $errors['err']=sprintf(__('Unable to create %s.'), __('this group')) - .' '.__('Internal error occurred'); - } - 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) { - - if (!isset($vars['flags'])) - $vars['flags'] = 0; - - $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.http.php b/include/class.http.php index c358355a7cd438c621e25d9eabc548709c3ad128..5e14ee932bb2acbd013b348083a4176a13fc42cb 100644 --- a/include/class.http.php +++ b/include/class.http.php @@ -61,18 +61,20 @@ class Http { exit; } - function cacheable($etag, $modified, $ttl=3600) { + function cacheable($etag, $modified=false, $ttl=3600) { // Thanks, http://stackoverflow.com/a/1583753/1025836 // Timezone doesn't matter here — but the time needs to be // consistent round trip to the browser and back. - $last_modified = strtotime($modified." GMT"); - header("Last-Modified: ".date('D, d M Y H:i:s', $last_modified)." GMT", false); + if ($modified) { + $last_modified = strtotime($modified." GMT"); + header("Last-Modified: ".date('D, d M Y H:i:s', $last_modified)." GMT", false); + } header('ETag: "'.$etag.'"'); header("Cache-Control: private, max-age=$ttl"); header('Expires: ' . gmdate('D, d M Y H:i:s', Misc::gmtime() + $ttl)." GMT"); header('Pragma: private'); - if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified || - @trim($_SERVER['HTTP_IF_NONE_MATCH'], '" ') == $etag) { + if (($modified && @strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $last_modified) + || @trim($_SERVER['HTTP_IF_NONE_MATCH'], '" ') == $etag) { header("HTTP/1.1 304 Not Modified"); exit(); } diff --git a/include/class.nav.php b/include/class.nav.php index 867f34623c541ac6191cc6c4843221b7a63b7899..e2dde777539cf2aa5157a68aa6fe4f559f01ae89 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -118,7 +118,7 @@ class StaffNav { $this->tabs['dashboard'] = array( 'desc'=>__('Dashboard'),'href'=>'dashboard.php','title'=>__('Agent Dashboard'), "class"=>"no-pjax" ); - if ($thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) { + if ($thisstaff->hasPerm(User::PERM_DIRECTORY)) { $this->tabs['users'] = array( 'desc' => __('Users'), 'href' => 'users.php', 'title' => __('User Directory') ); @@ -154,7 +154,7 @@ class StaffNav { 'iconclass'=>'assignedTickets', 'droponly'=>true); - if ($staff->hasPerm(TicketModel::PERM_CREATE)) + if ($staff->hasPerm(TicketModel::PERM_CREATE, false)) $subnav[]=array('desc'=>__('New Ticket'), 'title' => __('Open a New Ticket'), 'href'=>'tickets.php?a=open', @@ -175,9 +175,9 @@ class StaffNav { case 'kbase': $subnav[]=array('desc'=>__('FAQs'),'href'=>'kb.php', 'urls'=>array('faq.php'), 'iconclass'=>'kb'); if($staff) { - if ($staff->getRole()->hasPerm(FAQ::PERM_MANAGE)) + if ($staff->hasPerm(FAQ::PERM_MANAGE)) $subnav[]=array('desc'=>__('Categories'),'href'=>'categories.php','iconclass'=>'faq-categories'); - if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(CannedModel::PERM_MANAGE)) + if ($cfg->isCannedResponseEnabled() && $staff->getRole()->hasPerm(Canned::PERM_MANAGE, false)) $subnav[]=array('desc'=>__('Canned Responses'),'href'=>'canned.php','iconclass'=>'canned'); } break; @@ -273,7 +273,6 @@ class AdminNav extends StaffNav{ case 'staff': $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; diff --git a/include/class.organization.php b/include/class.organization.php index 6d3ceaeb143471d6eccb237253c0cb1dac11f200..e1514f19d0c6859477340067a966d601a9dba6fa 100644 --- a/include/class.organization.php +++ b/include/class.organization.php @@ -293,9 +293,6 @@ implements TemplateVariable { } function getVar($tag) { - if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) - return call_user_func(array($this, 'get'.ucfirst($tag))); - $tag = mb_strtolower($tag); foreach ($this->getDynamicData() as $e) if ($a = $e->getAnswer($tag)) diff --git a/include/class.orm.php b/include/class.orm.php index b0aa78eaac87d714b708be1e2e541cf60f996aef..56ee74bdba907098b8f7ca07a87e44c5a5d5ef08 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -308,7 +308,6 @@ class VerySimpleModel { if (in_array($field, static::getMeta('fields'))) return null; - // TODO: Inspect fields from database before throwing this error throw new OrmException(sprintf(__('%s: %s: Field not defined'), get_class($this), $field)); } @@ -316,6 +315,15 @@ class VerySimpleModel { return $this->get($field, null); } + function getByPath($path) { + if (is_string($path)) + $path = explode('__', $path); + $root = $this; + foreach ($path as $P) + $root = $root->get($P); + return $root; + } + function __isset($field) { return array_key_exists($field, $this->ht) || isset(static::$meta['joins'][$field]); @@ -893,6 +901,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl var $model; var $constraints = array(); + var $path_constraints = array(); var $ordering = array(); var $limit = false; var $offset = 0; @@ -934,6 +943,15 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl return $this; } + function constrain() { + foreach (func_get_args() as $I) { + foreach ($I as $path => $Q) { + $this->path_constraints[$path][] = $Q; + } + } + return $this; + } + function defer() { foreach (func_get_args() as $f) $this->defer[$f] = true; @@ -1359,7 +1377,7 @@ class ModelInstanceManager extends ResultSet { // using an AnnotatedModel instance. if ($annotations && $modelClass == $this->model) { foreach ($annotations as $name=>$A) { - if (isset($fields[$name])) { + if (array_key_exists($name, $fields)) { $extras[$name] = $fields[$name]; unset($fields[$name]); } @@ -1556,6 +1574,86 @@ class InstrumentedList extends ModelInstanceManager { return new static(array($this->model, $key), $this->filter($constraint)); } + /** + * Find the first item in the current set which matches the given criteria. + * This would be used in favor of ::filter() which might trigger another + * database query. The criteria is intended to be quite simple and should + * not traverse relationships which have not already been fetched. + * Otherwise, the ::filter() or ::window() methods would provide better + * performance. + * + * Example: + * >>> $a = new User(); + * >>> $a->roles->add(Role::lookup(['name' => 'administator'])); + * >>> $a->roles->findFirst(['roles__name__startswith' => 'admin']); + * <Role: administrator> + */ + function findFirst(array $criteria) { + foreach ($this as $record) { + $matches = true; + foreach ($criteria as $field=>$check) { + if (!SqlCompiler::evaluate($record, $field, $check)) { + $matches = false; + break; + } + } + if ($matches) + return $record; + } + } + + /** + * Sort the instrumented list in place. This would be useful to change the + * sorting order of the items in the list without fetching the list from + * the database again. + * + * Parameters: + * $key - (callable|int) A callable function to produce the sort keys + * or one of the SORT_ constants used by the array_multisort + * function + * $reverse - (bool) true if the list should be sorted descending + * + * Returns: + * This instrumented list for chaining and inlining. + */ + function sort($key=false, $reverse=false) { + // Fetch all records into the cache + $this->asArray(); + if (is_callable($key)) { + array_multisort( + array_map($key, $this->cache), + $reverse ? SORT_DESC : SORT_ASC, + $this->cache); + } + elseif ($key) { + array_multisort($this->cache, + $reverse ? SORT_DESC : SORT_ASC, $key); + } + elseif ($reverse) { + rsort($this->cache); + } + else + sort($this->cache); + return $this; + } + + /** + * Reverse the list item in place. Returns this object for chaining + */ + function reverse() { + $this->asArray(); + array_reverse($this->cache); + return $this; + } + + // Save all changes made to any list items + function saveAll() { + foreach ($this as $I) + if (!$I->save()) + return false; + return true; + } + // QuerySet delegates function count() { return $this->objects()->count(); @@ -1614,6 +1712,56 @@ class SqlCompiler { return $this->options['parent']; } + /** + * Split a criteria item into the identifying pieces: path, field, and + * operator. + */ + static function splitCriteria($criteria) { + static $operators = array( + 'exact' => 1, 'isnull' => 1, + 'gt' => 1, 'lt' => 1, 'gte' => 1, 'lte' => 1, + 'contains' => 1, 'like' => 1, 'startswith' => 1, 'endswith' => 1, + 'in' => 1, 'intersect' => 1, + 'hasbit' => 1, + ); + $path = explode('__', $criteria); + if (!isset($options['table'])) { + $field = array_pop($path); + if (isset($operators[$field])) { + $operator = $field; + $field = array_pop($path); + } + } + return array($field, $path, $operator ?: 'exact'); + } + + /** + * Check if the values match given the operator. + * + * Throws: + * OrmException - if $operator is not supported + */ + static function evaluate($record, $field, $check) { + static $ops; if (!isset($ops)) { $ops = array( + 'exact' => function($a, $b) { return is_string($a) ? strcasecmp($a, $b) == 0 : $a == $b; }, + 'isnull' => function($a, $b) { return is_null($a) == $b; }, + 'gt' => function($a, $b) { return $a > $b; }, + 'gte' => function($a, $b) { return $a >= $b; }, + 'lt' => function($a, $b) { return $a < $b; }, + 'lte' => function($a, $b) { return $a <= $b; }, + 'contains' => function($a, $b) { return stripos($a, $b) !== false; }, + 'startswith' => function($a, $b) { return stripos($a, $b) === 0; }, + 'hasbit' => function($a, $b) { return $a & $b == $b; }, + ); } + list($field, $path, $operator) = self::splitCriteria($field); + if (!isset($ops[$operator])) + throw new OrmException($operator.': Unsupported operator'); + + if ($path) + $record = $record->getByPath($path); + return $ops[$operator]($record->get($field), $check); + } + /** * Handles breaking down a field or model search descriptor into the * model search path, field, and operator parts. When used in a queryset @@ -1680,16 +1828,8 @@ class SqlCompiler { // The parts after each of the __ pieces are links to other tables. // The last item (after the last __) is allowed to be an operator // specifiction. - $parts = explode('__', $field); - $operator = static::$operators['exact']; - if (!isset($options['table'])) { - $field = array_pop($parts); - if (isset(static::$operators[$field])) { - $operator = static::$operators[$field]; - $field = array_pop($parts); - } - } - + list($field, $parts, $op) = static::splitCriteria($field); + $operator = static::$operators[$op]; $path = ''; $rootModel = $model; @@ -1876,8 +2016,20 @@ class SqlCompiler { function getJoins($queryset) { $sql = ''; - foreach ($this->joins as $j) - $sql .= $j['sql']; + foreach ($this->joins as $path => $j) { + if (!$j['sql']) + continue; + list($base, $constraints) = $j['sql']; + // Add in path-specific constraints, if any + if (isset($queryset->path_constraints[$path])) { + foreach ($queryset->path_constraints[$path] as $Q) { + $constraints[] = $this->compileQ($Q, $queryset->model); + } + } + $sql .= $base; + if ($constraints) + $sql .= ' ON ('.implode(' AND ', $constraints).')'; + } // Add extra items from QuerySet if (isset($queryset->extra['tables'])) { foreach ($queryset->extra['tables'] as $S) { @@ -2071,9 +2223,7 @@ class MySqlCompiler extends SqlCompiler { ? $rmodel::getQuery($this) : $this->quote($rmodel::getMeta('table')); $base = "{$join}{$table} {$alias}"; - if ($constraints) - $base .= ' ON ('.implode(' AND ', $constraints).')'; - return $base; + return array($base, $constraints); } /** @@ -2089,14 +2239,14 @@ class MySqlCompiler extends SqlCompiler { * (string) token to be placed into the compiled SQL statement. This * is a colon followed by a number */ - function input($what, $slot=false) { + function input($what, $slot=false, $model=false) { if ($what instanceof QuerySet) { $q = $what->getQuery(array('nosort'=>true)); $this->params = array_merge($this->params, $q->params); return '('.$q->sql.')'; } elseif ($what instanceof SqlFunction) { - return $what->toSql($this); + return $what->toSql($this, $model); } elseif (!isset($what)) { return 'NULL'; @@ -2384,7 +2534,7 @@ class MySqlCompiler extends SqlCompiler { $table = $model::getMeta('table'); $set = array(); foreach ($what as $field=>$value) - $set[] = sprintf('%s = %s', $this->quote($field), $this->input($value)); + $set[] = sprintf('%s = %s', $this->quote($field), $this->input($value, false, $model)); $set = implode(', ', $set); list($where, $having) = $this->getWhereHavingClause($queryset); $joins = $this->getJoins($queryset); diff --git a/include/class.role.php b/include/class.role.php index ce4256c5ba75da5a0c67eac8b51319a040758562..64fdbe07d2c74aebb608e4fdb30e8bcaaeaf9d74 100644 --- a/include/class.role.php +++ b/include/class.role.php @@ -19,10 +19,10 @@ class RoleModel extends VerySimpleModel { 'table' => ROLE_TABLE, 'pk' => array('id'), 'joins' => array( - 'groups' => array( + 'extensions' => array( 'null' => true, 'list' => true, - 'reverse' => 'Group.role', + 'reverse' => 'StaffDeptAccess.role', ), 'agents' => array( 'reverse' => 'Staff.role', @@ -70,7 +70,7 @@ class RoleModel extends VerySimpleModel { } function isDeleteable() { - return $this->groups->count() + $this->agents->count() == 0; + return $this->extensions->count() + $this->agents->count() == 0; } } @@ -185,7 +185,7 @@ class Role extends RoleModel { return false; // Remove dept access entries - GroupDeptAccess::objects() + StaffDeptAccess::objects() ->filter(array('role_id'=>$this->getId())) ->update(array('role_id' => 0)); @@ -306,6 +306,19 @@ class RolePermission { return $this->perms; } + function merge($perms) { + if ($perms instanceof self) + $perms = $perms->getInfo(); + foreach ($perms as $perm=>$value) { + if (is_numeric($perm)) { + // Array of perm names + $perm = $value; + $value = true; + } + $this->set($perm, $value); + } + } + static function allPermissions() { return static::$_permissions; } @@ -321,4 +334,56 @@ class RolePermission { } } } -?> + +class RoleQuickAddForm +extends AbstractForm { + function buildFields() { + $permissions = array(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k => $v) { + if ($v['primary']) + continue; + $permissions[$g][$k] = "{$v['title']} — {$v['desc']}"; + } + } + return array( + 'name' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __('Name'), + 'classes' => 'span12', + 'autofocus' => true, + 'length' => 128, + ), + )), + 'clone' => new ChoiceField(array( + 'default' => 0, + 'choices' => + array(0 => '— '.__('Clone an existing role').' —') + + Role::getRoles(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'perms' => new ChoiceField(array( + 'choices' => $permissions, + 'widget' => 'TabbedBoxChoicesWidget', + 'configuration' => array( + 'multiple' => true, + 'classes' => 'vertical-pad', + ), + )), + ); + } + + function getClean() { + $clean = parent::getClean(); + // Index permissions as ['ticket.edit' => 1] + $clean['perms'] = array_keys($clean['perms']); + return $clean; + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} diff --git a/include/class.staff.php b/include/class.staff.php index be5ea3562a664d6693cdc9983a2f4e87de8e67e4..153bdc0a0d23a6b1d19b9379da7e33d36d9ef88c 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -18,7 +18,6 @@ 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'); include_once(INCLUDE_DIR.'class.auth.php'); @@ -36,8 +35,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { 'role' => array( 'constraint' => array('role_id' => 'Role.id'), ), - 'group' => array( - 'constraint' => array('group_id' => 'Group.id'), + 'dept_access' => array( + 'reverse' => 'StaffDeptAccess.staff', ), 'teams' => array( 'reverse' => 'TeamMember.staff', @@ -52,7 +51,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { var $passwd_change; var $_roles = null; var $_teams = null; - var $_perms = null; + var $_perm; function __onload() { @@ -101,8 +100,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { function getHashtable() { $base = $this->ht; - $base['group'] = $base['group_id']; unset($base['teams']); + unset($base['dept_access']); return $base; } @@ -311,7 +310,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { // Departments the staff is "allowed" to access... // based on the group they belong to + user's primary dept + user's managed depts. $sql='SELECT DISTINCT d.id FROM '.STAFF_TABLE.' s ' - .' LEFT JOIN '.GROUP_DEPT_TABLE.' g ON (s.group_id=g.group_id) ' + .' LEFT JOIN '.STAFF_DEPT_TABLE.' g ON (s.staff_id=g.staff_id) ' .' INNER JOIN '.DEPT_TABLE.' d ON (LOCATE(CONCAT("/", s.dept_id, "/"), d.path) OR d.manager_id=s.staff_id OR LOCATE(CONCAT("/", g.dept_id, "/"), d.path)) ' .' WHERE s.staff_id='.db_input($this->getId()); $depts = array(); @@ -325,8 +324,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { 'path__contains' => '/'.$this->dept_id.'/', 'manager_id' => $this->getId(), )); - // Add in group access - foreach ($this->group->depts->values_flat('dept_id') as $row) { + // Add in extended access + foreach ($this->dept_access->depts->values_flat('dept_id') as $row) { // Skip primary dept if ($row[0] == $this->dept_id) continue; @@ -342,10 +341,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { $depts[] = $row[0]; */ - if (!$depts) { //Neptune help us! (fallback) - $depts = array_merge($this->getGroup()->getDepartments(), array($this->getDeptId())); - } - $this->departments = $depts; } @@ -363,14 +358,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { ))?array_keys($depts):array(); } - function getGroupId() { - return $this->group_id; - } - - function getGroup() { - return $this->group; - } - function getDeptId() { return $this->dept_id; } @@ -379,6 +366,27 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { return $this->dept; } + function setDepartmentId($dept_id, $eavesdrop=false) { + // Grant access to the current department + $old = $this->dept_id; + if ($eavesdrop) { + $da = StaffDeptAccess::create(array( + 'dept_id' => $old, + 'role_id' => $this->role_id, + )); + $da->setAlerts(true); + $this->dept_access->add($da); + } + + // Drop extended access to new department + $this->dept_id = $dept_id; + if ($da = $this->dept_access->findFirst(array( + 'dept_id' => $dept_id)) + ) { + $this->dept_access->remove($da); + } + } + function getLanguage() { return (isset($this->lang)) ? $this->lang : false; } @@ -400,25 +408,22 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if (isset($this->_roles[$deptId])) return $this->_roles[$deptId]; - if (($role = $this->group->getRole($deptId))) - return $this->_roles[$deptId] = $role; + if ($access = $this->dept_access->findFirst(array('dept_id' => $deptId))) + return $this->_roles[$deptId] = $access->role; } // For the primary department, use the primary role return $this->role; } - function hasPerm($perm) { - if (!isset($this->_perms)) { - $this->_perms = array(); - foreach ($this->getDepartments() as $deptId) { - if (($role = $this->getRole($deptId))) { - foreach ($role->getPermission()->getInfo() as $perm=>$v) { - $this->_perms[$perm] |= $v; - } - } - } - } - return @$this->_perms[$perm] ?: false; + function hasPerm($perm, $global=true) { + if ($global) + return $this->getPermission()->has($perm); + if ($this->getRole()->hasPerm($perm)) + return true; + foreach ($this->dept_access as $da) + if ($da->role->hasPerm($perm)) + return true; + return false; } function canManageTickets() { @@ -435,10 +440,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { return TRUE; } - function isGroupActive() { - return $this->group->isEnabled(); - } - function isactive() { return $this->isactive; } @@ -452,7 +453,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } function isAvailable() { - return ($this->isactive() && $this->isGroupActive() && !$this->onVacation()); + return ($this->isactive() && !$this->onVacation()); } function showAssignedOnly() { @@ -545,6 +546,17 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } } + function getPermission() { + if (!isset($this->_perm)) { + $this->_perm = new RolePermission($this->permissions); + } + return $this->_perm; + } + + function getPermissionInfo() { + return $this->getPermission()->getInfo(); + } + function onLogin($bk) { // Update last apparent language preference $this->setExtraAttr('browser_lang', @@ -624,8 +636,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } } - if($errors) return false; - $this->firstname = $vars['firstname']; $this->lastname = $vars['lastname']; $this->email = $vars['email']; @@ -642,37 +652,39 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { $this->default_paper_size = $vars['default_paper_size']; $this->lang = $vars['lang']; + if ($errors) + return false; + $_SESSION['::lang'] = null; TextDomain::configureForUser($this); return $this->save(); } - function updateTeams($team_ids) { - - if (is_array($team_ids)) { - $members = TeamMember::objects() - ->filter(array('staff_id' => $this->getId())); - foreach ($members as $member) { - if ($idx = array_search($member->team_id, $team_ids)) { - unset($team_ids[$idx]); - } else { - $member->delete(); - } - } + function updateTeams($membership, &$errors) { + $dropped = array(); + foreach ($this->teams as $TM) + $dropped[$TM->team_id] = 1; - foreach ($team_ids as $id) { - TeamMember::create(array( - 'staff_id'=>$this->getId(), - 'team_id'=>$id - ))->save(); + reset($membership); + while(list(, list($team_id, $alerts)) = each($membership)) { + $member = $this->teams->findFirst(array('team_id' => $team_id)); + if (!$member) { + $this->teams->add($member = TeamMember::create(array( + 'team_id' => $team_id, + ))); } - } else { - TeamMember::objects() - ->filter(array('staff_id'=>$this->getId())) + $member->setAlerts($alerts); + if (!$errors) + $member->save(); + unset($dropped[$member->team_id]); + } + if (!$errors && $dropped) { + $member = $this->teams + ->filter(array('team_id__in' => array_keys($dropped))) ->delete(); + $this->teams->reset(); } - return true; } @@ -685,22 +697,24 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if (!parent::delete()) return false; - //Update the poster and clear staff_id on ticket thread table. - db_query('UPDATE '.THREAD_ENTRY_TABLE - .' SET staff_id=0, poster= '.db_input($this->getName()->getOriginal()) - .' WHERE staff_id='.db_input($this->getId())); - // DO SOME HOUSE CLEANING //Move remove any ticket assignments...TODO: send alert to Dept. manager? - db_query('UPDATE '.TICKET_TABLE.' SET staff_id=0 WHERE staff_id='.db_input($this->getId())); + Ticket::objects() + ->filter(array('staff_id' => $this->getId())) + ->update(array('staff_id' => 0)); //Update the poster and clear staff_id on ticket thread table. - db_query('UPDATE '.TICKET_THREAD_TABLE - .' SET staff_id=0, poster= '.db_input($this->getName()->getOriginal()) - .' WHERE staff_id='.db_input($this->getId())); + ThreadEntry::objects() + ->filter(array('staff_id' => $this->getId())) + ->update(array( + 'staff_id' => 0, + 'poster' => $this->getName()->getOriginal(), + )); // Cleanup Team membership table. - $this->updateTeams(array()); + TeamMember::objects() + ->filter(array('staff_id'=>$this->getId())) + ->delete(); return true; } @@ -726,7 +740,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if (isset($criteria['available'])) { $members = $members->filter(array( - 'group__flags__hasbit' => Group::FLAG_ENABLED, 'onvacation' => 0, 'isactive' => 1, )); @@ -896,11 +909,8 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { if(!$vars['role_id']) $errors['role_id']=__('Role for primary department is required'); - if(!$vars['group_id']) - $errors['group_id']=__('Group is required'); - // Ensure we will still have an administrator with access - if ($vars['isadmin'] !== '1' || $vars['isactive'] !== '1') { + if ($vars['isadmin'] !== '1' || $vars['islocked'] === '1') { $sql = 'select count(*), max(staff_id) from '.STAFF_TABLE .' WHERE isadmin=1 and isactive=1'; if (($res = db_query($sql)) @@ -913,17 +923,55 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } } - if ($errors) - return false; + // Update the user's password if requested + if ($vars['passwd1']) { + try { + $this->setPassword($vars['passwd1'], null); + } + catch (BadPassword $ex) { + $errors['passwd1'] = $ex->getMessage(); + } + catch (PasswordUpdateFailed $ex) { + // TODO: Add a warning banner or crash the update + } + if (isset($vars['change_passwd'])) + $this->change_passwd = 1; + } + elseif (!isset($vars['change_passwd'])) { + $this->change_passwd = 0; + } + + // Update some things for ::updateAccess to inspect + $this->setDepartmentId($vars['dept_id']); + + // Format access update as [array(dept_id, role_id, alerts?)] + $access = array(); + if (isset($vars['dept_access'])) { + foreach (@$vars['dept_access'] as $dept_id) { + $access[] = array($dept_id, $vars['dept_access_role'][$dept_id], + @$vars['dept_access_alerts'][$dept_id]); + } + } + $this->updateAccess($access, $errors); + + // Format team membership as [array(team_id, alerts?)] + $teams = array(); + if (isset($vars['teams'])) { + foreach (@$vars['teams'] as $team_id) { + $teams[] = array($team_id, @$vars['team_alerts'][$team_id]); + } + } + $this->updateTeams($teams, $errors); + + // Update the local permissions + $this->updatePerms($vars['perms'], $errors); $this->isadmin = $vars['isadmin']; - $this->isactive = $vars['isactive']; + $this->isactive = isset($vars['islocked']) ? 0 : 1; $this->isvisible = isset($vars['isvisible'])?1:0; $this->onvacation = isset($vars['onvacation'])?1:0; $this->assigned_only = isset($vars['assigned_only'])?1:0; - $this->dept_id = $vars['dept_id']; $this->role_id = $vars['role_id']; - $this->group_id = $vars['group_id']; $this->timezone = $vars['timezone']; $this->username = $vars['username']; $this->firstname = $vars['firstname']; @@ -936,25 +984,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { $this->signature = Format::sanitize($vars['signature']); $this->notes = Format::sanitize($vars['notes']); - // Update the user's password if requested - if ($vars['passwd1']) { - try { - $this->setPassword($vars['passwd1'], null); - } - catch (BadPassword $ex) { - $errors['passwd1'] = $ex->getMessage(); - } - catch (PasswordUpdateFailed $ex) { - // TODO: Add a warning banner or crash the update - } - if (isset($vars['change_passwd'])) - $this->change_passwd = 1; - } - elseif (!isset($vars['change_passwd'])) { - $this->change_passwd = 0; - } + if ($errors) + return false; - if ($this->save() && $this->updateTeams($vars['teams'])) { + if ($this->save()) { if ($vars['welcome_email']) $this->sendResetEmail('registration-staff', false); return true; @@ -969,9 +1002,380 @@ implements AuthenticatedUser, EmailContact, TemplateVariable { } return false; } + + /** + * Parameters: + * $access - (<array($dept_id, $role_id, $alerts)>) a list of the complete, + * extended access for this agent. Any the agent currently has, which + * is not listed will be removed. + * $errors - (<array>) list of error messages from the process, which will + * be indexed by the dept_id number. + */ + function updateAccess($access, &$errors) { + reset($access); + $dropped = array(); + foreach ($this->dept_access as $DA) + $dropped[$DA->dept_id] = 1; + while (list(, list($dept_id, $role_id, $alerts)) = each($access)) { + unset($dropped[$dept_id]); + if (!$role_id || !Role::lookup($role_id)) + $errors['dept_access'][$dept_id] = __('Select a valid role'); + if (!$dept_id || !Dept::lookup($dept_id)) + $errors['dept_access'][$dept_id] = __('Select a valid departent'); + if ($dept_id == $this->getDeptId()) + $errors['dept_access'][$dept_id] = __('Agent already has access to this department'); + $da = $this->dept_access->findFirst(array('dept_id' => $dept_id)); + if (!isset($da)) { + $da = StaffDeptAccess::create(array( + 'dept_id' => $dept_id, 'role_id' => $role_id + )); + $this->dept_access->add($da); + } + else { + $da->role_id = $role_id; + } + $da->setAlerts($alerts); + if (!$errors) + $da->save(); + } + if (!$errors && $dropped) { + $this->dept_access + ->filter(array('dept_id__in' => array_keys($dropped))) + ->delete(); + $this->dept_access->reset(); + } + return !$errors; + } + + function updatePerms($vars, &$errors) { + if (!$vars) { + $this->permissions = ''; + return; + } + $permissions = $this->getPermission(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k => $v) { + $permissions->set($k, in_array($k, $vars) ? 1 : 0); + } + } + $this->permissions = $permissions->toJson(); + return true; + } + } interface RestrictedAccess { function checkStaffPerm($staff); } -?> + +class StaffDeptAccess extends VerySimpleModel { + static $meta = array( + 'table' => STAFF_DEPT_TABLE, + 'pk' => array('staff_id', 'dept_id'), + 'select_related' => array('dept', 'role'), + 'joins' => array( + 'dept' => array( + 'constraint' => array('dept_id' => 'Dept.id'), + ), + 'staff' => array( + 'constraint' => array('staff_id' => 'Staff.staff_id'), + ), + 'role' => array( + 'constraint' => array('role_id' => 'Role.id'), + ), + ), + ); + + const FLAG_ALERTS = 0x0001; + + function isAlertsEnabled() { + return $this->flags & self::FLAG_ALERTS != 0; + } + + function setFlag($flag, $value) { + if ($value) + $this->flags |= $flag; + else + $this->flags &= ~$flag; + } + + function setAlerts($value) { + $this->setFlag(self::FLAG_ALERTS, $value); + } +} + +/** + * This form is used to administratively change the password. The + * ChangePasswordForm is used for an agent to change their own password. + */ +class PasswordResetForm +extends AbstractForm { + function buildFields() { + return array( + 'welcome_email' => new BooleanField(array( + 'default' => true, + 'configuration' => array( + 'desc' => __('Send the agent a password reset email'), + ), + )), + 'passwd1' => new PasswordField(array( + 'placeholder' => __('New Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('welcome_email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'passwd2' => new PasswordField(array( + 'placeholder' => __('Confirm Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('welcome_email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'change_passwd' => new BooleanField(array( + 'default' => true, + 'configuration' => array( + 'desc' => __('Require password change at next login'), + 'classes' => 'form footer', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('welcome_email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + ); + } + + function validate($clean) { + if ($clean['passwd1'] != $clean['passwd2']) + $this->getField('passwd1')->addError(__('Passwords do not match')); + } +} + +class PasswordChangeForm +extends AbstractForm { + function buildFields() { + return array( + 'current' => new PasswordField(array( + 'placeholder' => __('Current Password'), + 'required' => true, + 'autofocus' => true, + )), + 'passwd1' => new PasswordField(array( + 'label' => __('Enter a new password'), + 'placeholder' => __('New Password'), + 'required' => true, + 'layout' => new GridFluidCell(12, array('style' => 'padding-top: 20px')), + )), + 'passwd2' => new PasswordField(array( + 'placeholder' => __('Confirm Password'), + 'required' => true, + )), + ); + } + + function getInstructions() { + return __('Confirm your current password and enter a new password to continue'); + } + + function validate($clean) { + if ($clean['passwd1'] != $clean['passwd2']) + $this->getField('passwd1')->addError(__('Passwords do not match')); + } +} + +class ResetAgentPermissionsForm +extends AbstractForm { + function buildFields() { + $permissions = array(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k => $v) { + if (!$v['primary']) + continue; + $permissions[$g][$k] = "{$v['title']} — {$v['desc']}"; + } + } + return array( + 'clone' => new ChoiceField(array( + 'default' => 0, + 'choices' => + array(0 => '— '.__('Clone an existing agent').' —') + + Staff::getStaffMembers(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'perms' => new ChoiceField(array( + 'choices' => $permissions, + 'widget' => 'TabbedBoxChoicesWidget', + 'configuration' => array( + 'multiple' => true, + 'classes' => 'vertical-pad', + ), + )), + ); + } + + function getClean() { + $clean = parent::getClean(); + // Index permissions as ['ticket.edit' => 1] + $clean['perms'] = array_keys($clean['perms']); + return $clean; + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} + +class ChangeDepartmentForm +extends AbstractForm { + function buildFields() { + return array( + 'dept_id' => new ChoiceField(array( + 'default' => 0, + 'required' => true, + 'label' => __('Primary Department'), + 'choices' => + array(0 => '— '.__('Primary Department').' —') + + Dept::getDepartments(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'role_id' => new ChoiceField(array( + 'default' => 0, + 'required' => true, + 'label' => __('Primary Role'), + 'choices' => + array(0 => '— '.__('Corresponding Role').' —') + + Role::getRoles(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + 'eavesdrop' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('Maintain access to current primary department'), + 'classes' => 'form footer', + ), + )), + // alerts? + ); + } + + function getInstructions() { + return __('Change the primary department and primary role of the selected agents'); + } + + function getClean() { + $clean = parent::getClean(); + $clean['eavesdrop'] = $clean['eavesdrop'] ? 1 : 0; + return $clean; + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} + +class StaffQuickAddForm +extends AbstractForm { + static $layout = 'GridFormLayout'; + + function buildFields() { + global $cfg; + + return array( + 'firstname' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __("First Name"), + 'autofocus' => true, + ), + 'layout' => new GridFluidCell(6), + )), + 'lastname' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __("Last Name"), + ), + 'layout' => new GridFluidCell(6), + )), + 'email' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'validator' => 'email', + 'placeholder' => __('Email Address — e.g. me@mycompany.com'), + ), + )), + 'dept_id' => new ChoiceField(array( + 'label' => __('Department'), + 'required' => true, + 'choices' => Dept::getDepartments(), + 'default' => $cfg->getDefaultDeptId(), + 'layout' => new GridFluidCell(6), + )), + 'role_id' => new ChoiceField(array( + 'label' => __('Primary Role'), + 'required' => true, + 'choices' => + array(0 => __('Select Role')) + + Role::getRoles(), + 'layout' => new GridFluidCell(6), + )), + 'isadmin' => new BooleanField(array( + 'label' => __('Account Type'), + 'configuration' => array( + 'desc' => __('Agent has access to the admin panel'), + ), + 'layout' => new GridFluidCell(6), + )), + 'welcome_email' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('Send a welcome email with login information'), + ), + 'default' => true, + 'layout' => new GridFluidCell(12, array('style' => 'padding-top: 50px')), + )), + 'passwd1' => new PasswordField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __("Temporary Password"), + ), + 'visibility' => new VisibilityConstraint( + new Q(array('welcome_email' => false)) + ), + 'layout' => new GridFluidCell(6), + )), + 'passwd2' => new PasswordField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __("Confirm Password"), + ), + 'visibility' => new VisibilityConstraint( + new Q(array('welcome_email' => false)) + ), + 'layout' => new GridFluidCell(6), + )), + // TODO: Add role_id drop-down + ); + } + + function getClean() { + $clean = parent::getClean(); + list($clean['username'],) = preg_split('/[^\w.-]/', $clean['email'], 2); + if (Staff::lookup($clean['username'])) + $clean['username'] = mb_strtolower($clean['firstname']); + $clean['role_id'] = 1; + return $clean; + } +} diff --git a/include/class.team.php b/include/class.team.php index 12d14e71382714cd42cb1c1991fc804ac9e703e4..d059eba518a7af990b6d65442a1c8ca98ff26119 100644 --- a/include/class.team.php +++ b/include/class.team.php @@ -26,7 +26,6 @@ implements TemplateVariable { 'constraint' => array('lead_id' => 'Staff.staff_id'), ), 'members' => array( - 'null' => true, 'list' => true, 'reverse' => 'TeamMember.team', ), @@ -58,6 +57,13 @@ implements TemplateVariable { ); } + function getVar($tag) { + switch ($tag) { + case 'members': + return new UserList($this->getMembers()->all()); + } + } + function getId() { return $this->team_id; } @@ -74,14 +80,12 @@ implements TemplateVariable { } function getMembers() { - if (!isset($this->_members)) { $this->_members = array(); foreach ($this->members as $m) $this->_members[] = $m->staff; } - - return new UserList($this->_members); + return $this->_members; } function hasMember($staff) { @@ -106,7 +110,7 @@ implements TemplateVariable { $base = $this->ht; $base['isenabled'] = $this->isEnabled(); $base['noalerts'] = !$this->alertsEnabled(); - unset($base['staffmembers']); + unset($base['members']); return $base; } @@ -148,9 +152,6 @@ implements TemplateVariable { $errors['name']=__('Team name already exists'); } - if ($errors) - return false; - // Reset team lead if they're getting removed if (isset($this->lead_id) && $this->lead_id == $vars['lead_id'] @@ -162,20 +163,23 @@ implements TemplateVariable { ($vars['isenabled'] ? self::FLAG_ENABLED : 0) | (isset($vars['noalerts']) ? self::FLAG_NOALERTS : 0); $this->lead_id = $vars['lead_id'] ?: 0; - $this->name = $vars['name']; + $this->name = Format::striptags($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(); + // Format access update as [array(staff_id, alerts?)] + $access = array(); + if (isset($vars['members'])) { + foreach (@$vars['members'] as $staff_id) { + $access[] = array($staff_id, @$vars['member_alerts'][$staff_id]); } - - return true; } + $this->updateMembers($access, $errors); + + if ($errors) + return false; + + if ($this->save()) + return $this->members->saveAll(); if (isset($this->team_id)) { $errors['err']=sprintf(__('Unable to update %s.'), __('this team')) @@ -188,6 +192,31 @@ implements TemplateVariable { return false; } + function updateMembers($access, &$errors) { + reset($access); + $dropped = array(); + foreach ($this->members as $member) + $dropped[$member->staff_id] = 1; + while (list(, list($staff_id, $alerts)) = each($access)) { + unset($dropped[$staff_id]); + if (!$staff_id || !Staff::lookup($staff_id)) + $errors['members'][$staff_id] = __('No such agent'); + $member = $this->members->findFirst(array('staff_id' => $staff_id)); + if (!isset($member)) { + $member = TeamMember::create(array('staff_id' => $staff_id)); + $this->members->add($member); + } + $member->setAlerts($alerts); + } + if (!$errors && $dropped) { + $this->members + ->filter(array('staff_id__in' => array_keys($dropped))) + ->delete(); + $this->members->reset(); + } + return !$errors; + } + function save($refetch=false) { if ($this->dirty) $this->updated = SqlFunction::NOW(); @@ -206,11 +235,12 @@ implements TemplateVariable { return false; # Remove members of this team - $this->staffmembers->delete(); + $this->members->delete(); # Reset ticket ownership for tickets owned by this team - db_query('UPDATE '.TICKET_TABLE.' SET team_id=0 WHERE team_id=' - .db_input($id)); + Ticket::objects() + ->filter(array('team_id' => $id)) + ->update(array('team_id' => 0)); return true; } @@ -240,7 +270,6 @@ implements TemplateVariable { 'flags__hasbit'=>self::FLAG_ENABLED, 'members__staff__isactive'=>1, 'members__staff__onvacation'=>0, - 'members__staff__group__flags__hasbit'=>Group::FLAG_ENABLED, )) ->filter(array('members_count__gt'=>0)); } @@ -291,6 +320,7 @@ class TeamMember extends VerySimpleModel { static $meta = array( 'table' => TEAM_MEMBER_TABLE, 'pk' => array('team_id', 'staff_id'), + 'select_related' => array('staff'), 'joins' => array( 'team' => array( 'constraint' => array('team_id' => 'Team.team_id'), @@ -300,5 +330,52 @@ class TeamMember extends VerySimpleModel { ), ), ); + + const FLAG_ALERTS = 0x0001; + + function isAlertsEnabled() { + return $this->flags & self::FLAG_ALERTS != 0; + } + + function setFlag($flag, $value) { + if ($value) + $this->flags |= $flag; + else + $this->flags &= ~$flag; + } + + function setAlerts($value) { + $this->setFlag(self::FLAG_ALERTS, $value); + } +} + +class TeamQuickAddForm +extends AbstractForm { + function buildFields() { + return array( + 'name' => new TextboxField(array( + 'required' => true, + 'configuration' => array( + 'placeholder' => __('Name'), + 'classes' => 'span12', + 'autofocus' => true, + 'length' => 128, + ), + )), + 'lead_id' => new ChoiceField(array( + 'label' => __('Optionally select a leader for the team'), + 'default' => 0, + 'choices' => + array(0 => '— '.__('None').' —') + + Staff::getStaffMembers(), + 'configuration' => array( + 'classes' => 'span12', + ), + )), + ); + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } } -?> diff --git a/include/class.thread.php b/include/class.thread.php index 7e84d5baa643d4c3118558101f1caba8db706584..20cb0f8f246a26e351813595d7c778a640422c70 100644 --- a/include/class.thread.php +++ b/include/class.thread.php @@ -1049,9 +1049,6 @@ implements TemplateVariable { } function getVar($tag) { - if ($tag && is_callable(array($this, 'get'.ucfirst($tag)))) - return call_user_func(array($this, 'get'.ucfirst($tag))); - switch(strtolower($tag)) { case 'create_date': return new FormattedDate($this->getCreateDate()); @@ -1060,8 +1057,6 @@ implements TemplateVariable { case 'files': throw new OOBContent(OOBContent::FILES, $this->attachments->all()); } - - return false; } static function getVarScope() { @@ -2366,7 +2361,12 @@ implements TemplateVariable { $vars['threadId'] = $this->getId(); $vars['staffId'] = 0; - return MessageThreadEntry::create($vars, $errors); + if (!($message = MessageThreadEntry::create($vars, $errors))) + return $message; + + $this->lastmessage = SqlFunction::NOW(); + $this->save(); + return $message; } function addResponse($vars, &$errors) { @@ -2374,7 +2374,12 @@ implements TemplateVariable { $vars['threadId'] = $this->getId(); $vars['userId'] = 0; - return ResponseThreadEntry::create($vars, $errors); + if (!($resp = ResponseThreadEntry::create($vars, $errors))) + return $resp; + + $this->lastresponse = SqlFunction::NOW(); + $this->save(); + return $resp; } function getVar($name) { diff --git a/include/class.ticket.php b/include/class.ticket.php index 9eb714dc45235412d33b1a2ccbd3a07e203077ea..11bcda3db6cecb7a821110c15b048f284bf6eb6b 100644 --- a/include/class.ticket.php +++ b/include/class.ticket.php @@ -143,7 +143,7 @@ class TicketModel extends VerySimpleModel { function getEffectiveDate() { return Format::datetime(max( - strtotime($this->lastmessage), + strtotime($this->thread->lastmessage), strtotime($this->closed), strtotime($this->reopened), strtotime($this->created) @@ -683,7 +683,7 @@ implements RestrictedAccess, Threadable { } function getLastMessageDate() { - return $this->lastmessage; + return $this->thread->lastmessage; } function getLastMsgDate() { @@ -691,7 +691,7 @@ implements RestrictedAccess, Threadable { } function getLastResponseDate() { - return $this->lastresponse; + return $this->thread->lastresponse; } function getLastRespDate() { @@ -1283,7 +1283,6 @@ implements RestrictedAccess, Threadable { function onResponse($response, $options=array()) { $this->isanswered = 1; - $this->lastresponse = SqlFunction::NOW(); $this->save(); $vars = array_merge($options, @@ -1663,9 +1662,6 @@ implements RestrictedAccess, Threadable { function getVar($tag) { global $cfg; - if ($tag && is_callable(array($this, 'get'.ucfirst($tag)))) - return call_user_func(array($this, 'get'.ucfirst($tag))); - switch(mb_strtolower($tag)) { case 'phone': case 'phone_number': @@ -1703,7 +1699,6 @@ implements RestrictedAccess, Threadable { // answer is coerced into text return $this->_answers[$tag]; } - return false; } static function getVarScope() { @@ -2094,9 +2089,6 @@ implements RestrictedAccess, Threadable { } } - // Set the last message time here - $this->lastmessage = SqlFunction::NOW(); - if (!$alerts) return $message; //Our work is done... @@ -2951,7 +2943,7 @@ implements RestrictedAccess, Threadable { } $user_form = UserForm::getUserForm()->getForm($vars); - $can_create = !$thisstaff || $thisstaff->getRole()->hasPerm(User::PERM_CREATE); + $can_create = !$thisstaff || $thisstaff->hasPerm(User::PERM_CREATE); if (!$user_form->isValid($field_filter('user')) || !($user=User::fromVars($user_form->getClean(), $can_create)) ) { diff --git a/include/class.user.php b/include/class.user.php index ad3302a2c3b95c3442abefb95faf53b7768a0324..d9a7328616065df3be453037ee676b1af0902c8a 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -360,9 +360,6 @@ implements TemplateVariable { } function getVar($tag) { - if($tag && is_callable(array($this, 'get'.ucfirst($tag)))) - return call_user_func(array($this, 'get'.ucfirst($tag))); - $tag = mb_strtolower($tag); foreach ($this->getDynamicData() as $e) if ($a = $e->getAnswer($tag)) diff --git a/include/class.variable.php b/include/class.variable.php index 869c87f9b4a6f6f38f458d02bf725abef01165ba..8c10fc61c76d245e9c7c0f2ad4ee1a6ca5ed184e 100644 --- a/include/class.variable.php +++ b/include/class.variable.php @@ -59,38 +59,48 @@ class VariableReplacer { function getVar($obj, $var) { - if(!$obj) return ""; + if (!$obj) + return ""; - if (!$var) { - if (method_exists($obj, 'asVar')) - return call_user_func(array($obj, 'asVar'), $this); - elseif (method_exists($obj, '__toString')) - return (string) $obj; + // Order or resolving %{... .tag.remainder} + // 1. $obj[$tag] + // 2. $obj->tag + // 3. $obj->getVar(tag) + // 4. $obj->getTag() + @list($tag, $remainder) = explode('.', $var ?: '', 2); + $tag = mb_strtolower($tag); + $rv = null; + + if (!is_object($obj)) { + if ($tag && is_array($obj) && array_key_exists($tag, $obj)) + $rv = $obj[$tag]; + else + // Not able to continue the lookup + return ''; } - - list($v, $part) = explode('.', $var, 2); - if ($v && is_callable(array($obj, 'get'.ucfirst($v)))) { - $rv = call_user_func(array($obj, 'get'.ucfirst($v))); - if(!$rv || !is_object($rv)) - return $rv; - - return $this->getVar($rv, $part); + else { + if (!$var) { + if (method_exists($obj, 'asVar')) + return call_user_func(array($obj, 'asVar'), $this); + elseif (method_exists($obj, '__toString')) + return (string) $obj; + } + if (method_exists($obj, 'getVar')) { + $rv = $obj->getVar($tag, $this); + } + if (!isset($rv) && property_exists($obj, $tag)) { + $rv = $obj->{$tag}; + } + if (!isset($rv) && is_callable(array($obj, 'get'.ucfirst($tag)))) { + $rv = call_user_func(array($obj, 'get'.ucfirst($tag))); + } } - if (is_array($obj) && isset($obj[$v])) - return $obj[$v]; - - if (!$var || !method_exists($obj, 'getVar')) - return ""; - - list($tag, $remainder) = explode('.', $var, 2); - if(($rv = call_user_func(array($obj, 'getVar'), $tag, $this))===false) - return ""; - - if(!is_object($rv)) - return $rv; + // Recurse with $rv + if (is_object($rv) || $remainder) + return $this->getVar($rv, $remainder); - return $this->getVar($rv, $remainder); + return $rv; } function replaceVars($input) { diff --git a/include/client/faq.inc.php b/include/client/faq.inc.php index 36c25d5cfe2912d52dfb8984dbd15dca7c80aaf1..3ab1c273c8429a69fbc82aaa6d5497e3330a49b2 100644 --- a/include/client/faq.inc.php +++ b/include/client/faq.inc.php @@ -63,5 +63,3 @@ if ($faq->getHelpTopics()->count()) { ?> </div> </div> - -<?php $faq->logView(); ?> diff --git a/include/i18n/en_US/role.yaml b/include/i18n/en_US/role.yaml index 3be09a6392a5c3f8e92d880c18048812b8c77a0f..ca76b2650513d4ffcf0d5bb48fa57cb07b203757 100644 --- a/include/i18n/en_US/role.yaml +++ b/include/i18n/en_US/role.yaml @@ -32,18 +32,6 @@ task.close, task.delete, canned.manage, - faq.manage, - stats.agents, - emails.banlist, - user.create, - user.edit, - user.delete, - user.manage, - user.dir, - org.create, - org.edit, - org.delete, - search.all, thread.edit] - id: 2 @@ -65,18 +53,7 @@ task.transfer, task.reply, task.close, - canned.manage, - faq.manage, - stats.agents, - emails.banlist, - user.create, - user.edit, - user.delete, - user.manage, - user.dir, - org.create, - org.edit, - org.delete] + canned.manage] - id: 3 flags: 1 @@ -92,9 +69,10 @@ task.create, task.assign, task.transfer, - task.reply, - user.edit, - user.manage, - user.dir, - org.create, - org.edit] + task.reply] + +- id: 4 + flags: 1 + name: View only + notes: Simple role with no permissions + permissions: [] diff --git a/include/i18n/en_US/team.yaml b/include/i18n/en_US/team.yaml index 16ae1244976ab7ed90a88e795a7a3395e986022b..136de6505dc57c689d06142771e897069423f129 100644 --- a/include/i18n/en_US/team.yaml +++ b/include/i18n/en_US/team.yaml @@ -2,15 +2,15 @@ # Initial teams defined for the system. # # Fields: -# isenabled - (bool:1|0) true or false if the team should be initially +# flags - (int) +# - isenabled - (0x01) true or false if the team should be initially # enabled -# noalerts - (bool:1|0) +# - noalerts - (0x02) # name - Descriptive name for the team # notes - Administrative notes (viewable internal only) # --- -- isenabled: 1 - noalerts: 0 +- flags: 0x01 name: Level I Support notes: | Tier 1 support, responsible for the initial iteraction with customers diff --git a/include/staff/apikeys.inc.php b/include/staff/apikeys.inc.php index d244ea256064c42701d4aa28fe167728f14c6191..749db038e9953a842fb09f74edd58d29fd0effb9 100644 --- a/include/staff/apikeys.inc.php +++ b/include/staff/apikeys.inc.php @@ -40,14 +40,32 @@ else $showing=__('No API keys found!'); ?> +<form action="apikeys.php" method="POST" name="keys"> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('API Keys');?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="apikeys.php?a=add" class="Icon newapi"><?php echo __('Add New API Key');?></a></b></div> + <a href="apikeys.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New API Key');?></a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="apikeys.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="apikeys.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="apikeys.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> -<form action="apikeys.php" method="POST" name="keys"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -105,11 +123,7 @@ else if($res && $num): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>"> -</p> + <?php endif; ?> diff --git a/include/staff/banlist.inc.php b/include/staff/banlist.inc.php index 4093439573edb4df326db69e7b5c9763c4a63944..1415b61219f8521c77d99ec0a51f2b5043b5990a 100644 --- a/include/staff/banlist.inc.php +++ b/include/staff/banlist.inc.php @@ -51,7 +51,7 @@ $query="$select $from $where ORDER BY $order_by LIMIT ".$pageNav->getStart()."," <h2><?php echo __('Banned Email Addresses');?> <i class="help-tip icon-question-sign" href="#ban_list"></i> </h2> -<div class="pull-left" style="width:600;padding-top:5px;"> +<div class="pull-left" style="margin-bottom:5px;"> <form action="banlist.php" method="GET" name="filter"> <input type="hidden" name="a" value="filter" > <div> @@ -62,7 +62,28 @@ $query="$select $from $where ORDER BY $order_by LIMIT ".$pageNav->getStart()."," </div> </form> </div> -<div class="pull-right flush-right" style="padding-right:5px;"><b><a href="banlist.php?a=add" class="Icon newstaff"><?php echo __('Ban New Email');?></a></b></div> +<form action="banlist.php" method="POST" name="banlist"> +<div class="pull-right flush-right" style="margin-bottom:5px;"> + <a href="banlist.php?a=add" class="red button action-button"><i class="icon-ban-circle"></i> <?php echo __('Ban New Email');?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="banlist.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="banlist.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li><a class="confirm" data-name="delete" href="banlist.php?a=delete"> + <i class="icon-undo icon-fixed-width"></i> + <?php echo __('Remove'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> <?php if(($res=db_query($query)) && ($num=db_num_rows($res))) @@ -74,7 +95,6 @@ if($search) $showing=__('Search Results').': '.$showing; ?> -<form action="banlist.php" method="POST" name="banlist"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -129,13 +149,7 @@ if($search) if($res && $num): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>" > - - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>"> -</p> + <?php endif; ?> diff --git a/include/staff/category.inc.php b/include/staff/category.inc.php index a506b90ab1bde45edd2015cee18d5e5112e0fc9a..c1b4dcda8f8534268408a8a04958bbb21b1e4a6d 100644 --- a/include/staff/category.inc.php +++ b/include/staff/category.inc.php @@ -1,6 +1,6 @@ <?php if (!defined('OSTSCPINC') || !$thisstaff - || !$thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) + || !$thisstaff->hasPerm(FAQ::PERM_MANAGE)) die('Access Denied'); $info=array(); diff --git a/include/staff/department.inc.php b/include/staff/department.inc.php index 82653e9b4e54ef7fd8d9147d661e8745787c2b7c..ba30fdb29148172cc805f00ec668edad7199ac73 100644 --- a/include/staff/department.inc.php +++ b/include/staff/department.inc.php @@ -8,9 +8,10 @@ if($dept && $_REQUEST['a']!='add') { $submit_text=__('Save Changes'); $info = $dept->getInfo(); $info['id'] = $dept->getId(); - $info['groups'] = $dept->getAllowedGroups(); $qs += array('id' => $dept->getId()); } else { + if (!$dept) + $dept = Dept::create(); $title=__('Add New Department'); $action='create'; $submit_text=__('Create Dept'); @@ -35,7 +36,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <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> + <i class="icon-user"></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"> @@ -290,87 +291,69 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); </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> - - <label> - <?php - $ck = ($info['groups'] && in_array($group->getId(), $info['groups'])) ? 'checked="checked"' : ''; - echo sprintf('%s %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">— <?php - echo sprintf('%s (%s)', - __('Group Default'), - $group->getRole()); - ?> - —</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> +<div id="access" class="hidden tab_content"> + <table class="two-column table" width="100%"> + <tbody> + <tr class="header"> + <td colspan="2"> + <?php echo __('Department Members'); ?> + <div><small> + <?php echo __('Agents who are primary members of this department'); ?> + </small></div> </td> - </tr> - <?php - } ?> - </tbody> - <tfoot> - <tr> + </tr> +<?php +$agents = Staff::getStaffMembers(); +foreach ($dept->getMembers() as $member) { + unset($agents[$member->getId()]); +} ?> + <tr id="add_extended_access"> <td colspan="2"> - <?php echo __('Select');?>: - <a id="selectAll" href="#grp-ckb"><?php echo __('All');?></a> - <a id="selectNone" href="#grp-ckb"><?php echo __('None');?></a> - <a id="selectToggle" href="#grp-ckb"><?php echo __('Toggle');?></a> + <i class="icon-plus-sign"></i> + <select id="add_access" data-quick-add="staff"> + <option value="0">— <?php echo __('Select Agent');?> —</option> + <?php + foreach ($agents as $id=>$name) { + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="action-button"> + <?php echo __('Add'); ?> + </button> + </td> + </tr> + </tbody> + <tbody> + <tr id="member_template" class="hidden"> + <td> + <input type="hidden" data-name="members[]" value="" /> </td> - </tr> - </tfoot> - </table> + <td> + <select data-name="member_role" data-quick-add="role"> + <option value="0">— <?php echo __('Select Role');?> —</option> + <?php + foreach (Role::getRoles() as $id=>$name) { + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <span style="display:inline-block;width:60px"> </span> + <label> + <input type="checkbox" data-name="member_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> + <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> + </tr> + </tbody> + </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');?>"> @@ -378,3 +361,74 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); onclick='window.location.href="?"'> </p> </form> + +<script type="text/javascript"> +var addAccess = function(staffid, name, role, alerts, primary, error) { + if (!staffid) return; + var copy = $('#member_template').clone(); + + copy.find('td:first').append(document.createTextNode(name)); + if (primary) { + copy.find('td:first').append($('<span class="faded">').text(primary)); + copy.find('td:last').empty(); + } + else { + copy.find('[data-name^=member_alerts]') + .attr('name', 'member_alerts['+staffid+']') + .prop('checked', alerts); + copy.find('[data-name^=member_role]') + .attr('name', 'member_role['+staffid+']') + .val(role || 0); + copy.find('[data-name=members\\[\\]]') + .attr('name', 'members[]') + .val(staffid); + } + copy.attr('id', '').show().insertBefore($('#add_extended_access')); + copy.removeClass('hidden') + if (error) + $('<div class="error">').text(error).appendTo(copy.find('td:last')); +}; + +$('#add_extended_access').find('button').on('click', function() { + var selected = $('#add_access').find(':selected'); + addAccess(selected.val(), selected.text(), 0, true); + selected.remove(); + return false; +}); + +$(document).on('click', 'a.drop-membership', function() { + var tr = $(this).closest('tr'); + $('#add_access').append( + $('<option>') + .attr('value', tr.find('input[name^=members][type=hidden]').val()) + .text(tr.find('td:first').text()) + ); + tr.fadeOut(function() { $(this).remove(); }); + return false; +}); + +<?php +if ($dept) { + $members = $dept->members->all(); + foreach ($dept->extended as $x) { + $members[] = new AnnotatedModel($x->staff, array( + 'alerts' => $x->isAlertsEnabled(), + 'role_id' => $x->role_id, + )); + } + usort($members, function($a, $b) { return strcmp($a->getName(), $b->getName()); }); + + foreach ($members as $member) { + $primary = $member->dept_id == $info['id']; + echo sprintf('addAccess(%d, %s, %d, %d, %s, %s);', + $member->getId(), + JsonDataEncoder::encode((string) $member->getName()), + $member->role_id, + $member->get('alerts', 0), + JsonDataEncoder::encode($primary ? ' — '.__('Primary') : ''), + JsonDataEncoder::encode($errors['members'][$member->staff_id]) + ); + } +} +?> +</script> diff --git a/include/staff/departments.inc.php b/include/staff/departments.inc.php index c07f2d32b60f31a2fe5d0fad5520c4a88c7637b6..6e5f35c2bc6a9a82adb5e571722e462a1af108ef 100644 --- a/include/staff/departments.inc.php +++ b/include/staff/departments.inc.php @@ -39,13 +39,29 @@ $qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); $pageNav->setURL('departments.php', $qs); $showing = $pageNav->showing().' '._N('department', 'departments', $count); ?> -<div class="pull-left" style="width:700px;padding-top:5px;"> - <h2><?php echo __('Departments');?></h2> - </div> -<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"> +<div class="sticky bar"> + <div class="content"> + <div class="pull-left"> + <h2><?php echo __('Departments');?></h2> + </div> + <div class="pull-right flush-right"> + <a href="departments.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Department');?></a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li class="danger"><a class="confirm" data-name="delete" href="departments.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + </div> + <div class="clear"></div> + </div> +</div> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -136,14 +152,9 @@ $showing = $pageNav->showing().' '._N('department', 'departments', $count); </tfoot> </table> <?php -if ($count): //Show options.. +if ($count): echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; - ?> - <p class="centered" id="actions"> - <input class="button" type="submit" name="delete" value="<?php echo - __('Delete'); - ?>" > - </p> +?> <?php endif; ?> diff --git a/include/staff/dynamic-forms.inc.php b/include/staff/dynamic-forms.inc.php index 47754fd054b9749ca5bab4a5c553109d96ea7916..65bcc43c64016b0115069493f9753f740a08656f 100644 --- a/include/staff/dynamic-forms.inc.php +++ b/include/staff/dynamic-forms.inc.php @@ -1,9 +1,25 @@ -<div class="pull-left" style="width:700;padding-top:5px;"> +<form action="forms.php" method="POST" name="forms"> + +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Custom Forms'); ?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> -<b><a href="forms.php?a=add" class="Icon form-add"><?php - echo __('Add New Custom Form'); ?></a></b></div> +<a href="forms.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php + echo __('Add New Custom Form'); ?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li class="danger"><a class="confirm" data-name="delete" href="forms.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + +</div> <div class="clear"></div> <?php @@ -18,7 +34,6 @@ $pageNav->setURL('forms.php'); $showing=$pageNav->showing().' '._N('form','forms',$count); ?> -<form action="forms.php" method="POST" name="forms"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -98,9 +113,7 @@ $showing=$pageNav->showing().' '._N('form','forms',$count); if ($count) //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete'); ?>"> -</p> + </form> <div style="display:none;" class="dialog" id="confirm-action"> diff --git a/include/staff/dynamic-lists.inc.php b/include/staff/dynamic-lists.inc.php index 6d294a587fca5c19e3cf66441ac331de7eb7467b..a31784452e12f04ef8021d8276eadbfa5999256b 100644 --- a/include/staff/dynamic-lists.inc.php +++ b/include/staff/dynamic-lists.inc.php @@ -1,9 +1,24 @@ -<div class="pull-left" style="width:700;padding-top:5px;"> +<form action="lists.php" method="POST" name="lists"> + +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Custom Lists'); ?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="lists.php?a=add" class="Icon list-add"><?php - echo __('Add New Custom List'); ?></a></b></div> + <a href="lists.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php + echo __('Add New Custom List'); ?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li class="danger"><a class="confirm" data-name="delete" href="lists.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> <?php @@ -14,7 +29,6 @@ $pageNav->setURL('lists.php'); $showing=$pageNav->showing().' '._N('custom list', 'custom lists', $count); ?> -<form action="lists.php" method="POST" name="lists"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -77,9 +91,6 @@ if ($count) //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete'); ?>"> -</p> </form> <div style="display:none;" class="dialog" id="confirm-action"> diff --git a/include/staff/emails.inc.php b/include/staff/emails.inc.php index 5862d4a14e84722d9a5b9e7ea140a3e393616e6a..a64186b5fadb26be460b8964c2b0330c6582afd2 100644 --- a/include/staff/emails.inc.php +++ b/include/staff/emails.inc.php @@ -39,13 +39,26 @@ $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;"> +<form action="emails.php" method="POST" name="emails"> + +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Email Addresses');?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="emails.php?a=add" class="Icon newEmail"><?php echo __('Add New Email');?></a></b></div> + <a href="emails.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Email');?></a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li class="danger"><a class="confirm" data-name="delete" href="emails.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> -<form action="emails.php" method="POST" name="emails"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -117,9 +130,7 @@ $def_priority = $cfg->getDefaultPriority()->getDesc(); if ($count): echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete Email(s)');?>" > -</p> + <?php endif; ?> diff --git a/include/staff/faq-category.inc.php b/include/staff/faq-category.inc.php index b6287bcb9eb3779a9aab6aa5a49dc244683e1856..9f2ff86dd3f548ab34a9064e18fabc8be2ab750c 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->getRole()->hasPerm(FAQ::PERM_MANAGE)) { +if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { 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 05a6b0f48cf50d15087677dcb286707f0003c944..be15f6f7ef2c71c302d68e04fa4a09ee23364106 100644 --- a/include/staff/faq-view.inc.php +++ b/include/staff/faq-view.inc.php @@ -78,7 +78,7 @@ $query = http_build_query($query); ?> <?php echo __('Print'); ?> </a> <?php -if ($thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) { ?> +if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { ?> <a href="faq.php?id=<?php echo $faq->getId(); ?>&a=edit" class="action-button"> <i class="icon-edit"></i> <?php echo __('Edit FAQ'); ?> @@ -102,12 +102,12 @@ if ($thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) { ?> <hr> <?php -if ($thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) { ?> +if ($thisstaff->hasPerm(FAQ::PERM_MANAGE)) { ?> <form action="faq.php?id=<?php echo $faq->getId(); ?>" method="post"> <?php csrf_token(); ?> <input type="hidden" name="do" value="manage-faq"> <input type="hidden" name="id" value="<?php echo $faq->getId(); ?>"> - <button name="a" value="delete"><?php echo __('Delete FAQ'); ?></button> + <button name="a" class="button" value="delete"><?php echo __('Delete FAQ'); ?></button> </form> <?php } ?> diff --git a/include/staff/faq.inc.php b/include/staff/faq.inc.php index 89584be75d0c313e9e74bc7811076bd000e5e764..fa2f9316bf6e3fb5586b1663e66d8ebe536ad3ac 100644 --- a/include/staff/faq.inc.php +++ b/include/staff/faq.inc.php @@ -1,6 +1,6 @@ <?php if (!defined('OSTSCPINC') || !$thisstaff - || !$thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) + || !$thisstaff->hasPerm(FAQ::PERM_MANAGE)) die('Access Denied'); $info = $qs = array(); diff --git a/include/staff/filters.inc.php b/include/staff/filters.inc.php index ef04bf366c44bd7830830c8e499c08e12d964b8f..5d81d4e00864be5a335383fcac9f71c24d743fa6 100644 --- a/include/staff/filters.inc.php +++ b/include/staff/filters.inc.php @@ -44,14 +44,35 @@ else $showing=__('No filters found!'); ?> +<form action="filters.php" method="POST" name="filters"> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Ticket Filters');?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="filters.php?a=add" class="Icon newTicketFilter"><?php echo __('Add New Filter');?></a></b></div> + <a href="filters.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Filter');?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="filters.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="filters.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="filters.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + + +</div> <div class="clear"></div> -<form action="filters.php" method="POST" name="filters"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -114,11 +135,7 @@ else if($res && $num): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>"> - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>"> -</p> + <?php endif; ?> diff --git a/include/staff/group.inc.php b/include/staff/group.inc.php deleted file mode 100644 index 032d0b3f05d5652ae8370449cb7a21373a81f9f6..0000000000000000000000000000000000000000 --- a/include/staff/group.inc.php +++ /dev/null @@ -1,184 +0,0 @@ -<?php - -if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); -$info = $qs = array(); -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'); - $qs += array('id' => $group->getId()); -} else { - $title = __('Add New Group'); - $action = 'add'; - $submit_text = __('Create Group'); - $info['isactive'] = isset($info['isactive']) ? $info['isactive'] : 1; - $qs += array('a' => $_REQUEST['a']); -} - -$info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $info); -$roles = Role::getActiveRoles(); - -?> -<form action="groups.php?<?php echo Http::build_query($qs); ?>" 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 ?: __('New Group'); ?></h2> -<ul class="clean 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><?php echo __( - 'Roles are used to define agents\' permissions' - ); ?> <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']; ?>" - autofocus data-translate-tag="<?php echo $trans['name']; ?>"/> - <span class="error">* <?php echo $errors['name']; ?></span> - </td> - </tr> - <tr> - <td width="180" class="required"> - <?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="0" <?php - echo !$info['isactive'] ? 'checked="checked"' : ''; ?>><strong><?php echo __('Disabled');?></strong> - <span class="error">* <?php echo $errors['status']; ?></span> - <i class="help-tip icon-question-sign" href="#status"></i> - </td> - </tr> - <tr> - <td width="180" class="required"> - <?php echo __('Default Role');?>: - </td> - <td> - <select name="role_id"> - <option value="0"><?php echo __('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> - <span class="error">* <?php echo $errors['role_id']; ?></span> - <i class="help-tip icon-question-sign" href="#role"></i> - </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="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><?php echo __('Check departments the group is allowed to access and optionally select an effective role.') ?></em> - </th> - </tr> - <tr> - <th width="40%"><?php echo __('Department'); ?></th> - <th><?php echo __('Group Role'); ?></th> - </tr> - </thead> - <tbody> - <?php - foreach (Dept::getDepartments() as $deptId => $name) { ?> - <tr> - <td> - - <label> - <?php - $ck = ($info['depts'] && in_array($deptId, $info['depts'])) ? 'checked="checked"' : ''; - echo sprintf('%s %s', - sprintf('<input type="checkbox" class="dept-ckb" - name="depts[]" value="%s" %s />', - $deptId, $ck), - Format::htmlchars($name)); - ?> - </label> - </td> - <td> - <?php - $DeptAccess = $group ? $group->getDepartmentsAccess() : array(); - $_name = 'dept'.$deptId.'_role_id'; - ?> - <select name="<?php echo $_name; ?>"> - <option value="0">— <?php - echo __('Group Default'); ?><?php - if (isset($group)) echo ' ('.$group->role->getName().')'; - ?> —</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> - <tfoot> - <tr> - <td colspan="2"> - <?php echo __('Select');?>: - <a id="selectAll" href="#dept-ckb"><?php echo __('All');?></a> - <a id="selectNone" href="#dept-ckb"><?php echo __('None');?></a> - <a id="selectToggle" href="#dept-ckb"><?php echo __('Toggle');?></a> - </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="?"'> -</p> -</form> diff --git a/include/staff/groups.inc.php b/include/staff/groups.inc.php deleted file mode 100644 index c1ec406f9510422ae1d5bfac84e9f4c3d55cdeae..0000000000000000000000000000000000000000 --- a/include/staff/groups.inc.php +++ /dev/null @@ -1,175 +0,0 @@ -<?php -if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) - die('Access Denied'); - -$qs = array(); -$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'; - -//Sorting options... -if ($sort && $sortOptions[$sort]) { - $order_column = $sortOptions[$sort]; -} - -$order_column = $order_column ? $order_column : 'name'; - -if ($_REQUEST['order'] && isset($orderWays[strtoupper($_REQUEST['order'])])) { - $order = $orderWays[strtoupper($_REQUEST['order'])]; -} else { - $order = 'ASC'; -} - -if ($order_column && strpos($order_column,',')) { - $order_column=str_replace(','," $order,",$order_column); -} -$x=$sort.'_sort'; -$$x=' class="'.strtolower($order).'" '; -$page = ($_GET['p'] && is_numeric($_GET['p'])) ? $_GET['p'] : 1; -$count = Group::objects()->count(); -$pageNav = new Pagenate($count, $page, PAGE_LIMIT); -$qstr = '&'. Http::build_query($qs); -$qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); -$qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); -$pageNav->setURL('pages.php', $qs); -$showing = $pageNav->showing().' '._N('group', 'groups', $count); -?> -<div class="pull-left" style="width:700px;padding-top:5px;"> - <h2><?php echo __('Agent Groups');?> - <i class="help-tip icon-question-sign" href="#groups"></i> - </h2> - </div> -<div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="groups.php?a=add" class="Icon newgroup"><?php echo __('Add New Group');?></a></b></div> -<div class="clear"></div> -<form action="groups.php" method="POST" name="groups"> - <?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="7px"> </th> - <th width="200"><a <?php echo $name_sort; ?> href="groups.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Group Name');?></a></th> - <th width="80"><a <?php echo $status_sort; ?> href="groups.php?<?php echo $qstr; ?>&sort=status"><?php echo __('Status');?></a></th> - <th width="80" style="text-align:center;"><a <?php echo $users_sort; ?>href="groups.php?<?php echo $qstr; ?>&sort=users"><?php echo __('Members');?></a></th> - <th width="80" style="text-align:center;"><a <?php echo $depts_sort; ?>href="groups.php?<?php echo $qstr; ?>&sort=depts"><?php echo __('Departments');?></a></th> - <th width="100"><a <?php echo $created_sort; ?> href="groups.php?<?php echo $qstr; ?>&sort=created"><?php echo __('Created On');?></a></th> - <th width="120"><a <?php echo $updated_sort; ?> href="groups.php?<?php echo $qstr; ?>&sort=updated"><?php echo __('Last Updated');?></a></th> - </tr> - </thead> - <tbody> - <?php - $total=0; - $ids = ($errors && is_array($_POST['ids'])) ? $_POST['ids'] : null; - if ($count) { - $groups= Group::objects() - ->annotate(array( - 'members_count'=>SqlAggregate::COUNT('members__staff_id', 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; - $id = $group->getId(); - if($ids && in_array($id, $ids)) - $sel=true; - ?> - <tr id="<?php echo $id; ?>"> - <td width=7px> - <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> </td> - <td> <?php echo $group->isenabled ? __('Active') : '<b>'.__('Disabled').'</b>'; ?></td> - <td style="text-align:right;padding-right:30px"> - <?php if ($num=$group->members_count) { ?> - <a href="staff.php?gid=<?php echo $id; ?>"><?php echo $num; ?></a> - <?php } else { ?> 0 - <?php } ?> - - </td> - <td style="text-align:right;padding-right:30px"> - <?php echo $group->depts_count; ?> - </td> - <td><?php echo Format::date($group->getCreateDate()); ?> </td> - <td><?php echo Format::datetime($group->getUpdateDate()); ?> </td> - </tr> - <?php - } //end of while. - } ?> - <tfoot> - <tr> - <td colspan="7"> - <?php if ($count) { ?> - <?php echo __('Select');?>: - <a id="selectAll" href="#ckb"><?php echo __('All');?></a> - <a id="selectNone" href="#ckb"><?php echo __('None');?></a> - <a id="selectToggle" href="#ckb"><?php echo __('Toggle');?></a> - <?php }else{ - echo __('No groups found!'); - } ?> - </td> - </tr> - </tfoot> -</table> -<?php -if ($count): - echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; -?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>" > - <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> - <hr/> - <p class="confirm-action" style="display:none;" id="enable-confirm"> - <?php echo sprintf(__('Are you sure you want to <b>enable</b> %s?'), - _N('selected group', 'selected groups', 2));?> - </p> - <p class="confirm-action" style="display:none;" id="disable-confirm"> - <?php echo sprintf(__('Are you sure you want to <b>disable</b> %s?'), - _N('selected group', 'selected groups', 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 group', 'selected groups', 2));?></strong></font> - <br><br><?php echo __("Deleted data CANNOT be recovered and might affect agents' access.");?> - </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/header.inc.php b/include/staff/header.inc.php index 8ceef837a5488e15199e1d04402ae495ffb94b5f..0b595def74aa9406e6b6f09d2a75e4a1ea06920c 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -65,7 +65,7 @@ if ($lang) { <?php }else{ ?> | <a href="index.php" class="no-pjax"><?php echo __('Agent Panel'); ?></a> <?php } ?> - | <a href="profile.php"><?php echo __('My Preferences'); ?></a> + | <a href="profile.php"><?php echo __('Profile'); ?></a> | <a href="logout.php?auth=<?php echo $ost->getLinkToken(); ?>" class="no-pjax"><?php echo __('Log Out'); ?></a> </p> <a href="index.php" class="no-pjax" id="logo"> diff --git a/include/staff/helptopic.inc.php b/include/staff/helptopic.inc.php index f6dbcfe7bb813d619ccbf60043e9ab2a940457fb..bd3e28dc30b292f93b351293db76ba804b03d86b 100644 --- a/include/staff/helptopic.inc.php +++ b/include/staff/helptopic.inc.php @@ -122,14 +122,14 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php echo __('Department'); ?>: </td> <td> - <select name="dept_id"> + <select name="dept_id" data-quick-add="department"> <option value="0">— <?php echo __('System Default'); ?> —</option> <?php foreach (Dept::getDepartments() as $id=>$name) { $selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':''; echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name); - } - ?> + } ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <span class="error"> <?php echo $errors['dept_id']; ?></span> <i class="help-tip icon-question-sign" href="#department"></i> @@ -286,7 +286,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); <?php echo __('Auto-assign To');?>: </td> <td> - <select name="assign"> + <select name="assign" data-quick-add> <option value="0">— <?php echo __('Unassigned'); ?> —</option> <?php if (($users=Staff::getStaffMembers())) { @@ -302,19 +302,20 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); } echo '</OPTGROUP>'; } - if (($teams=Team::getTeams())) { - echo sprintf('<OPTGROUP label="%s">', - sprintf(__('Teams (%d)'), count($teams))); + if ($teams = Team::getTeams()) { ?> + <optgroup data-quick-add="team" label="<?php + echo sprintf(__('Teams (%d)'), count($teams)); ?>"><?php foreach ($teams as $id => $name) { $k="t$id"; $selected = ($info['assign']==$k || $info['team_id']==$id) ? 'selected="selected"' : ''; ?> <option value="<?php echo $k; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> <?php - } - echo '</OPTGROUP>'; - } - ?> + } ?> + <option value="0" data-quick-add data-id-prefix="t">— <?php echo __('Add New Team'); ?> —</option> + </optgroup> + <?php + } ?> </select> <span class="error"> <?php echo $errors['assign']; ?></span> <i class="help-tip icon-question-sign" href="#auto_assign_to"></i> diff --git a/include/staff/helptopics.inc.php b/include/staff/helptopics.inc.php index 1c91d64cbefa1f7abd2d8ad24fe05a3e7afec365..54d05a395d6ee0580c114a77d8091690af67ad8c 100644 --- a/include/staff/helptopics.inc.php +++ b/include/staff/helptopics.inc.php @@ -11,13 +11,39 @@ $showing = $pageNav->showing().' '._N('help topic', 'help topics', $count); $order_by = ($cfg->getTopicSortMode() == 'm') ? 'sort' : 'topic'; ?> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<form action="helptopics.php" method="POST" name="topics"> + +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Help Topics');?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="helptopics.php?a=add" class="Icon newHelpTopic"><?php echo __('Add New Help Topic');?></a></b></div> + <?php if ($cfg->getTopicSortMode() != 'a') { ?> + <input class="button no-confirm" type="submit" name="sort" value="Save"/> + <?php } ?> + <a href="helptopics.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Help Topic');?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="helptopics.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="helptopics.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="helptopics.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + + + +</div> <div class="clear"></div> -<form action="helptopics.php" method="POST" name="topics"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="sort" > @@ -126,14 +152,7 @@ $order_by = ($cfg->getTopicSortMode() == 'm') ? 'sort' : 'topic'; if ($count): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> -<?php if ($cfg->getTopicSortMode() != 'a') { ?> - <input class="button no-confirm" type="submit" name="sort" value="Save"/> -<?php } ?> - <button class="button" type="submit" name="enable" value="Enable" ><?php echo __('Enable'); ?></button> - <button class="button" type="submit" name="disable" value="Disable"><?php echo __('Disable'); ?></button> - <button class="button" type="submit" name="delete" value="Delete"><?php echo __('Delete'); ?></button> -</p> + <?php endif; ?> @@ -168,4 +187,3 @@ endif; </p> <div class="clear"></div> </div> - diff --git a/include/staff/org-view.inc.php b/include/staff/org-view.inc.php index 94e658d24c39cb02253dfb2d2538be5d6f369421..becfd7adbe80894e81f77e3fae0b7baff9b24680 100644 --- a/include/staff/org-view.inc.php +++ b/include/staff/org-view.inc.php @@ -9,20 +9,20 @@ if(!defined('OSTSCPINC') || !$thisstaff || !is_object($org)) die('Invalid path') title="Reload"><i class="icon-refresh"></i> <?php echo $org->getName(); ?></a></h2> </td> <td width="50%" class="right_align has_bottom_border"> -<?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(Organization::PERM_EDIT)) { ?> <span class="action-button pull-right" data-dropdown="#action-dropdown-more"> <i class="icon-caret-down pull-right"></i> <span ><i class="icon-cog"></i> <?php echo __('More'); ?></span> </span> <?php } ?> -<?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_DELETE)) { ?> +<?php if ($thisstaff->hasPerm(Organization::PERM_DELETE)) { ?> <a id="org-delete" class="action-button pull-right org-action" href="#orgs/<?php echo $org->getId(); ?>/delete"><i class="icon-trash"></i> <?php echo __('Delete Organization'); ?></a> <?php } ?> <div id="action-dropdown-more" class="action-dropdown anchor-right"> <ul> -<?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(Organization::PERM_EDIT)) { ?> <li><a href="#ajax.php/orgs/<?php echo $org->getId(); ?>/forms/manage" onclick="javascript: $.dialog($(this).attr('href').substr(1), 201); @@ -42,13 +42,13 @@ if(!defined('OSTSCPINC') || !$thisstaff || !is_object($org)) die('Invalid path') <tr> <th width="150"><?php echo __('Name'); ?>:</th> <td> -<?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(Organization::PERM_EDIT)) { ?> <b><a href="#orgs/<?php echo $org->getId(); ?>/edit" class="org-action"><i class="icon-edit"></i> <?php } echo $org->getName(); - if ($thisstaff->getRole()->hasPerm(Organization::PERM_EDIT)) { ?> + if ($thisstaff->hasPerm(Organization::PERM_EDIT)) { ?> </a></b> <?php } ?> </td> diff --git a/include/staff/orgs.inc.php b/include/staff/orgs.inc.php index bc41711ff8b59016163c65248071aef835107011..b614eb84c460f61399144151fdc6350ad7ff1b42 100644 --- a/include/staff/orgs.inc.php +++ b/include/staff/orgs.inc.php @@ -89,14 +89,14 @@ $_SESSION['orgs_qs_'.$qhash] = $query; </div> <div class="pull-right"> -<?php if ($thisstaff->getRole()->hasPerm(Organization::PERM_CREATE)) { ?> +<?php if ($thisstaff->hasPerm(Organization::PERM_CREATE)) { ?> <a class="action-button add-org" href="#"> <i class="icon-plus-sign"></i> <?php echo __('Add Organization'); ?> </a> <?php } -if ($thisstaff->getRole()->hasPerm(Organization::PERM_DELETE)) { ?> +if ($thisstaff->hasPerm(Organization::PERM_DELETE)) { ?> <span class="action-button" data-dropdown="#action-dropdown-more" style="/*DELME*/ vertical-align:top; margin-bottom:0"> <i class="icon-caret-down pull-right"></i> diff --git a/include/staff/pages.inc.php b/include/staff/pages.inc.php index 2398c5e573dbadf3228421a0705df5ddf1b0473e..543b6bb78209c770784622ccce33d37b73a487d7 100644 --- a/include/staff/pages.inc.php +++ b/include/staff/pages.inc.php @@ -31,19 +31,39 @@ $qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']); $pageNav->setURL('pages.php', $qs); //Ok..lets roll...create the actual query if ($total) - $showing=$pageNav->showing()._N('site page','site pages', $num); + $showing=$pageNav->showing().' '._N('site page','site pages', $num); else $showing=__('No pages found!'); ?> +<form action="pages.php" method="POST" name="tpls"> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Site Pages'); ?> <i class="help-tip icon-question-sign" href="#site_pages"></i> </h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="pages.php?a=add" class="Icon newPage"><?php echo __('Add New Page'); ?></a></b></div> + <a href="pages.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Page'); ?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="pages.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="pages.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="pages.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> <form action="pages.php" method="POST" name="tpls"> <?php csrf_token(); ?> @@ -63,7 +83,6 @@ else </thead> <tbody> <?php - $total=0; $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; $defaultPages=$cfg->getDefaultPages(); foreach ($pages as $page) { @@ -91,7 +110,7 @@ else <tfoot> <tr> <td colspan="6"> - <?php if($res && $num){ ?> + <?php if($total){ ?> <?php echo __('Select'); ?>: <a id="selectAll" href="#ckb"><?php echo __('All'); ?></a> <a id="selectNone" href="#ckb"><?php echo __('None'); ?></a> @@ -104,14 +123,10 @@ else </tfoot> </table> <?php -if($res && $num): //Show options.. +if($total): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable'); ?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable'); ?>" > - <input class="button" type="submit" name="delete" value="<?php echo __('Delete'); ?>" > -</p> + <?php endif; ?> diff --git a/include/staff/plugins.inc.php b/include/staff/plugins.inc.php index 7e05195abc4765ddad3a9e3b14ad2fe4882b9590..bbb25bd04bfef47ff1b280e62a98c8e0026039f2 100644 --- a/include/staff/plugins.inc.php +++ b/include/staff/plugins.inc.php @@ -1,9 +1,30 @@ -<div class="pull-left" style="width:700;padding-top:5px;"> +<form action="plugins.php" method="POST" name="forms"> + + <div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Currently Installed Plugins'); ?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="plugins.php?a=add" class="Icon form-add"><?php - echo __('Add New Plugin'); ?></a></b></div> + <a href="plugins.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php + echo __('Add New Plugin'); ?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="plugins.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="plugins.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="plugins.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> <?php @@ -23,7 +44,7 @@ $showing=$pageNav->showing().' '._N('plugin', 'plugins', $count); <tr> <th width="7"> </th> <th><?php echo __('Plugin Name'); ?></th> - <th><?php echo __('Status'); ?></td> + <th><?php echo __('Status'); ?></th> <th><?php echo __('Date Installed'); ?></th> </tr> </thead> @@ -63,11 +84,7 @@ foreach ($ost->plugins->allInstalled() as $p) { if ($count) //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="delete" value="<?php echo __('Delete'); ?>"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable'); ?>"> - <input class="button" type="submit" name="disable" value="<?php echo __('Disable'); ?>"> -</p> + </form> <div style="display:none;" class="dialog" id="confirm-action"> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index c942f984160efc0e6e12acc0bc87a27f5ee00d9b..bef18543809543e618d3796180aa6df85bc57fb5 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -1,139 +1,144 @@ <?php if(!defined('OSTSTAFFINC') || !$staff || !$thisstaff) die('Access Denied'); - -$info=$staff->getInfo(); -$info['signature'] = Format::viewableImages($info['signature']); -$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); -$info['id']=$staff->getId(); ?> + <form action="profile.php" method="post" id="save" autocomplete="off"> <?php csrf_token(); ?> <input type="hidden" name="do" value="update"> - <input type="hidden" name="id" value="<?php echo $info['id']; ?>"> + <input type="hidden" name="id" value="<?php echo $staff->getId(); ?>"> <h2><?php echo __('My Account Profile');?></h2> - <table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2"> - <thead> - <tr> - <th colspan="2"> - <h4><?php echo __('Account Information');?></h4> - <em><?php echo __('Contact information');?></em> - </th> - </tr> - </thead> - <tbody> - <tr> - <td width="180" class="required"> - <?php echo __('Username');?>: - </td> - <td><b><?php echo $staff->getUserName(); ?></b> <i class="help-tip icon-question-sign" href="#username"></i></td> - </tr> + <ul class="clean tabs"> + <li class="active"><a href="#account"><i class="icon-user"></i> <?php echo __('Account'); ?></a></li> + <li><a href="#preferences"><?php echo __('Preferences'); ?></a></li> + <li><a href="#signature"><?php echo __('Signature'); ?></a></li> + </ul> + + <div class="tab_content" id="account"> + <table class="table two-column" width="940" border="0" cellspacing="0" cellpadding="2"> + <tbody> <tr> - <td width="180" class="required"> - <?php echo __('First Name');?>: - </td> - <td> - <input type="text" size="34" name="firstname" value="<?php echo $info['firstname']; ?>"> - <span class="error">* <?php echo $errors['firstname']; ?></span> - </td> + <td class="required"><?php echo __('Name'); ?>:</td> + <td> + <input type="text" size="20" maxlength="64" style="width: 145px" name="firstname" + autofocus value="<?php echo Format::htmlchars($staff->firstname); ?>" + placeholder="<?php echo __("First Name"); ?>" /> + <input type="text" size="20" maxlength="64" style="width: 145px" name="lastname" + value="<?php echo Format::htmlchars($staff->lastname); ?>" + placeholder="<?php echo __("Last Name"); ?>" /> + <div class="error"><?php echo $errors['firstname']; ?></div> + <div class="error"><?php echo $errors['lastname']; ?></div> + </td> </tr> <tr> - <td width="180" class="required"> - <?php echo __('Last Name');?>: - </td> - <td> - <input type="text" size="34" name="lastname" value="<?php echo $info['lastname']; ?>"> - <span class="error">* <?php echo $errors['lastname']; ?></span> - </td> + <td class="required"><?php echo __('Email Address'); ?>:</td> + <td> + <input type="email" size="40" maxlength="64" style="width: 300px" name="email" + value="<?php echo Format::htmlchars($staff->email); ?>" + placeholder="<?php echo __('e.g. me@mycompany.com'); ?>" /> + <div class="error"><?php echo $errors['email']; ?></div> + </td> </tr> <tr> - <td width="180" class="required"> - <?php echo __('Email Address');?>: - </td> - <td> - <input type="text" size="34" name="email" value="<?php echo $info['email']; ?>"> - <span class="error">* <?php echo $errors['email']; ?></span> - </td> + <td><?php echo __('Phone Number');?>:</td> + <td> + <input type="tel" size="18" name="phone" class="auto phone" + value="<?php echo Format::htmlchars($staff->phone); ?>" /> + <?php echo __('Ext');?> + <input type="text" size="5" name="phone_ext" + value="<?php echo Format::htmlchars($staff->phone_ext); ?>"> + <div class="error"><?php echo $errors['phone']; ?></div> + <div class="error"><?php echo $errors['phone_ext']; ?></div> + </td> </tr> <tr> - <td width="180"> - <?php echo __('Phone Number');?>: - </td> - <td> - <input type="text" size="22" name="phone" value="<?php echo $info['phone']; ?>"> - <span class="error"> <?php echo $errors['phone']; ?></span> - Ext <input type="text" size="5" name="phone_ext" value="<?php echo $info['phone_ext']; ?>"> - <span class="error"> <?php echo $errors['phone_ext']; ?></span> - </td> + <td><?php echo __('Mobile Number');?>:</td> + <td> + <input type="tel" size="18" name="mobile" class="auto phone" + value="<?php echo Format::htmlchars($staff->mobile); ?>" /> + <div class="error"><?php echo $errors['mobile']; ?></div> + </td> </tr> - <tr> - <td width="180"> - <?php echo __('Mobile Number');?>: - </td> - <td> - <input type="text" size="22" name="mobile" value="<?php echo $info['mobile']; ?>"> - <span class="error"> <?php echo $errors['mobile']; ?></span> - </td> + </tbody> + <!-- ================================================ --> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Authentication'); ?> + </th> </tr> + <?php if ($bk = $staff->getAuthBackend()) { ?> <tr> - <th colspan="2"> - <em><strong><?php echo __('Preferences');?></strong>: <?php echo __('Profile preferences and settings.');?></em> - </th> + <td><?php echo __("Backend"); ?></td> + <td><?php echo $bk->getName(); ?></td> </tr> + <?php } ?> <tr> - <td width="180"> - <?php echo __('Time Zone');?>: - </td> - <td> - <?php - $TZ_NAME = 'timezone'; - $TZ_TIMEZONE = $info['timezone']; - include STAFFINC_DIR.'templates/timezone.tmpl.php'; ?> - <div class="error"><?php echo $errors['timezone']; ?></div> - </td> + <td class="required"><?php echo __('Username'); ?>: + <span class="error">*</span></td> + <td> + <input type="text" size="40" style="width:300px" + class="staff-username typeahead" + name="username" disabled value="<?php echo Format::htmlchars($staff->username); ?>" /> +<?php if (!$bk || $bk->supportsPasswordChange()) { ?> + <button type="button" class="action-button" onclick="javascript: + $.dialog('ajax.php/staff/'+<?php echo $staff->getId(); ?>+'/change-password', 201);"> + <i class="icon-refresh"></i> <?php echo __('Change Password'); ?> + </button> +<?php } ?> + <i class="offset help-tip icon-question-sign" href="#username"></i> + <div class="error"><?php echo $errors['username']; ?></div> + </td> + </tr> + </tbody> + <!-- ================================================ --> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Status and Settings'); ?> + </th> </tr> -<?php if ($cfg->getSecondaryLanguages()) { ?> <tr> - <td width="180"> - <?php echo __('Preferred Language'); ?>: - </td> - <td> - <?php - $langs = Internationalization::getConfiguredSystemLanguages(); ?> - <select name="lang"> - <option value="">— <?php echo __('Use Browser Preference'); ?> —</option> -<?php foreach($langs as $l) { - $selected = ($info['lang'] == $l['code']) ? 'selected="selected"' : ''; ?> - <option value="<?php echo $l['code']; ?>" <?php echo $selected; - ?>><?php echo Internationalization::getLanguageDescription($l['code']); ?></option> -<?php } ?> - </select> - <span class="error"> <?php echo $errors['lang']; ?></span> - </td> + <td colspan="2"> + <label> + <input type="checkbox" name="show_assigned_tickets" + <?php echo ($staff->show_assigned_tickets) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Show assigned tickets on open queue.'); ?> + </label> + <i class="help-tip icon-question-sign" href="#show_assigned_tickets"></i> + <br/> + <label> + <input type="checkbox" name="onvacation" + <?php echo ($staff->onvacation) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Vacation Mode'); ?> + </label> + <br/> </tr> -<?php } ?> -<?php if (extension_loaded('intl')) { ?> - <tr><td width="220"><?php echo __('Preferred Locale');?>:</td> - <td> - <select name="locale"> - <option value=""><?php echo __('Use Language Preference'); ?></option> -<?php foreach (Internationalization::allLocales() as $code=>$name) { ?> - <option value="<?php echo $code; ?>" <?php - if ($code == $info['locale']) - echo 'selected="selected"'; - ?>><?php echo $name; ?></option> -<?php } ?> - </select> - </td> + </tbody> + </table> + </div> + + <!-- =================== PREFERENCES ======================== --> + + <div class="hidden tab_content" id="preferences"> + <table class="table two-column" width="100%"> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Preferences'); ?> + <div><small><?php echo __( + "Profile preferences and settings" + ); ?> + </small></div> + </th> </tr> -<?php } ?> <tr> <td width="180"><?php echo __('Maximum Page size');?>:</td> <td> <select name="max_page_size"> <option value="0">— <?php echo __('system default');?> —</option> <?php - $pagelimit=$info['max_page_size']?$info['max_page_size']:$cfg->getPageSize(); + $pagelimit = $staff->max_page_size ?: $cfg->getPageSize(); for ($i = 5; $i <= 50; $i += 5) { $sel=($pagelimit==$i)?'selected="selected"':''; echo sprintf('<option value="%d" %s>'.__('show %s records').'</option>',$i,$sel,$i); @@ -142,14 +147,16 @@ $info['id']=$staff->getId(); </td> </tr> <tr> - <td width="180"><?php echo __('Auto Refresh Rate');?>:</td> + <td width="180"><?php echo __('Auto Refresh Rate');?>: + <div class="faded"><?php echo __('Tickets page refresh rate in minutes.'); ?></div> + </td> <td> <select name="auto_refresh_rate"> <option value="0">— <?php echo __('disable');?> —</option> <?php $y=1; for($i=1; $i <=30; $i+=$y) { - $sel=($info['auto_refresh_rate']==$i)?'selected="selected"':''; + $sel=($staff->auto_refresh_rate==$i)?'selected="selected"':''; echo sprintf('<option value="%1$d" %2$s>' .sprintf( _N('Every minute', 'Every %d minutes', $i), $i) @@ -158,11 +165,12 @@ $info['id']=$staff->getId(); $y=2; } ?> </select> - <em><?php echo __('(Tickets page refresh rate in minutes.)');?></em> </td> </tr> <tr> - <td width="180"><?php echo __('Default Signature');?>:</td> + <td><?php echo __('Default Signature');?>: + <div class="faded"><?php echo __('This can be selected when replying to a ticket');?></div> + </td> <td> <select name="default_signature_type"> <option value="none" selected="selected">— <?php echo __('None');?> —</option> @@ -171,16 +179,17 @@ $info['id']=$staff->getId(); __('if set' /* This is used in 'Department Signature (>if set<)' */))); foreach($options as $k=>$v) { echo sprintf('<option value="%s" %s>%s</option>', - $k,($info['default_signature_type']==$k)?'selected="selected"':'',$v); + $k,($staff->default_signature_type==$k)?'selected="selected"':'',$v); } ?> </select> - <em><?php echo __('(This can be selected when replying to a ticket)');?></em> - <span class="error"> <?php echo $errors['default_signature_type']; ?></span> + <div class="error"><?php echo $errors['default_signature_type']; ?></div> </td> </tr> <tr> - <td width="180"><?php echo __('Default Paper Size');?>:</td> + <td width="180"><?php echo __('Default Paper Size');?>: + <div class="faded"><?php echo __('Paper size used when printing tickets to PDF');?></div> + </td> <td> <select name="default_paper_size"> <option value="none" selected="selected">— <?php echo __('None');?> —</option> @@ -188,74 +197,95 @@ $info['id']=$staff->getId(); foreach(Export::$paper_sizes as $v) { echo sprintf('<option value="%s" %s>%s</option>', - $v,($info['default_paper_size']==$v)?'selected="selected"':'',__($v)); + $v,($staff->default_paper_size==$v)?'selected="selected"':'',__($v)); } ?> </select> - <em><?php echo __('Paper size used when printing tickets to PDF');?></em> - <span class="error"> <?php echo $errors['default_paper_size']; ?></span> + <div class="error"><?php echo $errors['default_paper_size']; ?></div> </td> </tr> - <tr> - <td><?php echo __('Show Assigned Tickets');?>:</td> - <td> - <input type="checkbox" name="show_assigned_tickets" <?php echo $info['show_assigned_tickets']?'checked="checked"':''; ?>> - <em><?php echo __('Show assigned tickets on open queue.');?></em> - <i class="help-tip icon-question-sign" href="#show_assigned_tickets"></i></em> - </td> + </tbody> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Localization'); ?> + </th> </tr> <tr> - <th colspan="2"> - <em><strong><?php echo __('Password');?></strong>: <?php echo __('To reset your password, provide your current password and a new password below.');?> <span class="error"> <?php echo $errors['passwd']; ?></span></em> - </th> - </tr> - <?php if (!isset($_SESSION['_staff']['reset-token'])) { ?> - <tr> - <td width="180"> - <?php echo __('Current Password');?>: - </td> + <td><?php echo __('Time Zone');?>:</td> <td> - <input type="password" size="18" name="cpasswd" value="<?php echo $info['cpasswd']; ?>"> - <span class="error"> <?php echo $errors['cpasswd']; ?></span> + <?php + $TZ_NAME = 'timezone'; + $TZ_TIMEZONE = $staff->timezone; + include STAFFINC_DIR.'templates/timezone.tmpl.php'; ?> + <div class="error"><?php echo $errors['timezone']; ?></div> </td> </tr> - <?php } ?> +<?php if ($cfg->getSecondaryLanguages()) { ?> <tr> - <td width="180"> - <?php echo __('New Password');?>: - </td> + <td><?php echo __('Preferred Language'); ?>:</td> <td> - <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>"> - <span class="error"> <?php echo $errors['passwd1']; ?></span> + <?php + $langs = Internationalization::getConfiguredSystemLanguages(); ?> + <select name="lang"> + <option value="">— <?php echo __('Use Browser Preference'); ?> —</option> +<?php foreach($langs as $l) { + $selected = ($staff->lang == $l['code']) ? 'selected="selected"' : ''; ?> + <option value="<?php echo $l['code']; ?>" <?php echo $selected; + ?>><?php echo Internationalization::getLanguageDescription($l['code']); ?></option> +<?php } ?> + </select> + <span class="error"> <?php echo $errors['lang']; ?></span> </td> </tr> +<?php } ?> +<?php if (extension_loaded('intl')) { ?> <tr> - <td width="180"> - <?php echo __('Confirm New Password');?>: - </td> + <td><?php echo __('Preferred Locale');?>:</td> <td> - <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>"> - <span class="error"> <?php echo $errors['passwd2']; ?></span> + <select name="locale"> + <option value=""><?php echo __('Use Language Preference'); ?></option> +<?php foreach (Internationalization::allLocales() as $code=>$name) { ?> + <option value="<?php echo $code; ?>" <?php + if ($code == $staff->locale) + echo 'selected="selected"'; + ?>><?php echo $name; ?></option> +<?php } ?> + </select> </td> </tr> - <tr> - <th colspan="2"> - <em><strong><?php echo __('Signature');?></strong>: <?php echo __('Optional signature used on outgoing emails.');?> - <span class="error"> <?php echo $errors['signature']; ?></span> <i class="help-tip icon-question-sign" href="#signature"></i></em> - </th> +<?php } ?> + </table> + </div> + + <!-- ==================== SIGNATURES ======================== --> + + <div id="signature" class="hidden"> + <table class="table two-column" width="100%"> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Signature'); ?> + <div><small><?php echo __( + "Optional signature used on outgoing emails.") + .' '. + __('Signature is made available as a choice, on ticket reply.'); ?> + </small></div> + </th> </tr> <tr> - <td colspan=2> + <td colspan="2"> <textarea class="richtext no-bar" name="signature" cols="21" - rows="5" style="width: 60%;"><?php echo $info['signature']; ?></textarea> - <br><em><?php echo __('Signature is made available as a choice, on ticket reply.');?></em> + rows="5" style="width: 60%;"><?php echo $staff->signature; ?></textarea> </td> </tr> - </tbody> -</table> -<p style="text-align:center;"> - <input type="submit" name="submit" value="<?php echo __('Save Changes');?>"> - <input type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> - <input type="button" name="cancel" value="<?php echo __('Cancel Changes');?>" onclick='window.location.href="index.php"'> -</p> + </tbody> + </table> + </div> + + <p style="text-align:center;"> + <input type="submit" name="submit" value="<?php echo __('Save Changes'); ?>"> + <input type="reset" name="reset" value="<?php echo __('Reset');?>"> + <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick="window.history.go(-1);"> + </p> </form> diff --git a/include/staff/role.inc.php b/include/staff/role.inc.php index 0e982e044bc04cd69ea92d9568f3ef51ef7ba68b..de2eecb2ba4583b396b2c8bd324f424d8baff2f9 100644 --- a/include/staff/role.inc.php +++ b/include/staff/role.inc.php @@ -80,13 +80,22 @@ $info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $in </thead> <tbody> <?php - $setting = $role ? $role->getPermissionInfo() : array(); - foreach (RolePermission::allPermissions() as $g => $perms) { ?> - <tr><th><?php + + // Eliminate groups without any department-specific permissions + $buckets = array(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k => $v) { + if ($v['primary']) + continue; + $buckets[$g][$k] = $v; + } + } + foreach ($buckets as $g => $perms) { ?> + <tr><th><?php echo Format::htmlchars(__($g)); ?></th></tr> - <?php - foreach($perms as $k => $v) { ?> +<?php + foreach ($perms as $k => $v) { ?> <tr> <td> <label> @@ -98,11 +107,6 @@ $info = Format::htmlchars(($errors && $_POST) ? array_merge($info, $_POST) : $in <?php echo Format::htmlchars(__($v['title'])); ?> — - <?php - if ($v['primary']) { ?> - <i class="icon-globe faded" title="<?php echo - __('This permission only applies to the staff primary role'); ?>"></i> -<?php } ?> <em><?php echo Format::htmlchars(__($v['desc'])); ?></em> </label> diff --git a/include/staff/roles.inc.php b/include/staff/roles.inc.php index 49ba6d87caa7d4e234e4359e720536b54f05fa69..c568616c6f44312796172f9349bd5437d77d13e2 100644 --- a/include/staff/roles.inc.php +++ b/include/staff/roles.inc.php @@ -1,11 +1,33 @@ -<div class="pull-left" style="width:700;padding-top:5px;"> - <h2><?php echo __('Roles'); ?></h2> +<form action="roles.php" method="POST" name="roles"> +<div class="sticky bar"> + <div class="content"> + <div class="pull-left"> + <h2><?php echo __('Roles'); ?></h2> + </div> + <div class="pull-right flush-right"> + <a href="roles.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php +echo __('Add New Role'); ?></a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="roles.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="roles.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="roles.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + </div> + <div class="clear"></div> + </div> </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(); @@ -13,9 +35,7 @@ $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(); ?> +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"> @@ -80,17 +100,6 @@ $showing=$pageNav->showing().' '._N('role', 'roles', $count); if ($count) //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> - -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo - __('Enable'); ?>"> - - <input class="button" type="submit" name="disable" value="<?php echo - __('Disable'); ?>"> - - <input class="button" type="submit" name="delete" value="<?php echo - __('Delete'); ?>"> -</p> </form> <div style="display:none;" class="dialog" id="confirm-action"> diff --git a/include/staff/settings-emails.inc.php b/include/staff/settings-emails.inc.php index 5e977a706f0d96c435562b6ebef21d68ef697671..06b631664215fd071d142ce2292616b19c2f9da0 100644 --- a/include/staff/settings-emails.inc.php +++ b/include/staff/settings-emails.inc.php @@ -172,7 +172,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) </tr> </tbody> </table> -<p style="padding-left:250px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes');?>"> <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> </p> diff --git a/include/staff/settings-kb.inc.php b/include/staff/settings-kb.inc.php index 941b08e30fed4e3ca89c6023ce472ae4a452288f..cd55261edc2837f7cfacb81b649429e1830c5fe4 100644 --- a/include/staff/settings-kb.inc.php +++ b/include/staff/settings-kb.inc.php @@ -39,7 +39,7 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config) </tr> </tbody> </table> -<p style="padding-left:210px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes'); ?>"> <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes'); ?>"> </p> diff --git a/include/staff/settings-pages.inc.php b/include/staff/settings-pages.inc.php index 4d9910b4e2c9e04cd5d41fc40ddde1961f0e1abf..586a7087eb79b06e01c3c55c3643587788956450 100644 --- a/include/staff/settings-pages.inc.php +++ b/include/staff/settings-pages.inc.php @@ -182,7 +182,7 @@ $pages = Page::getPages(); </tr> </tbody> </table> -<p style="padding-left:250px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit-button" value="<?php echo __('Save Changes'); ?>"> <input class="button" type="reset" name="reset" value="<?php diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index 3e09d725c19bd2715eb0265b4d59759905fb63dc..809998a5fc2cba056bb82de50d0df9f592b63c34 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -47,16 +47,17 @@ $gmtime = Misc::gmtime(); <tr> <td width="220" class="required"><?php echo __('Default Department');?>:</td> <td> - <select name="default_dept_id"> + <select name="default_dept_id" data-quick-add="department"> <option value="">— <?php echo __('Select Default Department');?> —</option> <?php 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> + <option value="<?php echo $id; ?>"<?php echo $selected; ?>><?php echo $name; ?></option> <?php } } ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <font class="error">* <?php echo $errors['default_dept_id']; ?></font> <i class="help-tip icon-question-sign" href="#default_department"></i> </td> @@ -353,7 +354,7 @@ $gmtime = Misc::gmtime(); </tr> </tbody> </table> -<p style="padding-left:250px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes');?>"> <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> </p> diff --git a/include/staff/settings-tasks.inc.php b/include/staff/settings-tasks.inc.php index 83b5ac06703c840391cd349ae017768e06185454..dff1b8d79bfd05738712e2569660bc18e885db82 100644 --- a/include/staff/settings-tasks.inc.php +++ b/include/staff/settings-tasks.inc.php @@ -286,7 +286,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) </table> </div> </div> -<p style="padding-left:250px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes');?>"> <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> </p> diff --git a/include/staff/settings-tickets.inc.php b/include/staff/settings-tickets.inc.php index e4f00e2dc69952a0e325f5e8687d65e07929b950..c02f437f76877cf66129641552a88957d7e658c1 100644 --- a/include/staff/settings-tickets.inc.php +++ b/include/staff/settings-tickets.inc.php @@ -230,7 +230,7 @@ if(!($maxfileuploads=ini_get('max_file_uploads'))) <?php include STAFFINC_DIR . 'settings-alerts.inc.php'; ?> </div> -<p style="padding-left:250px;"> +<p style="text-align:center;"> <input class="button" type="submit" name="submit" value="<?php echo __('Save Changes');?>"> <input class="button" type="reset" name="reset" value="<?php echo __('Reset Changes');?>"> </p> diff --git a/include/staff/slaplans.inc.php b/include/staff/slaplans.inc.php index 2f1b3904efcfefb2773e447f0e28701a14de7c45..2e018639d20cc8b5d9e0460afe370668bd3a25d8 100644 --- a/include/staff/slaplans.inc.php +++ b/include/staff/slaplans.inc.php @@ -39,13 +39,33 @@ $pageNav->setURL('slas.php', $qs); $showing = $pageNav->showing().' '._N('SLA plan', 'SLA plans', $count); $qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); ?> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<form action="slas.php" method="POST" name="slas"> + +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Service Level Agreements');?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="slas.php?a=add" class="Icon newsla"><?php echo __('Add New SLA Plan');?></a></b></div> + <a href="slas.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New SLA Plan');?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="slas.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="slas.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="slas.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> +</div> <div class="clear"></div> -<form action="slas.php" method="POST" name="slas"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -119,11 +139,7 @@ $qstr .= '&order='.($order=='DESC' ? 'ASC' : 'DESC'); if ($count): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>" > - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>" > -</p> + <?php endif; ?> diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index b18b7cd772b11206c46962cd0a2937f204163789..ed810332a24a3192e997585497e30fab2d1df5f0 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -2,359 +2,493 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access Denied'); $info = $qs = array(); -if($staff && $_REQUEST['a']!='add'){ + +if ($_REQUEST['a']=='add'){ + if (!$staff) + $staff = Staff::create(array( + 'isactive' => true, + )); + $title=__('Add New Agent'); + $action='create'; + $submit_text=__('Create'); +} +else { //Editing Department. - $title=__('Update Agent'); + $title=__('Manage Agent'); $action='update'; $submit_text=__('Save Changes'); - $passwd_text=__('To reset the password enter a new one below'); - $info=$staff->getInfo(); - $info['id']=$staff->getId(); - $info['teams'] = $staff->getTeams(); - $info['signature'] = Format::viewableImages($info['signature']); + $info['id'] = $staff->getId(); $qs += array('id' => $staff->getId()); -}else { - $title=__('Add New Agent'); - $action='create'; - $submit_text=__('Add Agent'); - $passwd_text=__('Temporary password required only for "Local" authentication'); - //Some defaults for new staff. - $info['change_passwd']=1; - $info['welcome_email']=1; - $info['isactive']=1; - $info['isvisible']=1; - $info['isadmin']=0; - $qs += array('a' => 'add'); } -$info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> + <form action="staff.php?<?php echo Http::build_query($qs); ?>" method="post" id="save" autocomplete="off"> - <?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 __('Agent Account');?></h2> - <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 __('User Information');?></strong></em> - </th> - </tr> - </thead> - <tbody> - <tr> - <td width="180" class="required"> - <?php echo __('Username');?>: - </td> - <td> - <input type="text" size="30" class="staff-username typeahead" - autofocus name="username" value="<?php echo $info['username']; ?>"> - <span class="error">* <?php echo $errors['username']; ?></span> <i class="help-tip icon-question-sign" href="#username"></i> - </td> - </tr> + <?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 $title; ?> + <div> + <small><?php echo $staff->getName(); ?></small> + </div> + </h2> + + <ul class="clean tabs"> + <li class="active"><a href="#account"><i class="icon-user"></i> <?php echo __('Account'); ?></a></li> + <li><a href="#access"><?php echo __('Access'); ?></a></li> + <li><a href="#permissions"><?php echo __('Permisions'); ?></a></li> + <li><a href="#teams"><?php echo __('Teams'); ?></a></li> + </ul> + + <div class="tab_content" id="account"> + <table class="table two-column" width="940" border="0" cellspacing="0" cellpadding="2"> + <tbody> <tr> - <td width="180" class="required"> - <?php echo __('First Name');?>: - </td> - <td> - <input type="text" size="30" name="firstname" class="auto first" - value="<?php echo $info['firstname']; ?>"> - <span class="error">* <?php echo $errors['firstname']; ?></span> - </td> + <td class="required"><?php echo __('Name'); ?>:</td> + <td> + <input type="text" size="20" maxlength="64" style="width: 145px" name="firstname" + autofocus value="<?php echo Format::htmlchars($staff->firstname); ?>" + placeholder="<?php echo __("First Name"); ?>" /> + <input type="text" size="20" maxlength="64" style="width: 145px" name="lastname" + value="<?php echo Format::htmlchars($staff->lastname); ?>" + placeholder="<?php echo __("Last Name"); ?>" /> + <div class="error"><?php echo $errors['firstname']; ?></div> + <div class="error"><?php echo $errors['lastname']; ?></div> + </td> </tr> <tr> - <td width="180" class="required"> - <?php echo __('Last Name');?>: - </td> - <td> - <input type="text" size="30" name="lastname" class="auto last" - value="<?php echo $info['lastname']; ?>"> - <span class="error">* <?php echo $errors['lastname']; ?></span> - </td> + <td class="required"><?php echo __('Email Address'); ?>:</td> + <td> + <input type="email" size="40" maxlength="64" style="width: 300px" name="email" + value="<?php echo Format::htmlchars($staff->email); ?>" + placeholder="<?php echo __('e.g. me@mycompany.com'); ?>" /> + <div class="error"><?php echo $errors['email']; ?></div> + </td> </tr> <tr> - <td width="180" class="required"> - <?php echo __('Email Address');?>: - </td> - <td> - <input type="text" size="30" name="email" class="auto email" - value="<?php echo $info['email']; ?>"> - <span class="error">* <?php echo $errors['email']; ?></span> <i class="help-tip icon-question-sign" href="#email_address"></i> - </td> + <td><?php echo __('Phone Number');?>:</td> + <td> + <input type="tel" size="18" name="phone" class="auto phone" + value="<?php echo Format::htmlchars($staff->phone); ?>" /> + <?php echo __('Ext');?> + <input type="text" size="5" name="phone_ext" + value="<?php echo Format::htmlchars($staff->phone_ext); ?>"> + <div class="error"><?php echo $errors['phone']; ?></div> + <div class="error"><?php echo $errors['phone_ext']; ?></div> + </td> </tr> <tr> - <td width="180"> - <?php echo __('Phone Number');?>: - </td> - <td> - <input type="text" size="18" name="phone" class="auto phone" - value="<?php echo $info['phone']; ?>"> - <span class="error"> <?php echo $errors['phone']; ?></span> - <?php echo __('Ext');?> <input type="text" size="5" name="phone_ext" value="<?php echo $info['phone_ext']; ?>"> - <span class="error"> <?php echo $errors['phone_ext']; ?></span> - </td> + <td><?php echo __('Mobile Number');?>:</td> + <td> + <input type="tel" size="18" name="mobile" class="auto phone" + value="<?php echo Format::htmlchars($staff->mobile); ?>" /> + <div class="error"><?php echo $errors['mobile']; ?></div> + </td> </tr> - <tr> - <td width="180"> - <?php echo __('Mobile Number');?>: - </td> - <td> - <input type="text" size="18" name="mobile" class="auto mobile" - value="<?php echo $info['mobile']; ?>"> - <span class="error"> <?php echo $errors['mobile']; ?></span> - </td> + </tbody> + <!-- ================================================ --> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Authentication'); ?> + </th> </tr> -<?php if (!$staff) { ?> <tr> - <td width="180"><?php echo __('Welcome Email'); ?></td> - <td><input type="checkbox" name="welcome_email" id="welcome-email" <?php - if ($info['welcome_email']) echo 'checked="checked"'; - ?> onchange="javascript: - var sbk = $('#backend-selection'); - if ($(this).is(':checked')) - $('#password-fields').hide(); - else if (sbk.val() == '' || sbk.val() == 'local') - $('#password-fields').show(); - " /> - <?php echo __('Send sign in information'); ?> - <i class="help-tip icon-question-sign" href="#welcome_email"></i> - </td> - </tr> + <td class="required"><?php echo __('Username'); ?>: + <span class="error">*</span></td> + <td> + <input type="text" size="40" style="width:300px" + class="staff-username typeahead" + name="username" value="<?php echo Format::htmlchars($staff->username); ?>" /> +<?php if (!($bk = $staff->getAuthBackend()) || $bk->supportsPasswordChange()) { ?> + <button type="button" class="action-button" onclick="javascript: + $.dialog('ajax.php/staff/'+<?php echo $info['id'] ?: '0'; ?>+'/set-password', 201);"> + <i class="icon-refresh"></i> <?php echo __('Set Password'); ?> + </button> <?php } ?> - <tr> - <th colspan="2"> - <em><strong><?php echo __('Authentication'); ?></strong>: <?php echo $passwd_text; ?> <span class="error"> <?php echo $errors['temppasswd']; ?></span> <i class="help-tip icon-question-sign" href="#account_password"></i></em> - </th> + <i class="offset help-tip icon-question-sign" href="#username"></i> + <div class="error"><?php echo $errors['username']; ?></div> + </td> </tr> +<?php +$bks = array(); +foreach (StaffAuthenticationBackend::allRegistered() as $ab) { + if (!$ab->supportsInteractiveAuthentication()) continue; + $bks[] = $ab; +} +if (count($bks) > 1) { +?> <tr> - <td><?php echo __('Authentication Backend'); ?></td> - <td> - <select name="backend" id="backend-selection" onchange="javascript: + <td><?php echo __('Authentication Backend'); ?>:</td> + <td> + <select name="backend" id="backend-selection" + style="width:300px" onchange="javascript: if (this.value != '' && this.value != 'local') $('#password-fields').hide(); else if (!$('#welcome-email').is(':checked')) $('#password-fields').show(); "> - <option value="">— <?php echo __('Use any available backend'); ?> —</option> - <?php foreach (StaffAuthenticationBackend::allRegistered() as $ab) { - if (!$ab->supportsInteractiveAuthentication()) continue; ?> - <option value="<?php echo $ab::$id; ?>" <?php - if ($info['backend'] == $ab::$id) - echo 'selected="selected"'; ?>><?php - echo $ab->getName(); ?></option> - <?php } ?> + <option value="">— <?php echo __('Use any available backend'); ?> —</option> +<?php foreach ($bks as $ab) { ?> + <option value="<?php echo $ab::$id; ?>" <?php + if ($staff->backend == $ab::$id) + echo 'selected="selected"'; ?>><?php + echo $ab->getName(); ?></option> +<?php } ?> </select> - </td> + </td> </tr> - </tbody> - <tbody id="password-fields" style="<?php - if ($info['welcome_email'] || ($info['backend'] && $info['backend'] != 'local')) - echo 'display:none;'; ?>"> - <tr> - <td width="180"> - <?php echo __('Password');?>: - </td> - <td> - <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>"> - <span class="error"> <?php echo $errors['passwd1']; ?></span> - </td> +<?php +} ?> + </tbody> + <!-- ================================================ --> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Status and Settings'); ?> + </th> </tr> <tr> - <td width="180"> - <?php echo __('Confirm Password');?>: - </td> - <td> - <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>"> - <span class="error"> <?php echo $errors['passwd2']; ?></span> - </td> + <td colspan="2"> + <div class="error"><?php echo $errors['isadmin']; ?></div> + <div class="error"><?php echo $errors['isactive']; ?></div> + <label> + <input type="checkbox" name="islocked" value="1" + <?php echo (!$staff->isactive) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Locked'); ?> + </label> + <br/> + <label> + <input type="checkbox" name="isadmin" value="1" + <?php echo ($staff->isadmin) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Administrator'); ?> + </label> + <br/> + <label> + <input type="checkbox" name="assigned_only" + <?php echo ($staff->assigned_only) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Limit ticket access to ONLY assigned tickets'); ?> + </label> + <br/> + <label> + <input type="checkbox" name="onvacation" + <?php echo ($staff->onvacation) ? 'checked="checked"' : ''; ?> /> + <?php echo __('Vacation Mode'); ?> + </label> + <br/> </tr> + </tbody> + </table> - <tr> - <td width="180"> - <?php echo __('Forced Password Change');?>: - </td> - <td> - <input type="checkbox" name="change_passwd" value="0" <?php echo $info['change_passwd']?'checked="checked"':''; ?>> - <?php echo __('<strong>Force</strong> password change on next login.');?> - <i class="help-tip icon-question-sign" href="#forced_password_change"></i> - </td> - </tr> - </tbody> - <tbody> - <tr> - <th colspan="2"> - <em><strong><?php echo __("Agent's Signature");?></strong>: - <?php echo __('Optional signature used on outgoing emails.');?> - <span class="error"> <?php echo $errors['signature']; ?></span></em> - <i class="help-tip icon-question-sign" href="#agents_signature"></i></em> - </th> - </tr> - <tr> - <td colspan=2> - <textarea class="richtext no-bar" name="signature" cols="21" - rows="5" style="width: 60%;"><?php echo $info['signature']; ?></textarea> - <br><em><?php echo __('Signature is made available as a choice, on ticket reply.');?></em> - </td> - </tr> - <tr> - <th colspan="2"> - <em><strong><?php echo __('Account Status & Settings');?></strong>: <?php echo __('Department and group assigned control access permissions.');?></em> - </th> - </tr> - <tr> - <td width="180" class="required"> - <?php echo __('Account Type');?>: - </td> - <td> - <input type="radio" name="isadmin" value="1" <?php echo $info['isadmin']?'checked="checked"':''; ?>> - <font color="red"><strong><?php echo __('Admin');?></strong></font> - <input type="radio" name="isadmin" value="0" <?php echo !$info['isadmin']?'checked="checked"':''; ?>><strong><?php echo __('Agent');?></strong> - <span class="error"> <?php echo $errors['isadmin']; ?></span> - </td> - </tr> - <tr> - <td width="180" class="required"> - <?php echo __('Account 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="0" <?php echo !$info['isactive']?'checked="checked"':''; ?>><strong><?php echo __('Locked');?></strong> - <span class="error"> <?php echo $errors['isactive']; ?></span> <i class="help-tip icon-question-sign" href="#account_status"></i> - </td> - </tr> - <tr> - <td width="180" class="required"> - <?php echo __('Primary Department');?>: - </td> - <td> - <select name="dept_id" id="dept_id"> - <option value="0">— <?php echo __('Select Department');?> —</option> - <?php - foreach (Dept::getDepartments() as $id=>$name) { - $sel=($info['dept_id']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); - } - ?> - </select> - <span class="error">*</span> - <i class="help-tip icon-question-sign" href="#primary_department"></i> - <div class="error"><?php echo $errors['dept_id']; ?></div> - </td> - </tr> - <tr> - <td width="180" class="required"> - <?php echo __('Primary Role');?>: - </td> - <td> - <select name="role_id"> - <option value="0">— <?php echo __('Select Role');?> —</option> - <?php - foreach (Role::getRoles() as $id=>$name) { - $sel=($info['role_id']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); - } - ?> - </select> - <span class="error">*</span> - <i class="help-tip icon-question-sign" href="#primary_role"></i> - <div class="error"><?php echo $errors['role_id']; ?></div> - </td> + <div style="padding:8px 3px; margin-top: 1.6em"> + <strong class="big"><?php echo __('Internal Notes');?></strong><br/> + <?php echo __("be liberal, they're internal.");?> + </div> + + <textarea name="notes" class="richtext"> + <?php echo Format::viewableImages($staff->notes); ?> + </textarea> + </div> + + <!-- ============== DEPARTMENT ACCESS =================== --> + + <div class="hidden tab_content" id="access"> + <table class="table two-column" width="940" border="0" cellspacing="0" cellpadding="2"> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Primary Department and Role'); ?> + <span class="error">*</span> + <div><small><?php echo __( + "Select the departments the agent is allowed to access and optionally select an effective role." + ); ?> + </small></div> + </th> </tr> <tr> - <td width="180" class="required"> - <?php echo __('Assigned Group');?>: - </td> - <td> - <select name="group_id" id="group_id"> - <option value="0">— <?php echo __('Select Group');?> —</option> - <?php - foreach (Group::getGroups() as $id=>$name) { - $sel=($info['group_id']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>', - $id, $sel, $name); - } - ?> - </select> - <span class="error">* <?php echo $errors['group_id']; ?></span> <i class="help-tip icon-question-sign" href="#assigned_group"></i> - </td> + <td> + <select name="dept_id" id="dept_id" data-quick-add="department"> + <option value="0">— <?php echo __('Select Department');?> —</option> + <?php + foreach (Dept::getDepartments() as $id=>$name) { + $sel=($staff->dept_id==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <i class="offset help-tip icon-question-sign" href="#primary_department"></i> + <div class="error"><?php echo $errors['dept_id']; ?></div> + <div class="error"><?php echo $errors['role_id']; ?></div> + </td> + <td> + <select name="role_id" data-quick-add="role"> + <option value="0">— <?php echo __('Select Role');?> —</option> + <?php + foreach (Role::getRoles() as $id=>$name) { + $sel=($staff->role_id==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <i class="offset help-tip icon-question-sign" href="#primary_role"></i> + </td> </tr> - <tr> - <td width="180"> - <?php echo __('Time Zone');?>: - </td> - <td> - <?php - $TZ_NAME = 'timezone'; - $TZ_TIMEZONE = $info['timezone']; - include STAFFINC_DIR.'templates/timezone.tmpl.php'; ?> - <div class="error"><?php echo $errors['timezone']; ?></div> - </td> + </tbody> + <tbody> + <tr id="extended_access_template" class="hidden"> + <td> + <input type="hidden" data-name="dept_access[]" value="" /> + </td> + <td> + <select data-name="dept_access_role" data-quick-add="role"> + <option value="0">— <?php echo __('Select Role');?> —</option> + <?php + foreach (Role::getRoles() as $id=>$name) { + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <span style="display:inline-block;width:20px"> </span> + <label> + <input type="checkbox" data-name="dept_access_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> + <a href="#" class="pull-right drop-access" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> </tr> - <tr> - <td width="180"> - <?php echo __('Limited Access');?>: - </td> - <td> - <input type="checkbox" name="assigned_only" value="1" <?php echo $info['assigned_only']?'checked="checked"':''; ?>><?php echo __('Limit ticket access to ONLY assigned tickets.');?> - <i class="help-tip icon-question-sign" href="#limited_access"></i> - </td> + </tbody> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Extended Access'); ?> + </th> </tr> - <tr> - <td width="180"> - <?php echo __('Directory Listing');?>: - </td> - <td> - <input type="checkbox" name="isvisible" value="1" <?php echo $info['isvisible']?'checked="checked"':''; ?>> <?php - echo __('Make visible in the Agent Directory'); ?> - <i class="help-tip icon-question-sign" href="#directory_listing"></i> - </td> +<?php +$depts = Dept::getDepartments(); +foreach ($staff->dept_access as $dept_access) { + unset($depts[$dept_access->dept_id]); +} +?> + <tr id="add_extended_access"> + <td colspan="2"> + <i class="icon-plus-sign"></i> + <select id="add_access" data-quick-add="department"> + <option value="0">— <?php echo __('Select Department');?> —</option> + <?php + foreach ($depts as $id=>$name) { + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="green button"> + <?php echo __('Add'); ?> + </button> + </td> </tr> + </tbody> + </table> + </div> + + <!-- ================= PERMISSIONS ====================== --> + + <div id="permissions" class="hidden"> +<?php + $permissions = array(); + foreach (RolePermission::allPermissions() as $g => $perms) { + foreach ($perms as $k=>$P) { + if (!$P['primary']) + continue; + if (!isset($permissions[$g])) + $permissions[$g] = array(); + $permissions[$g][$k] = $P; + } + } +?> + <ul class="alt tabs"> +<?php + $first = true; + foreach ($permissions as $g => $perms) { ?> + <li <?php if ($first) { echo 'class="active"'; $first=false; } ?>> + <a href="#<?php echo Format::slugify($g); ?>"><?php echo Format::htmlchars(__($g));?></a> + </li> +<?php } ?> + </ul> +<?php + $first = true; + foreach ($permissions as $g => $perms) { ?> + <div class="tab_content <?php if (!$first) { echo 'hidden'; } else { $first = false; } + ?>" id="<?php echo Format::slugify($g); ?>"> + <table class="table"> +<?php foreach ($perms as $k => $v) { ?> <tr> - <td width="180"> - <?php echo __('Vacation Mode');?>: - </td> - <td> - <input type="checkbox" name="onvacation" value="1" <?php echo $info['onvacation']?'checked="checked"':''; ?>> - <?php echo __('Change Status to Vacation Mode'); ?> - <i class="help-tip icon-question-sign" href="#vacation_mode"></i> - </td> + <td> + <label> + <?php + echo sprintf('<input type="checkbox" name="perms[]" value="%s" %s />', + $k, ($staff->hasPerm($k)) ? 'checked="checked"' : ''); + ?> + + <?php echo Format::htmlchars(__($v['title'])); ?> + — + <em><?php echo Format::htmlchars(__($v['desc'])); ?></em> + </label> + </td> </tr> - <?php - // List team assignments. - $teams = Team::getTeams(); - if (count($teams)) { ?> - <tr> - <th colspan="2"> - <em><strong><?php echo __('Assigned Teams');?></strong>: <?php echo __("Agent will have access to tickets assigned to a team they belong to regardless of the ticket's department.");?> </em> - </th> +<?php } ?> + </table> + </div> +<?php } ?> + </div> + + <!-- ============== TEAM MEMBERSHIP =================== --> + + <div class="hidden tab_content" id="teams"> + <table class="table two-column" width="100%"> + <tbody> + <tr class="header"> + <th colspan="2"> + <?php echo __('Assigned Teams'); ?> + <div><small><?php echo __( + "Agent will have access to tickets assigned to a team they belong to regardless of the ticket's department. Alerts can be enabled for each associated team." + ); ?> + </small></div> + </th> </tr> - <?php - foreach ($teams as $id=>$name) { - $checked=($info['teams'] && in_array($id,$info['teams'])) - ? 'checked="checked"' : ''; - echo sprintf('<tr><td colspan=2><input type="checkbox" name="teams[]" value="%d" %s> %s</td></tr>', - $id,$checked,$name); - } - } ?> - <tr> - <th colspan="2"> - <em><strong><?php echo __('Internal Notes'); ?></strong></em> - </th> +<?php +$teams = Team::getTeams(); +foreach ($staff->teams as $TM) { + unset($teams[$TM->team_id]); +} +?> + <tr id="join_team"> + <td colspan="2"> + <i class="icon-plus-sign"></i> + <select id="add_team" data-quick-add="team"> + <option value="0">— <?php echo __('Select Team');?> —</option> + <?php + foreach ($teams as $id=>$name) { + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="green button"> + <?php echo __('Add'); ?> + </button> + </td> </tr> - <tr> - <td colspan=2> - <textarea class="richtext no-bar" name="notes" cols="28" - rows="7" style="width: 80%;"><?php echo $info['notes']; ?></textarea> - </td> + </tbody> + <tbody> + <tr id="team_member_template" class="hidden"> + <td> + <input type="hidden" data-name="teams[]" value="" /> + </td> + <td> + <label> + <input type="checkbox" data-name="team_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> + <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> </tr> - </tbody> -</table> -<p style="padding-left:250px;"> - <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="staff.php"'> -</p> + </tbody> + </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.history.go(-1);"> + </p> </form> + +<script type="text/javascript"> +var addAccess = function(daid, name, role, alerts, error) { + if (!daid) return; + var copy = $('#extended_access_template').clone(); + + copy.find('[data-name=dept_access\\[\\]]') + .attr('name', 'dept_access[]') + .val(daid); + copy.find('[data-name^=dept_access_role]') + .attr('name', 'dept_access_role['+daid+']') + .val(role || 0); + copy.find('[data-name^=dept_access_alerts]') + .attr('name', 'dept_access_alerts['+daid+']') + .prop('checked', alerts); + copy.find('td:first').append(document.createTextNode(name)); + copy.attr('id', '').show().insertBefore($('#add_extended_access')); + copy.removeClass('hidden') + if (error) + $('<div class="error">').text(error).appendTo(copy.find('td:last')); +}; + +$('#add_extended_access').find('button').on('click', function() { + var selected = $('#add_access').find(':selected'); + addAccess(selected.val(), selected.text(), 0, true); + selected.remove(); + return false; +}); + +$(document).on('click', 'a.drop-access', function() { + var tr = $(this).closest('tr'); + $('#add_access').append( + $('<option>') + .attr('value', tr.find('input[name^=dept_access][type=hidden]').val()) + .text(tr.find('td:first').text()) + ); + tr.fadeOut(function() { $(this).remove(); }); + return false; +}); + +var joinTeam = function(teamid, name, alerts, error) { + if (!teamid) return; + var copy = $('#team_member_template').clone(); + + copy.find('[data-name=teams\\[\\]]') + .attr('name', 'teams[]') + .val(teamid); + copy.find('[data-name^=team_alerts]') + .attr('name', 'team_alerts['+teamid+']') + .prop('checked', alerts); + copy.find('td:first').append(document.createTextNode(name)); + copy.attr('id', '').show().insertBefore($('#join_team')); + copy.removeClass('hidden'); + if (error) + $('<div class="error">').text(error).appendTo(copy.find('td:last')); +}; + +$('#join_team').find('button').on('click', function() { + var selected = $('#add_team').find(':selected'); + joinTeam(selected.val(), selected.text(), true); + selected.remove(); + return false; +}); + +<?php +foreach ($staff->dept_access as $dept_access) { + echo sprintf('addAccess(%d, %s, %d, %d, %s);', $dept_access->dept_id, + JsonDataEncoder::encode($dept_access->dept->getName()), + $dept_access->role_id, + $dept_access->isAlertsEnabled(), + JsonDataEncoder::encode(@$errors['dept_access'][$dept_access->dept_id]) + ); +} + +foreach ($staff->teams as $member) { + echo sprintf('joinTeam(%d, %s, %d, %s);', $member->team_id, + JsonDataEncoder::encode($member->team->getName()), + $member->isAlertsEnabled(), + JsonDataEncoder::encode(@$errors['teams'][$member->team_id]) + ); +} + +?> +</script> diff --git a/include/staff/staffmembers.inc.php b/include/staff/staffmembers.inc.php index d57258241a521e135a479b44fb23119804c92a37..68fca554cfdb1b4a8adf2471d50c53a436ca5128 100644 --- a/include/staff/staffmembers.inc.php +++ b/include/staff/staffmembers.inc.php @@ -8,7 +8,6 @@ $sortOptions = array( 'name' => array('firstname', 'lastname'), 'username' => 'username', 'status' => 'isactive', - 'group' => 'group__name', 'dept' => 'dept__name', 'created' => 'created', 'login' => 'lastlogin' @@ -48,11 +47,6 @@ if ($_REQUEST['did'] && is_numeric($_REQUEST['did'])) { $qs += array('did' => $_REQUEST['did']); } -if ($_REQUEST['gid'] && is_numeric($_REQUEST['gid'])) { - $filters += array('group_id' => $_REQUEST['gid']); - $qs += array('gid' => $_REQUEST['gid']); -} - if ($_REQUEST['tid'] && is_numeric($_REQUEST['tid'])) { $filters += array('teams__team_id' => $_REQUEST['tid']); $qs += array('tid' => $_REQUEST['tid']); @@ -86,51 +80,77 @@ $qstr .= '&order='.($order=='-' ? 'ASC' : 'DESC'); // 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">— <?php echo __('All Department');?> —</option> - <?php - if (($depts=Dept::getDepartments())) { - foreach ($depts as $id => $name) { - $sel=($_REQUEST['did'] && $_REQUEST['did']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); - } - } - ?> - </select> - <select name="gid" id="gid"> - <option value="0">— <?php echo __('All Groups');?> —</option> - <?php - if (($groups=Group::getGroups())) { - foreach ($groups as $id => $name) { - $sel=($_REQUEST['gid'] && $_REQUEST['gid']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); - } - } - ?> - </select> - <select name="tid" id="tid"> - <option value="0">— <?php echo __('All Teams');?> —</option> - <?php - if (($teams=Team::getTeams())) { - foreach ($teams as $id => $name) { - $sel=($_REQUEST['tid'] && $_REQUEST['tid']==$id)?'selected="selected"':''; - echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); - } - } - ?> - </select> - - <input type="submit" name="submit" value="<?php echo __('Apply');?>"/> - </form> - </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> -<form action="staff.php" method="POST" name="staff" > +<div class="sticky bar"> + <div class="content"> + <h2 class="inline"><?php echo __('Agents');?></h2> + <div class="pull-left inline"> + <form action="staff.php" method="GET" name="filter"> + <input type="hidden" name="a" value="filter" > + <select name="did" id="did"> + <option value="0">— <?php echo __('All Department');?> —</option> + <?php + if (($depts=Dept::getDepartments())) { + foreach ($depts as $id => $name) { + $sel=($_REQUEST['did'] && $_REQUEST['did']==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + } + ?> + </select> + <select name="tid" id="tid"> + <option value="0">— <?php echo __('All Teams');?> —</option> + <?php + if (($teams=Team::getTeams())) { + foreach ($teams as $id => $name) { + $sel=($_REQUEST['tid'] && $_REQUEST['tid']==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name); + } + } + ?> + </select> + <input type="submit" name="submit" class="small button" value="<?php echo __('Apply');?>"/> + </form> + </div> + <div class="pull-right"> + <a class="green button action-button" href="staff.php?a=add"> + <i class="icon-plus-sign"></i> + <?php echo __('Add New Agent'); ?> + </a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-form-id="mass-actions" data-name="enable" href="staff.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-form-id="mass-actions" data-name="disable" href="staff.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li><a class="dialog-first" data-action="permissions" href="#staff/reset-permissions"> + <i class="icon-sitemap icon-fixed-width"></i> + <?php echo __('Reset Permissions'); ?></a></li> + <li><a class="dialog-first" data-action="department" href="#staff/change-department"> + <i class="icon-truck icon-fixed-width"></i> + <?php echo __('Change Department'); ?></a></li> + <!-- TODO: Implement "Reset Access" mass action + <li><a class="dialog-first" href="#staff/reset-access"> + <i class="icon-puzzle-piece icon-fixed-width"></i> + <?php echo __('Reset Access'); ?></a></li> + --> + <li class="danger"><a class="confirm" data-form-id="mass-actions" data-name="delete" href="staff.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + </div> + <div class="clear" style="padding: 3px 0"></div> + </div> +</div> + +<form id="mass-actions" action="staff.php" method="POST" name="staff" > + <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -142,7 +162,6 @@ $agents->limit($pageNav->getLimit())->offset($pageNav->getStart()); <th width="200"><a <?php echo $name_sort; ?> href="staff.php?<?php echo $qstr; ?>&sort=name"><?php echo __('Name');?></a></th> <th width="100"><a <?php echo $username_sort; ?> href="staff.php?<?php echo $qstr; ?>&sort=username"><?php echo __('Username');?></a></th> <th width="100"><a <?php echo $status_sort; ?> href="staff.php?<?php echo $qstr; ?>&sort=status"><?php echo __('Status');?></a></th> - <th width="120"><a <?php echo $group_sort; ?>href="staff.php?<?php echo $qstr; ?>&sort=group"><?php echo __('Group');?></a></th> <th width="150"><a <?php echo $dept_sort; ?>href="staff.php?<?php echo $qstr; ?>&sort=dept"><?php echo __('Department');?></a></th> <th width="100"><a <?php echo $created_sort; ?> href="staff.php?<?php echo $qstr; ?>&sort=created"><?php echo __('Created');?></a></th> <th width="145"><a <?php echo $login_sort; ?> href="staff.php?<?php echo $qstr; ?>&sort=login"><?php echo __('Last Login');?></a></th> @@ -163,17 +182,16 @@ $agents->limit($pageNav->getLimit())->offset($pageNav->getStart()); <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((string) $agent->getName()); ?></a> </td> + Format::htmlchars((string) $agent->getName()); ?></a></td> <td><?php echo $agent->getUserName(); ?></td> - <td><?php echo $agent->isActive() ? __('Active') :'<b>'.__('Locked').'</b>'; ?> <?php + <td><?php echo $agent->isActive() ? __('Active') :'<b>'.__('Locked').'</b>'; ?><?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($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); ?> </td> + <td><?php echo Format::relativeTime(Misc::db2gmtime($agent->lastlogin)) ?: '<em class="faded">'.__('never').'</em>'; ?></td> </tr> <?php } //end of foreach @@ -194,18 +212,9 @@ $agents->limit($pageNav->getLimit())->offset($pageNav->getStart()); </tfoot> </table> <?php -if ($count): //Show options.. +if ($count) { //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; -?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - - <input class="button" type="submit" name="disable" value="<?php echo __('Lock');?>" > - - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>"> -</p> -<?php -endif; +} ?> </form> @@ -240,3 +249,33 @@ endif; <div class="clear"></div> </div> +<script type="text/javascript"> +$(document).on('click', 'a.dialog-first', function(e) { + e.preventDefault(); + var action = $(this).data('action'), + $form = $('form#mass-actions'); + if ($(':checkbox.ckb:checked', $form).length == 0) { + $.sysAlert(__('Oops'), + __('You need to select at least one item')); + return false; + } + ids = $form.find('.ckb'); + $.dialog('ajax.php/' + $(this).attr('href').substr(1), 201, function (xhr, data) { + $form.find('#action').val(action); + data = JSON.parse(data); + if (data) + $.each(data, function(k, v) { + if (v.length) { + $.each(v, function() { + $form.append($('<input type="hidden">').attr('name', k+'[]').val(this)); + }) + } + else { + $form.append($('<input type="hidden">').attr('name', k).val(v)); + } + }); + $form.submit(); + }, { data: ids.serialize()}); + return false; +}); +</script> diff --git a/include/staff/system.inc.php b/include/staff/system.inc.php index 3ea8767350538cd2493efdee571f93c7efee4cbb..42a302dcfca1bf3a707f599a920a00062fd99af5 100644 --- a/include/staff/system.inc.php +++ b/include/staff/system.inc.php @@ -104,7 +104,7 @@ $extensions = array( </thead> <tbody> <tr><td><?php echo __('Schema'); ?></td> - <td><?php echo sprintf('<span class="ltr">%s (%s)</span>', DBNAME, DBHOST); ?> </td> + <td><?php echo sprintf('<span class="ltr">%s (%s)</span>', DBNAME, DBHOST); ?> </td></tr> </tr> <tr><td><?php echo __('Schema Signature'); ?></td> <td><?php echo $cfg->getSchemaSignature(); ?> </td> @@ -120,7 +120,13 @@ $extensions = array( <td><?php $sql = 'SELECT SUM(LENGTH(filedata)) / 1048576 FROM '.FILE_CHUNK_TABLE; $space = db_result(db_query($sql)); - echo sprintf('%.2f MiB', $space); ?></td> + echo sprintf('%.2f MiB', $space); ?></td></tr> + <tr><td><?php echo __('Timezone'); ?></td> + <td><?php echo $dbtz = db_timezone(); ?> + <?php if ($cfg->getDbTimezone() != $dbtz) { ?> + (<?php echo sprintf(__('Interpreted as %s'), $cfg->getDbTimezone()); ?>) + <?php } ?> + </td></tr> </tbody> </table> <br/> diff --git a/include/staff/team.inc.php b/include/staff/team.inc.php index 93719667251ffef9ff2ac99ea041907eb62a1add..92ad9f13dfb017b363d7ced525c53248ee80422e 100644 --- a/include/staff/team.inc.php +++ b/include/staff/team.inc.php @@ -6,8 +6,6 @@ if ($team && $_REQUEST['a']!='add') { $title=__('Update Team'); $action='update'; $submit_text=__('Save Changes'); - $info=$team->getInfo(); - $info['id']=$team->getId(); $trans['name'] = $team->getTranslateTag('name'); $members = $team->getMembers(); $qs += array('id' => $team->getId()); @@ -15,18 +13,21 @@ if ($team && $_REQUEST['a']!='add') { $title=__('Add New Team'); $action='create'; $submit_text=__('Create Team'); - $info['isenabled']=1; - $info['noalerts']=0; + if (!$team) { + $team = Team::create(array( + 'flags' => Team::FLAG_ENABLED, + )); + } $qs += array('a' => $_REQUEST['a']); } -$info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); +$info = $team->getInfo(); ?> <form action="teams.php?<?php echo Http::build_query($qs); ?>" 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']; ?>"> + <input type="hidden" name="id" value="<?php echo $team->getId(); ?>"> <h2><?php echo __('Team');?> <i class="help-tip icon-question-sign" href="#teams"></i> </h2> @@ -34,13 +35,10 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <ul class="clean 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> @@ -57,7 +55,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <?php echo __('Name');?>: </td> <td> - <input type="text" size="30" name="name" value="<?php echo $info['name']; ?>" + <input type="text" size="30" name="name" value="<?php echo Format::htmlchars($team->name); ?>" autofocus data-translate-tag="<?php echo $trans['name']; ?>"/> <span class="error">* <?php echo $errors['name']; ?></span> </td> @@ -68,9 +66,9 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); </td> <td> <span> - <input type="radio" name="isenabled" value="1" <?php echo $info['isenabled']?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong> + <input type="radio" name="isenabled" value="1" <?php echo $team->isEnabled()?'checked="checked"':''; ?>><strong><?php echo __('Active');?></strong> - <input type="radio" name="isenabled" value="0" <?php echo !$info['isenabled']?'checked="checked"':''; ?>><?php echo __('Disabled');?> + <input type="radio" name="isenabled" value="0" <?php echo !$team->isEnabled()?'checked="checked"':''; ?>><?php echo __('Disabled');?> <span class="error">* </span> <i class="help-tip icon-question-sign" href="#status"></i> </span> @@ -82,16 +80,9 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); </td> <td> <span> - <select name="lead_id"> + <select id="team-lead-select" name="lead_id" data-quick-add="staff"> <option value="0">— <?php echo __('None');?> —</option> - <?php - 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()); - } - } - ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> </select> <span class="error"><?php echo $errors['lead_id']; ?></span> <i class="help-tip icon-question-sign" href="#lead"></i> @@ -103,7 +94,7 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <?php echo __('Assignment Alert');?>: </td> <td> - <input type="checkbox" name="noalerts" value="1" <?php echo $info['noalerts']?'checked="checked"':''; ?> > + <input type="checkbox" name="noalerts" value="1" <?php echo !$team->alertsEnabled()?'checked="checked"':''; ?> > <?php echo __('<strong>Disable</strong> for this Team'); ?> <i class="help-tip icon-question-sign" href="#assignment_alert"></i> </td> @@ -116,44 +107,129 @@ $info = Format::htmlchars(($errors && $_POST) ? $_POST : $info); <tr> <td colspan=2> <textarea class="richtext no-bar" name="notes" cols="21" - rows="8" style="width: 80%;"><?php echo $info['notes']; ?></textarea> + rows="8" style="width: 80%;"><?php echo Format::htmlchars($team->notes); ?></textarea> </td> </tr> </tbody> </table> </div> + <?php -if ($members) { ?> +$agents = Staff::getStaffMembers(); +foreach ($members as $m) + unset($agents[$m->staff_id]); +?> + <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> + <table class="two-column table" width="100%"> + <tbody> + <tr class="header"> + <td colspan="2"> + <?php echo __('Team Members'); ?> + <div><small> + <?php echo __('Agents who are members of this team'); ?> + <i class="help-tip icon-question-sign" href="#members"></i> + </small></div> + </td> </tr> - </thead> + <tr id="add_member"> + <td colspan="2"> + <i class="icon-plus-sign"></i> + <select id="add_access" data-quick-add="staff"> + <option value="0">— <?php echo __('Select Agent');?> —</option> + <?php + foreach ($agents as $id=>$name) { + echo sprintf('<option value="%d">%s</option>',$id,Format::htmlchars($name)); + } + ?> + <option value="0" data-quick-add>— <?php echo __('Add New');?> —</option> + </select> + <button type="button" class="action-button"> + <?php echo __('Add'); ?> + </button> + </td> + </tr> + </tbody> <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> - <input type="checkbox" name="remove[]" value="%d"><i>'.__('Remove').'</i></td></tr>', - $staff->getId() , - $staff->getName(), - $staff->getId()); - - } - ?> + <tr id="member_template" class="hidden"> + <td> + <input type="hidden" data-name="members[]" value="" /> + </td> + <td> + <label> + <input type="checkbox" data-name="member_alerts" value="1" /> + <?php echo __('Alerts'); ?> + </label> + <a href="#" class="pull-right drop-membership" title="<?php echo __('Delete'); + ?>"><i class="icon-trash"></i></a> + </td> + </tr> </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="?"'> </p> </form> + +<script type="text/javascript"> +var addMember = function(staffid, name, alerts, error) { + if (!staffid) return; + var copy = $('#member_template').clone(); + + copy.find('[data-name=members\\[\\]]') + .attr('name', 'members[]') + .val(staffid); + copy.find('[data-name^=member_alerts]') + .attr('name', 'member_alerts['+staffid+']') + .prop('checked', alerts); + copy.find('td:first').append(document.createTextNode(name)); + copy.attr('id', '').show().insertBefore($('#add_member')); + copy.removeClass('hidden') + if (error) + $('<div class="error">').text(error).appendTo(copy.find('td:last')); +}; + +$('#add_member').find('button').on('click', function() { + var selected = $('#add_access').find(':selected'), + id = selected.val(); + addMember(selected.val(), selected.text(), true); + if ($('#team-lead-select option[value='+id+']').length === 0) { + $('#team-lead-select').find('option[data-quick-add]') + .before( + $('<option>').val(selected.val()).text(selected.text()) + ); + } + selected.remove(); + return false; +}); + +$(document).on('click', 'a.drop-membership', function() { + var tr = $(this).closest('tr'), + id = tr.find('input[name^=members][type=hidden]').val(); + $('#add_access').append( + $('<option>') + .attr('value', id) + .text(tr.find('td:first').text()) + ); + $('#team-lead-select option[value='+id+']').remove(); + tr.fadeOut(function() { $(this).remove(); }); + return false; +}); + +<?php +if ($team) { + foreach ($team->members->sort(function($a) { return $a->staff->getName(); }) as $member) { + echo sprintf('addMember(%d, %s, %d, %s);', + $member->staff_id, + JsonDataEncoder::encode((string) $member->staff->getName()), + $member->isAlertsEnabled(), + JsonDataEncoder::encode($errors['members'][$member->staff_id]) + ); + } +} +?> +</script> diff --git a/include/staff/teams.inc.php b/include/staff/teams.inc.php index b2c0947cd91b5a0ed2e73e429de295f970b937b4..8296b06c623d5b86de8a811e5fe79a88ebc402d4 100644 --- a/include/staff/teams.inc.php +++ b/include/staff/teams.inc.php @@ -43,15 +43,37 @@ $qstr .= '&order='.urlencode($order=='DESC' ? 'ASC' : 'DESC'); ?> -<div class="pull-left" style="width:700px;padding-top:5px;"> - <h2><?php echo __('Teams');?> - <i class="help-tip icon-question-sign" href="#teams"></i> - </h2> - </div> -<div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="teams.php?a=add" class="Icon newteam"><?php echo __('Add New Team');?></a></b></div> -<div class="clear"></div> <form action="teams.php" method="POST" name="teams"> +<div class="sticky bar"> + <div class="content"> + <div class="pull-left"> + <h2><?php echo __('Teams');?> + <i class="help-tip icon-question-sign notsticky" href="#teams"></i> + </h2> + </div> + <div class="pull-right flush-right"> + <a href="teams.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Team');?></a> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="teams.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="teams.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="teams.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + </div> + <div class="clear"></div> + </div> +</div> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -131,11 +153,7 @@ $qstr .= '&order='.urlencode($order=='DESC' ? 'ASC' : 'DESC'); if ($count): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>" > - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>" > -</p> + <?php endif; ?> diff --git a/include/staff/templates.inc.php b/include/staff/templates.inc.php index e83f2a526374212a5dc9aeed12030c236cccbd5e..a64906211d51b3c88ec6dbfdd1c30cc7d9d89637 100644 --- a/include/staff/templates.inc.php +++ b/include/staff/templates.inc.php @@ -42,14 +42,34 @@ else $showing=__('No templates found!'); ?> +<form action="templates.php" method="POST" name="tpls"> -<div class="pull-left" style="width:700px;padding-top:5px;"> +<div class="pull-left" style="padding-top:5px;"> <h2><?php echo __('Email Template Sets'); ?></h2> </div> <div class="pull-right flush-right" style="padding-top:5px;padding-right:5px;"> - <b><a href="templates.php?a=add" class="Icon newEmailTemplate"><?php echo __('Add New Template Set'); ?></a></b></div> + <a href="templates.php?a=add" class="green button action-button"><i class="icon-plus-sign"></i> <?php echo __('Add New Template Set'); ?></a> + + <span class="action-button" data-dropdown="#action-dropdown-more"> + <i class="icon-caret-down pull-right"></i> + <span ><i class="icon-cog"></i> <?php echo __('More');?></span> + </span> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul id="actions"> + <li><a class="confirm" data-name="enable" href="templates.php?a=enable"> + <i class="icon-ok-sign icon-fixed-width"></i> + <?php echo __('Enable'); ?></a></li> + <li><a class="confirm" data-name="disable" href="templates.php?a=disable"> + <i class="icon-ban-circle icon-fixed-width"></i> + <?php echo __('Disable'); ?></a></li> + <li class="danger"><a class="confirm" data-name="delete" href="templates.php?a=delete"> + <i class="icon-trash icon-fixed-width"></i> + <?php echo __('Delete'); ?></a></li> + </ul> + </div> + +</div> <div class="clear"></div> -<form action="templates.php" method="POST" name="tpls"> <?php csrf_token(); ?> <input type="hidden" name="do" value="mass_process" > <input type="hidden" id="action" name="a" value="" > @@ -113,11 +133,7 @@ else if($res && $num): //Show options.. echo '<div> '.__('Page').':'.$pageNav->getPageLinks().' </div>'; ?> -<p class="centered" id="actions"> - <input class="button" type="submit" name="enable" value="<?php echo __('Enable');?>" > - <input class="button" type="submit" name="disable" value="<?php echo __('Disable');?>" > - <input class="button" type="submit" name="delete" value="<?php echo __('Delete');?>" > -</p> + <?php endif; ?> diff --git a/include/staff/templates/dynamic-field-config.tmpl.php b/include/staff/templates/dynamic-field-config.tmpl.php index 780fe18dec545d8d3ca8d760bcf1d21f31f3ad4a..24bf9a79fde1bc715fe70bbd3d3ea5f3fa26358c 100644 --- a/include/staff/templates/dynamic-field-config.tmpl.php +++ b/include/staff/templates/dynamic-field-config.tmpl.php @@ -209,7 +209,7 @@ display: inline-block; vertical-align: top; } -.dialog input, .dialog select { +.dialog input[type=text], .dialog select { margin: 2px; } hr.faded { diff --git a/include/staff/templates/dynamic-form-fields-view.tmpl.php b/include/staff/templates/dynamic-form-fields-view.tmpl.php index 9ec9651f55c39af31e6692ddad10e3eac3e64abb..9bca7741d4492bef7e12415acd54e88c8508bc25 100644 --- a/include/staff/templates/dynamic-form-fields-view.tmpl.php +++ b/include/staff/templates/dynamic-form-fields-view.tmpl.php @@ -19,11 +19,11 @@ </td> </tr> <tr class="header"> - <th><?php echo __('Enable'); ?></th> - <th><?php echo __('Label'); ?></th> - <th><?php echo __('Type'); ?></th> - <th><?php echo __('Visibility'); ?></th> - <th><?php echo __('Variable'); ?></th> + <td><?php echo __('Enable'); ?></td> + <td><?php echo __('Label'); ?></td> + <td><?php echo __('Type'); ?></td> + <td><?php echo __('Visibility'); ?></td> + <td><?php echo __('Variable'); ?></td> </tr> <?php foreach ($form->getFields() as $f) { ?> diff --git a/include/staff/templates/dynamic-form-simple.tmpl.php b/include/staff/templates/dynamic-form-simple.tmpl.php index 8ec0cb2726a6c9b0b33c4d90c5a069938549e920..ca7a91b4982f8646f35e322229face5e9e46b4b1 100644 --- a/include/staff/templates/dynamic-form-simple.tmpl.php +++ b/include/staff/templates/dynamic-form-simple.tmpl.php @@ -4,15 +4,17 @@ foreach ($form->getFields() as $name=>$f) { ?> <div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id; ?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>> + <div> + <?php if ($f->get('label')) { ?> <div class="field-label <?php if ($f->get('required')) echo 'required'; ?>"> <label for="<?php echo $f->getWidget()->name; ?>"> - <?php if ($f->get('label')) { ?> <?php echo Format::htmlchars($f->get('label')); ?>: - <?php } ?> <?php if ($f->get('required')) { ?> <span class="error">*</span> <?php } ?> </label> + </div> + <?php } ?> <?php if ($f->get('hint')) { ?> <em style="color:gray;display:block"><?php diff --git a/include/staff/templates/quick-add-role.tmpl.php b/include/staff/templates/quick-add-role.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..929107b716468dcc302faa6e7705c8215f149d2f --- /dev/null +++ b/include/staff/templates/quick-add-role.tmpl.php @@ -0,0 +1,26 @@ +<?php +include 'quick-add.tmpl.php'; +$clone = $form->getField('clone')->getWidget()->name; +$permissions = $form->getField('perms')->getWidget()->name; +$name = $form->getField('name')->getWidget()->name; +?> +<script type="text/javascript"> + $('#_<?php echo $clone; ?>').change(function() { + var $this = $(this), + id = $this.val(), + form = $this.closest('form'), + name = $('[name="<?php echo $name; ?>"]:first', form); + $.ajax({ + url: 'ajax.php/admin/role/'+id+'/perms', + dataType: 'json', + success: function(json) { + $('[name="<?php echo $permissions; ?>[]"]', form).prop('checked', false); + $.each(json, function(k, v) { + form.find('[value="'+k+'"]', form).prop('checked', !!v); + }); + if (!name.val()) + name.val(__('Copy of {0}').replace('{0}', $this.find(':selected').text())); + } + }); + }); +</script> diff --git a/include/staff/templates/quick-add.tmpl.php b/include/staff/templates/quick-add.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..7f04098f316b34b96d305cad3649bd90fbde5e5e --- /dev/null +++ b/include/staff/templates/quick-add.tmpl.php @@ -0,0 +1,25 @@ +<h3 class="drag-handle"><?php echo $title ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<?php if (isset($errors['err'])) { ?> + <div id="msg_error" class="error-banner"><?php echo Format::htmlchars($errors['err']); ?></div> +<?php } ?> +<form method="post" action="#<?php echo $path; ?>"> + <div class="quick-add"> + <?php echo $form->asTable(); ?> + </div> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>" /> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>" /> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Create'); ?>" /> + </span> + </p> + <div class="clear"></div> +</form> diff --git a/include/staff/templates/reset-agent-permissions.tmpl.php b/include/staff/templates/reset-agent-permissions.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b69dbd72309ab78def3c70f1c7a308874c02d4a4 --- /dev/null +++ b/include/staff/templates/reset-agent-permissions.tmpl.php @@ -0,0 +1,22 @@ +<?php +include 'quick-add.tmpl.php'; +$clone = $form->getField('clone')->getWidget()->name; +$permissions = $form->getField('perms')->getWidget()->name; +?> +<script type="text/javascript"> + $('#_<?php echo $clone; ?>').change(function() { + var $this = $(this), + id = $this.val(), + form = $this.closest('form'); + $.ajax({ + url: 'ajax.php/staff/'+id+'/perms', + dataType: 'json', + success: function(json) { + $('[name="<?php echo $permissions; ?>[]"]', form).prop('checked', false); + $.each(json, function(k, v) { + form.find('[value="'+k+'"]', form).prop('checked', !!v); + }); + } + }); + }); +</script> diff --git a/include/staff/templates/set-password.tmpl.php b/include/staff/templates/set-password.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..bc3d66ff50fc8e2d870b11e255c2c38a3a2ad287 --- /dev/null +++ b/include/staff/templates/set-password.tmpl.php @@ -0,0 +1,22 @@ +<h3 class="drag-handle"><?php echo $title ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<form method="post" action="#<?php echo $path; ?>"> + <div class="inset"> + <?php $form->render(); ?> + </div> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>" /> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>" /> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Update'); ?>" /> + </span> + </p> + <div class="clear"></div> +</form> diff --git a/include/staff/templates/thread-entry-view.tmpl.php b/include/staff/templates/thread-entry-view.tmpl.php index 0b5a542bae73f87eadfa31a810487042c05e54f5..06f73f4188f8930845e08af8aaae938508a496c4 100644 --- a/include/staff/templates/thread-entry-view.tmpl.php +++ b/include/staff/templates/thread-entry-view.tmpl.php @@ -7,7 +7,7 @@ <?php $E = $entry; $i = 0; -$omniscient = $thisstaff->getRole()->hasPerm(ThreadEntry::PERM_EDIT); +$omniscient = $thisstaff->hasPerm(ThreadEntry::PERM_EDIT); do { $i++; if (!$omniscient diff --git a/include/staff/templates/tickets.tmpl.php b/include/staff/templates/tickets.tmpl.php index 57ced36e88cb1db5f1867a7454f1cf06764c3ce6..d7ee6521b612e7e19a5944fbc2bb7b78c11f165b 100644 --- a/include/staff/templates/tickets.tmpl.php +++ b/include/staff/templates/tickets.tmpl.php @@ -9,7 +9,7 @@ elseif ($org) { $tickets->filter(array('user__org' => $org)); } -if (!$thisstaff->getRole()->hasPerm(SearchBackend::PERM_EVERYTHING)) { +if (!$thisstaff->hasPerm(SearchBackend::PERM_EVERYTHING)) { // -- Open and assigned to me $visibility = array( new Q(array('status__state'=>'open', 'staff_id' => $thisstaff->getId())) diff --git a/include/staff/templates/user-lookup.tmpl.php b/include/staff/templates/user-lookup.tmpl.php index 4a8e1c505bf782b04fec58c0b8a606958e10a6d2..3e55a4cb3280c10a606519043d75675365011e21 100644 --- a/include/staff/templates/user-lookup.tmpl.php +++ b/include/staff/templates/user-lookup.tmpl.php @@ -5,7 +5,7 @@ <?php if (!isset($info['lookup']) || $info['lookup'] !== false) { ?> <div><p id="msg_info"><i class="icon-info-sign"></i> <?php echo - $thisstaff->getRole()->hasPerm(User::PERM_CREATE) + $thisstaff->hasPerm(User::PERM_CREATE) ? __('Search existing users or add a new user.') : __('Search existing users.'); ?></p></div> @@ -28,7 +28,7 @@ if ($info['error']) { <form method="post" class="user" action="<?php echo $info['action'] ? $info['action'] : '#users/lookup'; ?>"> <input type="hidden" id="user-id" name="id" value="<?php echo $user ? $user->getId() : 0; ?>"/> <i class="icon-user icon-4x pull-left icon-border"></i> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_CREATE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_CREATE)) { ?> <a class="action-button pull-right" style="overflow:inherit" id="unselect-user" href="#"><i class="icon-remove"></i> <?php echo __('Add New User'); ?></a> @@ -69,7 +69,7 @@ if ($user) { ?> </form> </div> <div id="new-user-form" style="display:<?php echo $user ? 'none' :'block'; ?>;"> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_CREATE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_CREATE)) { ?> <form method="post" class="user" action="<?php echo $info['action'] ?: '#users/lookup/form'; ?>"> <table width="100%" class="fixed"> <?php diff --git a/include/staff/templates/user.tmpl.php b/include/staff/templates/user.tmpl.php index 5add6b0824d389ff454ded25c57e9b83ce49717c..9400e4f93de8ed5ea0f96d5ffca19ba6a170f0a4 100644 --- a/include/staff/templates/user.tmpl.php +++ b/include/staff/templates/user.tmpl.php @@ -50,10 +50,10 @@ if ($info['error']) { <div id="user_tabs_container"> <div class="tab_content" id="info-tab"> <div class="floating-options"> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> <a href="<?php echo $info['useredit'] ?: '#'; ?>" id="edituser" class="action" title="<?php echo __('Edit'); ?>"><i class="icon-edit"></i></a> <?php } - if ($thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) { ?> + if ($thisstaff->hasPerm(User::PERM_DIRECTORY)) { ?> <a href="users.php?id=<?php echo $user->getId(); ?>" title="<?php echo __('Manage User'); ?>" class="action"><i class="icon-share"></i></a> <?php } ?> @@ -76,7 +76,7 @@ if ($info['error']) { <?php if ($org) { ?> <div class="hidden tab_content" id="org-tab"> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_DIRECTORY)) { ?> <div class="floating-options"> <a href="orgs.php?id=<?php echo $org->getId(); ?>" title="<?php echo __('Manage Organization'); ?>" class="action"><i class="icon-share"></i></a> diff --git a/include/staff/templates/users.tmpl.php b/include/staff/templates/users.tmpl.php index a2111414b06683a14453e8998cd9dc5c0ac9706f..636e3999c49459738d201b1827d853ec4c85e087 100644 --- a/include/staff/templates/users.tmpl.php +++ b/include/staff/templates/users.tmpl.php @@ -57,7 +57,7 @@ else ?> <div style="width:700px;" class="pull-left"><b><?php echo $showing; ?></b></div> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> <div class="pull-right flush-right" style="padding-right:5px;"> <b><a href="#orgs/<?php echo $org->getId(); ?>/add-user" class="Icon newstaff add-user" ><?php echo __('Add User'); ?></a></b> diff --git a/include/staff/ticket-edit.inc.php b/include/staff/ticket-edit.inc.php index fe92935d10799e14796174c8cde866561857d635..7d9140b455d68e8d5541d18f08f7506da856851c 100644 --- a/include/staff/ticket-edit.inc.php +++ b/include/staff/ticket-edit.inc.php @@ -40,7 +40,7 @@ if ($_POST) <span id="client-name"><?php echo Format::htmlchars($user->getName()); ?></span> <<span id="client-email"><?php echo $user->getEmail(); ?></span>> </a> - <a class="action-button" style="overflow:inherit" href="#" + <a class="inline action-button" style="overflow:inherit" href="#" onclick="javascript: $.userLookup('ajax.php/tickets/<?php echo $ticket->getId(); ?>/change-user', function(user) { diff --git a/include/staff/ticket-open.inc.php b/include/staff/ticket-open.inc.php index 65d4e37a089a479817311d01380e34a495678a64..6e2f9bae14106e8077ff70b189ea9f3bacdb7472 100644 --- a/include/staff/ticket-open.inc.php +++ b/include/staff/ticket-open.inc.php @@ -1,6 +1,6 @@ <?php if (!defined('OSTSCPINC') || !$thisstaff - || !$thisstaff->hasPerm(TicketModel::PERM_CREATE)) + || !$thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) die('Access Denied'); $info=array(); diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index ecad25ee7d8a79e59575551cef4790ecf459da8a..695ac4d745a312fbe8a23042c8ef4e254d549f20 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -59,7 +59,7 @@ if($ticket->isOverdue()) <div class="content"> <div class="pull-right flush-right"> <?php - if ($role->hasPerm(Email::PERM_BANLIST) + if ($thisstaff->hasPerm(Email::PERM_BANLIST) || $role->hasPerm(TicketModel::PERM_EDIT) || ($dept && $dept->isManager($thisstaff))) { ?> <span class="action-button pull-right" data-dropdown="#action-dropdown-more"> @@ -145,7 +145,7 @@ if($ticket->isOverdue()) return false" ><i class="icon-paste"></i> <?php echo __('Manage Forms'); ?></a></li> -<?php if ($role->hasPerm(Email::PERM_BANLIST)) { +<?php if ($thisstaff->hasPerm(Email::PERM_BANLIST)) { if(!$emailBanned) {?> <li><a class="confirm-action" id="ticket-banemail" href="#banemail"><i class="icon-ban-circle"></i> <?php echo sprintf( @@ -231,16 +231,26 @@ if($ticket->isOverdue()) $user->getId(), sprintf(_N('%d Closed Ticket', '%d Closed Tickets', $closed), $closed)); ?> <li><a href="tickets.php?a=search&uid=<?php echo $ticket->getOwnerId(); ?>"><i class="icon-double-angle-right icon-fixed-width"></i> <?php echo __('All Tickets'); ?></a></li> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_DIRECTORY)) { ?> <li><a href="users.php?id=<?php echo $user->getId(); ?>"><i class="icon-user icon-fixed-width"></i> <?php echo __('Manage User'); ?></a></li> <?php } ?> </ul> </div> +<?php } # end if ($user) ?> + </td> + </tr> + <tr> + <th><?php echo __('Email'); ?>:</th> + <td> + <span id="user-<?php echo $ticket->getOwnerId(); ?>-email"><?php echo $ticket->getEmail(); ?></span> + </td> + </tr> <?php if ($user->getOrgId()) { ?> - <span style="display:inline-block"> - <i class="icon-building"></i> + <tr> + <th><?php echo __('Organization'); ?>:</th> + <td><i class="icon-building"></i> <?php echo Format::htmlchars($user->getOrganization()->getName()); ?> <a href="tickets.php?<?php echo Http::build_query(array( 'status'=>'open', 'a'=>'search', 'orgid'=> $user->getOrgId() @@ -248,7 +258,6 @@ if($ticket->isOverdue()) data-dropdown="#action-dropdown-org-stats"> (<b><?php echo $user->getNumOrganizationTickets(); ?></b>) </a> - </span> <div id="action-dropdown-org-stats" class="action-dropdown anchor-right"> <ul> <?php if ($open = $user->getNumOpenOrganizationTickets()) { ?> @@ -268,30 +277,16 @@ if($ticket->isOverdue()) 'a' => 'search', 'orgid' => $user->getOrgId() )); ?>"><i class="icon-double-angle-right icon-fixed-width"></i> <?php echo __('All Tickets'); ?></a></li> <?php } - if ($thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) { ?> + if ($thisstaff->hasPerm(User::PERM_DIRECTORY)) { ?> <li><a href="orgs.php?id=<?php echo $user->getOrgId(); ?>"><i class="icon-building icon-fixed-width"></i> <?php echo __('Manage Organization'); ?></a></li> <?php } ?> </ul> </div> -<?php } # end if (user->org) - } # end if ($user) - ?> - </td> - </tr> - <tr> - <th><?php echo __('Email'); ?>:</th> - <td> - <span id="user-<?php echo $ticket->getOwnerId(); ?>-email"><?php echo $ticket->getEmail(); ?></span> - </td> - </tr> - <tr> - <th><?php echo __('Phone'); ?>:</th> - <td> - <span id="user-<?php echo $ticket->getOwnerId(); ?>-phone"><?php echo $ticket->getPhoneNumber(); ?></span> - </td> - </tr> + </td> + </tr> +<?php } # end if (user->org) ?> <tr> <th><?php echo __('Source'); ?>:</th> <td><?php @@ -657,8 +652,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); </tbody> </table> <p style="padding:0 165px;"> - <input class="btn_sm" type="submit" value="<?php echo __('Post Reply');?>"> - <input class="btn_sm" type="reset" value="<?php echo __('Reset');?>"> + <input class="save pending" type="submit" value="<?php echo __('Post Reply');?>"> + <input class="" type="reset" value="<?php echo __('Reset');?>"> </p> </form> <?php @@ -740,8 +735,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); </table> <p style="padding-left:165px;"> - <input class="btn_sm" type="submit" value="<?php echo __('Post Note');?>"> - <input class="btn_sm" type="reset" value="<?php echo __('Reset');?>"> + <input class="save pending" type="submit" value="<?php echo __('Post Note');?>"> + <input class="" type="reset" value="<?php echo __('Reset');?>"> </p> </form> <?php @@ -770,7 +765,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); echo sprintf('<span class="faded">'.__('Ticket is currently in <b>%s</b> department.').'</span>', $ticket->getDeptName()); ?> <br> - <select id="deptId" name="deptId"> + <select id="deptId" name="deptId" data-quick-add="department"> <option value="0" selected="selected">— <?php echo __('Select Target Department');?> —</option> <?php if($depts=Dept::getDepartments()) { @@ -781,6 +776,7 @@ $tcount = $ticket->getThreadEntries($types)->count(); } } ?> + <option value="0" data-quick-add>— <?php echo __('Add New'); ?> —</option> </select> <span class='error'>* <?php echo $errors['deptId']; ?></span> </td> </tr> @@ -799,8 +795,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); </tr> </table> <p style="padding-left:165px;"> - <input class="btn_sm" type="submit" value="<?php echo __('Transfer');?>"> - <input class="btn_sm" type="reset" value="<?php echo __('Reset');?>"> + <input class="save pending" type="submit" value="<?php echo __('Transfer');?>"> + <input class="" type="reset" value="<?php echo __('Reset');?>"> </p> </form> <?php @@ -899,8 +895,8 @@ $tcount = $ticket->getThreadEntries($types)->count(); </tr> </table> <p style="padding-left:165px;"> - <input class="btn_sm" type="submit" value="<?php echo $ticket->isAssigned()?__('Reassign'):__('Assign'); ?>"> - <input class="btn_sm" type="reset" value="<?php echo __('Reset');?>"> + <input class="save pending" type="submit" value="<?php echo $ticket->isAssigned()?__('Reassign'):__('Assign'); ?>"> + <input class="" type="reset" value="<?php echo __('Reset');?>"> </p> </form> <?php diff --git a/include/staff/tickets.inc.php b/include/staff/tickets.inc.php index 6c74947473fcdbc92390ac242538a6ff93cf36a0..8d6c2bc4a8135ba38bb56ed85bbbe0b844971481 100644 --- a/include/staff/tickets.inc.php +++ b/include/staff/tickets.inc.php @@ -107,7 +107,7 @@ case 'search': } elseif (isset($_SESSION['advsearch'])) { $form = $search->getFormFromSession('advsearch'); $tickets = $search->mangleQuerySet($tickets, $form); - $view_all_tickets = $thisstaff->getRole()->hasPerm(SearchBackend::PERM_EVERYTHING); + $view_all_tickets = $thisstaff->hasPerm(SearchBackend::PERM_EVERYTHING); $results_type=__('Advanced Search') . '<a class="action-button" href="?clear_filter"><i style="top:0" class="icon-ban-circle"></i> <em>' . __('clear') . '</em></a>'; $has_relevance = false; @@ -174,7 +174,7 @@ if (!$view_all_tickets) { if ($teams = array_filter($thisstaff->getTeams())) $assigned->add(array('team_id__in' => $teams)); - $visibility = Q::any(array('status__state'=>'open', $assigned)); + $visibility = Q::any(new Q(array('status__state'=>'open', $assigned))); // -- Routed to a department of mine if (!$thisstaff->showAssignedOnly() && ($depts=$thisstaff->getDepts())) @@ -237,10 +237,10 @@ case 'closed': case 'answered': $date_header = __('Last Response'); - $date_col = 'lastresponse'; + $date_col = 'thread__lastresponse'; $date_fallback = '<em class="faded">'.__('unanswered').'</em>'; - $tickets->order_by('-lastresponse'); - $tickets->values('lastresponse'); + $tickets->order_by('-thread__lastresponse'); + $tickets->values('thread__lastresponse'); break; case 'hot': @@ -309,6 +309,30 @@ $_SESSION[':Q:tickets'] = $orig_tickets; <!-- SEARCH FORM START --> <div id='basic_search'> + <div class="pull-right" style="height:25px"> + <span class="valign-helper"></span> + <span class="action-button muted" data-dropdown="#sort-dropdown"> + <i class="icon-caret-down pull-right"></i> + <span><i class="icon-sort-by-attributes-alt"></i> <?php echo __('Sort');?></span> + </span> + <div id="sort-dropdown" class="action-dropdown anchor-right" + onclick="javascript: console.log(event); $.pjax({ + url:'?' + addSearchParam('sort', $(event.target).data('mode')), + timeout: 2000, + container: '#pjax-container'});"> + <ul class="bleed-left"> +<?php foreach ($queue_sort_options as $mode) { +$desc = $sort_options[$mode]; +$selected = $mode == $_SESSION[$queue_sort_key]; ?> + <li <?php if ($selected) echo 'class="active"'; ?>> + <a data-mode="<?php echo $mode; ?>"><i class="icon-fixed-width <?php + if ($selected) echo 'icon-hand-right'; + ?>"></i> <?php echo Format::htmlchars($desc); ?></a> + </li> +<?php } ?> + </div> + </div> + <form action="tickets.php" method="get" onsubmit="javascript: $.pjax({ url:$(this).attr('action') + '?' + $(this).serialize(), @@ -317,19 +341,18 @@ $_SESSION[':Q:tickets'] = $orig_tickets; }); return false;"> <input type="hidden" name="a" value="search"> - <table> - <tr> - <td><input type="search" id="basic-ticket-search" name="query" - autofocus size="30" value="<?php echo Format::htmlchars($_REQUEST['query'], true); ?>" - autocomplete="off" autocorrect="off" autocapitalize="off"> - <input type="hidden" name="search-type" value=""/> - </td> - <td><input type="submit" class="button" value="<?php echo __('Search'); ?>"></td> - <td> <a href="#" onclick="javascript: - $.dialog('ajax.php/tickets/search', 201);" - >[<?php echo __('advanced'); ?>]</a> <i class="help-tip icon-question-sign" href="#advanced"></i></td> - </tr> - </table> + <input type="hidden" name="search-type" value=""/> + <div class="attached input"> + <input type="text" id="basic-ticket-search" name="query" + autofocus size="30" value="<?php echo Format::htmlchars($_REQUEST['query'], true); ?>" + autocomplete="off" autocorrect="off" autocapitalize="off"> + <button type="submit" class="attached button"><i class="icon-search"></i> + </button> + </div> + <a href="#" onclick="javascript: + $.dialog('ajax.php/tickets/search', 201);" + >[<?php echo __('advanced'); ?>]</a> + <i class="help-tip icon-question-sign" href="#advanced"></i> </form> </div> <!-- SEARCH FORM END --> @@ -343,24 +366,12 @@ return false;"> $results_type.$showing; ?></a></h2> </div> <div class="pull-right flush-right"> - <span class="notsticky" style="display:inline-block"> - <span style="vertical-align: baseline">Sort:</span> - <select name="sort" onchange="javascript: $.pjax({ - url:'?' + addSearchParam('sort', $(this).val()), - timeout: 2000, - container: '#pjax-container'});"> -<?php foreach ($queue_sort_options as $mode) { - $desc = $sort_options[$mode]; ?> - <option value="<?php echo $mode; ?>" <?php if ($mode == $_SESSION[$queue_sort_key]) echo 'selected="selected"'; ?>><?php echo $desc; ?></option> -<?php } ?> - </select> - </span> <?php if ($thisstaff->canManageTickets()) { echo TicketStatus::status_options(); } - if ($thisstaff->hasPerm(TicketModel::PERM_DELETE)) { ?> - <a id="tickets-delete" class="action-button tickets-action" + if ($thisstaff->hasPerm(TicketModel::PERM_DELETE, false)) { ?> + <a id="tickets-delete" class="red button action-button tickets-action" href="#tickets/status/delete"><i class="icon-trash"></i> <?php echo __('Delete'); ?></a> <?php diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php index 256654d42816ec1d803c589dce7c00aa1dfde781..7c894ca6dc2fc6ad74657bc3c99f3c5c4cb6ae9b 100644 --- a/include/staff/user-view.inc.php +++ b/include/staff/user-view.inc.php @@ -14,18 +14,18 @@ $org = $user->getOrganization(); </td> <td width="50%" class="right_align has_bottom_border"> <?php if (($account && $account->isConfirmed()) - || $thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> + || $thisstaff->hasPerm(User::PERM_EDIT)) { ?> <span class="action-button pull-right" data-dropdown="#action-dropdown-more"> <i class="icon-caret-down pull-right"></i> <span><i class="icon-cog"></i> <?php echo __('More'); ?></span> </span> <?php } - if ($thisstaff->getRole()->hasPerm(User::PERM_DELETE)) { ?> + if ($thisstaff->hasPerm(User::PERM_DELETE)) { ?> <a id="user-delete" class="action-button pull-right user-action" href="#users/<?php echo $user->getId(); ?>/delete"><i class="icon-trash"></i> <?php echo __('Delete User'); ?></a> <?php } ?> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_MANAGE)) { ?> <?php if ($account) { ?> <a id="user-manage" class="action-button pull-right user-action" @@ -55,7 +55,7 @@ $org = $user->getOrganization(); <?php echo __('Send Password Reset Email'); ?></a></li> <?php } ?> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_MANAGE)) { ?> <li><a class="user-action" href="#users/<?php echo $user->getId(); ?>/manage/access"><i class="icon-lock"></i> @@ -63,7 +63,7 @@ $org = $user->getOrganization(); <?php } } ?> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> <li><a href="#ajax.php/users/<?php echo $user->getId(); ?>/forms/manage" onclick="javascript: $.dialog($(this).attr('href').substr(1), 201); @@ -85,13 +85,13 @@ $org = $user->getOrganization(); <th width="150"><?php echo __('Name'); ?>:</th> <td> <?php -if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> <b><a href="#users/<?php echo $user->getId(); ?>/edit" class="user-action"><i class="icon-edit"></i> <?php } echo Format::htmlchars($user->getName()->getOriginal()); -if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> </a> <?php } ?> </td> @@ -110,7 +110,7 @@ if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> if ($org) echo sprintf('<a href="#users/%d/org" class="user-action">%s</a>', $user->getId(), $org->getName()); - elseif ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { + elseif ($thisstaff->hasPerm(User::PERM_EDIT)) { echo sprintf( '<a href="#users/%d/org" class="user-action">%s</a>', $user->getId(), diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php index 5ff926b64d4e00c450f282efebfcca884ae0186a..746ac3683dac1fe0a815d844223389ff3465be92 100644 --- a/include/staff/users.inc.php +++ b/include/staff/users.inc.php @@ -74,7 +74,7 @@ $users->order_by($order . $order_column); </div> <div class="pull-right"> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_CREATE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_CREATE)) { ?> <a class="action-button popup-dialog" href="#users/add"> <i class="icon-plus-sign"></i> @@ -93,12 +93,12 @@ $users->order_by($order . $order_column); </span> <div id="action-dropdown-more" class="action-dropdown anchor-right"> <ul> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_DELETE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_DELETE)) { ?> <li><a class="users-action" href="#delete"> <i class="icon-trash icon-fixed-width"></i> <?php echo __('Delete'); ?></a></li> <?php } -if ($thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { ?> +if ($thisstaff->hasPerm(User::PERM_EDIT)) { ?> <li><a href="#orgs/lookup/form" onclick="javascript: $.dialog('ajax.php/orgs/lookup/form', 201); return false;"> @@ -110,7 +110,7 @@ if ('disabled' != $cfg->getClientRegistrationMode()) { ?> <li><a class="users-action" href="#reset"> <i class="icon-envelope icon-fixed-width"></i> <?php echo __('Send Password Reset Email'); ?></a></li> -<?php if ($thisstaff->getRole()->hasPerm(User::PERM_MANAGE)) { ?> +<?php if ($thisstaff->hasPerm(User::PERM_MANAGE)) { ?> <li><a class="users-action" href="#register"> <i class="icon-smile icon-fixed-width"></i> <?php echo __('Register'); ?></a></li> diff --git a/include/upgrader/aborted.inc.php b/include/upgrader/aborted.inc.php index 5e4b1e9f74e8ef35fa66a0412214aec07537e6f7..846d5cd7238567a9243b92e1cc65ff711e4e9a7b 100644 --- a/include/upgrader/aborted.inc.php +++ b/include/upgrader/aborted.inc.php @@ -27,7 +27,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D </div> <p><strong><?php echo __('Need Help?');?></strong> <?php echo sprintf(__('We provide %1$s professional upgrade services %2$s and commercial support.'), '<a target="_blank" href="http://osticket.com/support/professional_services.php"><u>','</u></a>'); echo sprintf(__('%1$s Contact us %2$s today for <u>expedited</u> help.'), '<a target="_blank" href="http://osticket.com/support/">','</a>');?></p> </div> - <div id="sidebar"> + <div class="sidebar"> <h3><?php echo __('What to do?');?></h3> <p><?php echo sprintf(__('Restore your previous version from backup and try again or %1$s seek help %2$s.'), '<a target="_blank" href="http://osticket.com/support/">','</a>');?></p> </div> diff --git a/include/upgrader/done.inc.php b/include/upgrader/done.inc.php index 097cbe5bd7b715d95b0acd22102503857a16a1e8..58edda1cb125c666acc72c9627170b0e9da9c292 100644 --- a/include/upgrader/done.inc.php +++ b/include/upgrader/done.inc.php @@ -21,7 +21,7 @@ $_SESSION['ost_upgrader']=null; <br> <p><b><?php echo __('PS');?></b>: <?php echo __("Don't just make customers happy, make happy customers!");?></p> </div> - <div id="sidebar"> + <div class="sidebar"> <h3><?php echo __("What's Next?");?></h3> <p><b><?php echo __('Post-upgrade');?></b>: <?php echo sprintf(__('You can now go to %s to enable the system and explore the new features. For complete and up-to-date release notes see the %s'), diff --git a/include/upgrader/rename.inc.php b/include/upgrader/rename.inc.php index 408c2f75a06a066f39c3f1a8c7ed93b525557ed9..c553f173fe5e788a5f4250897a7321b1e98b873e 100644 --- a/include/upgrader/rename.inc.php +++ b/include/upgrader/rename.inc.php @@ -24,7 +24,7 @@ if(!defined('OSTSCPINC') || !$thisstaff || !$thisstaff->isAdmin()) die('Access D </form> </div> </div> - <div id="sidebar"> + <div class="sidebar"> <h3><?php echo __('Need Help?');?></h3> <p> <?php echo __('If you are looking for a greater level of support, we provide <u>professional upgrade</u> and commercial support with guaranteed response times and access to the core development team. We can also help customize osTicket or even add new features to the system to meet your unique needs. <a target="_blank" href="http://osticket.com/support">Learn More!</a>'); ?> diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index 20bbf7d78e595692f660eb24112f04b267799f87..f6aa807b2faf040f3616a572ef0292b9182d2662 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -0d6099a650cc7884eb59a040feab2ce8 +98ad7d550c26ac44340350912296e673 diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql b/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql new file mode 100644 index 0000000000000000000000000000000000000000..362a9400529e15c70c6488810c9b8d28bf711566 --- /dev/null +++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.cleanup.sql @@ -0,0 +1,39 @@ +/** + * @signature 4e9f2e2441e82ba393df94647a1ec9ea + * @version v1.10.0 + * @title Access Control 2.0 + * + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%group_dept_access`; + +-- Drop `updated` if it exists (it stayed in the install script after it was +-- removed from the update path +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = '%TABLE_PREFIX%team_member' + AND table_schema = DATABASE() + AND column_name = 'updated' + ) > 0, + "SELECT 1", + "ALTER TABLE `%TABLE_PREFIX%team_member` DROP `updated`" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +-- Drop `views` and `score` from 1ee831c8 as it cannot handle translations +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = '%TABLE_PREFIX%faq' + AND table_schema = DATABASE() + AND column_name = 'views' + ) > 0, + "SELECT 1", + "ALTER TABLE `%TABLE_PREFIX%faq` DROP `views`, DROP `score`;" +)); +PREPARE stmt FROM @s; +EXECUTE stmt; + +ALTER TABLE `%TABLE_PREFIX%ticket` DROP `lastmessage`, DROP `lastresponse`; diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql new file mode 100644 index 0000000000000000000000000000000000000000..ab6a3f7576c1cd4b8a633c503e4051334b04057e --- /dev/null +++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.patch.sql @@ -0,0 +1,49 @@ +/** + * @signature 98ad7d550c26ac44340350912296e673 + * @version v1.10.0 + * @title Access Control 2.0 + * + */ + +DROP TABLE IF EXISTS `%TABLE_PREFIX%staff_dept_access`; +CREATE TABLE `%TABLE_PREFIX%staff_dept_access` ( + `staff_id` int(10) unsigned NOT NULL DEFAULT 0, + `dept_id` int(10) unsigned NOT NULL DEFAULT 0, + `role_id` int(10) unsigned NOT NULL DEFAULT 0, + `flags` int(10) unsigned NOT NULL DEFAULT 1, + PRIMARY KEY `staff_dept` (`staff_id`,`dept_id`), + KEY `dept_id` (`dept_id`) +) DEFAULT CHARSET=utf8; + +INSERT INTO `%TABLE_PREFIX%staff_dept_access` + (`staff_id`, `dept_id`, `role_id`) + SELECT A1.`staff_id`, A2.`dept_id`, A2.`role_id` + FROM `%TABLE_PREFIX%staff` A1 + JOIN `%TABLE_PREFIX%group_dept_access` A2 ON (A1.`group_id` = A2.`group_id`); + +ALTER TABLE `%TABLE_PREFIX%staff` + DROP `group_id`, + ADD `permissions` text AFTER `extra`; + +ALTER TABLE `%TABLE_PREFIX%team_member` + ADD `flags` int(10) unsigned NOT NULL DEFAULT 1 AFTER `staff_id`; + +ALTER TABLE `%TABLE_PREFIX%thread_collaborator` + ADD KEY `user_id` (`user_id`); + +ALTER TABLE `%TABLE_PREFIX%task` + ADD `closed` datetime DEFAULT NULL AFTER `duedate`; + +ALTER TABLE `%TABLE_PREFIX%thread` + ADD `lastresponse` datetime DEFAULT NULL AFTER `extra`, + ADD `lastmessage` datetime DEFAULT NULL AFTER `lastresponse`; + +UPDATE `%TABLE_PREFIX%thread` A1 + JOIN `%TABLE_PREFIX%ticket` A2 ON (A2.`ticket_id` = A1.`object_id` AND A1.`object_type` = 'T') + SET A1.`lastresponse` = A2.`lastresponse`, + A1.`lastmessage` = A2.`lastmessage`; + +-- Finished with patch +UPDATE `%TABLE_PREFIX%config` + SET `value` = '98ad7d550c26ac44340350912296e673' + WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php b/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php new file mode 100644 index 0000000000000000000000000000000000000000..3f336a9e17f780ef3607aba8be5044e6294cfe22 --- /dev/null +++ b/include/upgrader/streams/core/0d6099a6-98ad7d55.task.php @@ -0,0 +1,30 @@ +<?php + +class StaffPermissions extends MigrationTask { + var $description = "Add staff permissions"; + + function run($time) { + foreach (Staff::objects() as $staff) { + $role = $staff->getRole()->getPermission(); + $perms = array( + User::PERM_CREATE, + User::PERM_EDIT, + User::PERM_DELETE, + User::PERM_MANAGE, + User::PERM_DIRECTORY, + Organization::PERM_CREATE, + Organization::PERM_EDIT, + Organization::PERM_DELETE, + ); + if ($role->has(FAQ::PERM_MANAGE)) + $perms[] = FAQ::PERM_MANAGE; + if ($role->has(Email::PERM_BANLIST)) + $perms[] = Email::PERM_BANLIST; + + $errors = array(); + $staff->updatePerms($perms, $errors); + $staff->save(); + } + } +} +return 'StaffPermissions'; diff --git a/include/upgrader/streams/core/15b30765-dd0022fb.task.php b/include/upgrader/streams/core/15b30765-dd0022fb.task.php index faf8c99670670a6e96c8fc95302759e791d26428..f633c36c6defcbfb7cbce5a5c2d1e1207004e5bd 100644 --- a/include/upgrader/streams/core/15b30765-dd0022fb.task.php +++ b/include/upgrader/streams/core/15b30765-dd0022fb.task.php @@ -207,7 +207,7 @@ class AttachmentMigrater extends MigrationTask { $this->enqueue($info); } - return $this->queueAttachments($limit); + return $this->getQueueLength(); } function skip($attachId, $error) { diff --git a/include/upgrader/streams/core/1ee831c8-36f6b328.task.php b/include/upgrader/streams/core/1ee831c8-36f6b328.task.php index d507553358cb17a25ecef590cf3c127f89dbedc7..ddf1098207c1aacd20b0d81c40d31221118f6164 100644 --- a/include/upgrader/streams/core/1ee831c8-36f6b328.task.php +++ b/include/upgrader/streams/core/1ee831c8-36f6b328.task.php @@ -1,19 +1,43 @@ <?php +define('GROUP_TABLE', TABLE_PREFIX.'group'); +class Group extends VerySimpleModel { + static $meta = array( + 'table' => GROUP_TABLE, + 'pk' => array('id'), + ); + const FLAG_ENABLED = 0x0001; + + function getName() { + return $this->name; + } +} + +Staff::getMeta()->addJoin('group', array( + 'constraint' => array('group_id' => 'Group.id'), +)); + class GroupRoles extends MigrationTask { var $description = "Migrate permissions from Group to Role"; static $pmap = array( - 'can_create_tickets' => 'ticket.create', - 'can_edit_tickets' => 'ticket.edit', - 'can_post_ticket_reply' => 'ticket.reply', - 'can_delete_tickets' => 'ticket.delete', - 'can_close_tickets' => 'ticket.close', - 'can_assign_tickets' => 'ticket.assign', - 'can_transfer_tickets' => 'ticket.transfer', - 'can_ban_emails' => 'emails.banlist', - 'can_manage_premade' => 'canned.manage', - 'can_manage_faq' => 'faq.manage', - 'can_view_staff_stats' => 'stats.agents', + 'ticket.create' => 'can_create_tickets', + 'ticket.edit' => 'can_edit_tickets', + 'ticket.reply' => 'can_post_ticket_reply', + 'ticket.delete' => 'can_delete_tickets', + 'ticket.close' => 'can_close_tickets', + 'ticket.assign' => 'can_assign_tickets', + 'ticket.transfer' => 'can_transfer_tickets', + 'task.create' => 'can_create_tickets', + 'task.edit' => 'can_edit_tickets', + 'task.reply' => 'can_post_ticket_reply', + 'task.delete' => 'can_delete_tickets', + 'task.close' => 'can_close_tickets', + 'task.assign' => 'can_assign_tickets', + 'task.transfer' => 'can_transfer_tickets', + 'emails.banlist' => 'can_ban_emails', + 'canned.manage' => 'can_manage_premade', + 'faq.manage' => 'can_manage_faq', + 'stats.agents' => 'can_view_staff_stats', ); function run($max_time) { @@ -30,7 +54,7 @@ class GroupRoles extends MigrationTask { 'notes' => $group->getName() ); $perms = array(); - foreach (self::$pmap as $k => $v) { + foreach (self::$pmap as $v => $k) { if ($group->{$k}) $perms[] = $v; } diff --git a/include/upgrader/streams/core/435c62c3-2e7531a2.task.php b/include/upgrader/streams/core/435c62c3-2e7531a2.task.php index d80bc23a2e616248409c3c3780e3eedfca975799..6a3832f08d960deeb0600ad56e3c62ec4855f52f 100644 --- a/include/upgrader/streams/core/435c62c3-2e7531a2.task.php +++ b/include/upgrader/streams/core/435c62c3-2e7531a2.task.php @@ -1,6 +1,9 @@ <?php require_once INCLUDE_DIR.'class.migrater.php'; +// Replaced in v1.10 for STAFF_DEPT_TABLE +define('GROUP_DEPT_TABLE', TABLE_PREFIX.'group_dept_access'); + class MigrateGroupDeptAccess extends MigrationTask { var $description = "Migrate department access for groups from v1.6"; diff --git a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql index e9920fd42d992586552c50fd77e8db0aebc53d78..4eb98233d7ff69fad3fbafdcdb573b0304715a87 100644 --- a/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql +++ b/include/upgrader/streams/core/b26f29a6-1ee831c8.patch.sql @@ -14,10 +14,6 @@ ALTER TABLE `%TABLE_PREFIX%attachment` ADD `lang` varchar(16) AFTER `inline`; -ALTER TABLE `%TABLE_PREFIX%faq` - ADD `views` int(10) unsigned NOT NULL default '0' AFTER `notes`, - ADD `score` int(10) NOT NULL default '0' AFTER `views`; - ALTER TABLE `%TABLE_PREFIX%staff` ADD `lang` varchar(16) DEFAULT NULL AFTER `signature`, ADD `timezone` varchar(64) default NULL AFTER `lang`, diff --git a/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql index 67a21aa7759080efc95cc08428285ce8490462c9..5db5be880fadc8107b909d5df1d99d3016b85de1 100644 --- a/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql +++ b/include/upgrader/streams/core/f5692e24-4323a6a8.patch.sql @@ -66,7 +66,6 @@ ALTER TABLE `%TABLE_PREFIX%email` ALTER TABLE `%TABLE_PREFIX%help_topic` ADD `sort` int(10) unsigned NOT NULL default '0' AFTER `form_id`; --- Add `content_id` to the content table to allow for translations RENAME TABLE `%TABLE_PREFIX%page` TO `%TABLE_PREFIX%content`; ALTER TABLE `%TABLE_PREFIX%content` CHANGE `type` `type` varchar(32) NOT NULL default 'other'; @@ -110,9 +109,6 @@ INSERT INTO `%TABLE_PREFIX%content` WHERE A3.`key` = 'default_template_id' and `namespace` = 'core') AND A1.`code_name` = 'user.accesslink'; -UPDATE `%TABLE_PREFIX%content` SET `content_id` = LAST_INSERT_ID() - WHERE `id` = LAST_INSERT_ID(); - -- Transfer staff password reset link INSERT INTO `%TABLE_PREFIX%content` (`name`, `body`, `type`, `isactive`, `created`, `updated`) @@ -122,9 +118,6 @@ INSERT INTO `%TABLE_PREFIX%content` WHERE A3.`key` = 'default_template_id' and `namespace` = 'core') AND A1.`code_name` = 'staff.pwreset'; -UPDATE `%TABLE_PREFIX%content` SET `content_id` = LAST_INSERT_ID() - WHERE `id` = LAST_INSERT_ID(); - -- No longer saved in the email_template table DELETE FROM `%TABLE_PREFIX%email_template` WHERE `code_name` IN ('staff.pwreset', 'user.accesslink'); diff --git a/js/redactor-plugins.js b/js/redactor-plugins.js index 33edff821410ffa9c3dd136beed65170e145a0b5..8f01f5f6c8b81aee091e3a2ad91799c1d8ab3d63 100644 --- a/js/redactor-plugins.js +++ b/js/redactor-plugins.js @@ -1804,6 +1804,8 @@ RedactorPlugins.contexttypeahead = function() { }, select: function(item, event) { + // Collapse multiple textNodes together + (this.selection.getBlock() || this.$editor.get(0)).normalize(); var current = this.selection.getCurrent(), sel = this.selection.get(), range = this.sel.getRangeAt(0), diff --git a/scp/ajax.php b/scp/ajax.php index 054ef105068f6b36eb92797b4cac48467f78c066..be0e7c33db7ca4dfabef4b5f1d5087f164551e00 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -224,6 +224,22 @@ $dispatcher = patterns('', url_get('^translate/(?P<tag>\w+)$', 'getTranslations'), url_post('^translate/(?P<tag>\w+)$', 'updateTranslations'), url_get('^(?P<lang>[\w_]+)/(?P<tag>\w+)$', 'getLanguageFile') + )), + url('^/admin', patterns('ajax.admin.php:AdminAjaxAPI', + url('^/quick-add', patterns('ajax.admin.php:AdminAjaxAPI', + url('^/department$', 'addDepartment'), + url('^/team$', 'addTeam'), + url('^/role$', 'addRole'), + url('^/staff$', 'addStaff') + )), + url_get('^/role/(?P<id>\d+)/perms', 'getRolePerms') + )), + url('^/staff', patterns('ajax.staff.php:StaffAjaxAPI', + url('^/(?P<id>\d+)/set-password$', 'setPassword'), + url('^/(?P<id>\d+)/change-password$', 'changePassword'), + url_get('^/(?P<id>\d+)/perms', 'getAgentPerms'), + url('^/reset-permissions', 'resetPermissions'), + url('^/change-department', 'changeDepartment') )) ); diff --git a/scp/canned.php b/scp/canned.php index 85af1b2c46e4420a0e7528f95a33e76d8ab96060..8a54473754f103860f95c793059888b9e9a19193 100644 --- a/scp/canned.php +++ b/scp/canned.php @@ -18,7 +18,7 @@ include_once(INCLUDE_DIR.'class.canned.php'); /* check permission */ if(!$thisstaff - || !$thisstaff->getRole()->hasPerm(CannedModel::PERM_MANAGE) + || !$thisstaff->getRole()->hasPerm(Canned::PERM_MANAGE, false) || !$cfg->isCannedResponseEnabled()) { header('Location: kb.php'); exit; diff --git a/scp/categories.php b/scp/categories.php index d4e6125b4bca4d3fee6d40544d1ec173c408ff17..3e25c1d672e14f017b26c9da44863cc98f70455b 100644 --- a/scp/categories.php +++ b/scp/categories.php @@ -18,7 +18,7 @@ include_once(INCLUDE_DIR.'class.category.php'); /* check permission */ if(!$thisstaff || - !$thisstaff->getRole()->hasPerm(FAQ::PERM_MANAGE)) { + !$thisstaff->hasPerm(FAQ::PERM_MANAGE)) { header('Location: kb.php'); exit; } diff --git a/scp/css/dropdown.css b/scp/css/dropdown.css index 236deb3664c1c7f424e901130e9a71c41e7fd2c4..c0b90c83a9fc3b080df495c16e74c18380b88770 100644 --- a/scp/css/dropdown.css +++ b/scp/css/dropdown.css @@ -11,7 +11,7 @@ } .action-dropdown ul { text-align: left; - font-size: 13px; + font-size: 0.95em; min-width: 140px; list-style: none; background: #FFF; @@ -34,14 +34,28 @@ color: #555; text-decoration: none; line-height: 18px; - padding: 3px 15px; + padding: 3px 10px; white-space: nowrap; } -.action-dropdown ul li > a:hover { +.action-dropdown ul.bleed-left li > a { + padding-left: 8px; +} +.action-dropdown ul li > a i { + margin-right: 0.1em; +} +.action-dropdown ul li > a:hover, +.action-dropdown ul li.active > a:hover { background-color: #08C; color: #FFF !important; cursor: pointer; } +.action-dropdown ul li.active > a { + background-color: rgba(0, 136, 204, 0.2); + color: #08C; +} +.action-dropdown ul li.danger > a:hover { + background-color: #CF3F3F; +} .action-dropdown ul li > a.disabled { pointer-events: none; color: #999; @@ -85,56 +99,28 @@ left: auto; right: 10px; } - -.action-button { - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - -webkit-background-clip: padding-box; - -moz-background-clip: padding; - background-clip: padding-box; - color: #777 !important; - display: inline-block; - border: 1px solid #aaa; - cursor: pointer; - font-size: 11px; - overflow: hidden; - background-color: #dddddd; - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #efefef), color-stop(100% #dddddd)); - background-image: -webkit-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -moz-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -ms-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -o-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: linear-gradient(top, #efefef 0%, #dddddd 100%); - padding: 0 5px; - text-decoration: none !important; - line-height:18px; - margin-left:5px; - vertical-align: bottom; -} .action-button span, .action-button a { - color: #777 !important; + color: inherit; display: inline-block; float: left; } .action-button i.icon-caret-down { - background-color: #dddddd; - background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #efefef), color-stop(100% #dddddd)); - background-image: -webkit-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -moz-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -ms-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: -o-linear-gradient(top, #efefef 0%, #dddddd 100%); - background-image: linear-gradient(top, #efefef 0%, #dddddd 100%); - height: 18px; - line-height: 18px; - margin-right: 0; - margin-left: 5px; - padding-left: 5px; - border-left: 1px solid #aaa; + height: 17px; + line-height: 100%; + margin-right: -1px; + margin-left: 5px; + padding-left: 5px; + margin-top: -1px; + padding-top: 7px; + display: inline-block; + border-left: 1px solid #ccc; } -.action-button a { - color: #777; +.action-button:hover i.icon-caret-down { + border-color: inherit; +} +a.action-button, .action-button a { + color: inherit; text-decoration: none; } .action-buttons { diff --git a/scp/css/scp.css b/scp/css/scp.css index 88df4b16599867216d074dd03a8b32a431efa073..c35426aff4c593abc04c573fc1ef89d58405428c 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -1,8 +1,12 @@ body { + font-family: "Lato", "Helvetica Neue", arial, helvetica, sans-serif; + font-weight: 300; + letter-spacing: 0.15px; +} +body, html { background:#eee; - font-family:arial, helvetica, sans-serif; - font-size:10pt; color:#000; + font-size:14px; margin:0; padding:0; } @@ -235,7 +239,7 @@ a time.relative { width:230px; background:#fbfbfb; margin:0; - padding:0; + padding:5px 0; position:absolute; left: -1px; z-index:500; @@ -243,9 +247,7 @@ a time.relative { border-left:1px solid #ccc; border-right:1px solid #ccc; border-radius: 0 0 5px 5px; - display:block; - padding-left: 5px; -moz-box-shadow: 3px 3px 3px #ccc; -webkit-box-shadow: 3px 3px 3px #ccc; box-shadow: 3px 3px 3px #ccc; @@ -259,7 +261,7 @@ a time.relative { #nav .inactive li { display:block; margin:0; - padding:0 5px; + padding:0; list-style:none; text-align:left; } @@ -283,18 +285,21 @@ a time.relative { } #nav .inactive li a { - padding-left:24px; - background-position:0 50%; - background-repeat:no-repeat; + background-position: 10px 50%; + background-repeat: no-repeat; + padding: 0 10px 0 34px; } #nav .inactive li a:hover { color:#E65524; + background-color: #fbfbfb; + background-color: rgba(0,0,0,0.05); } #sub_nav { background:#f7f7f7; border-bottom:1px solid #bebebe; + padding: 2px 20px; } #sub_nav a { @@ -474,7 +479,7 @@ a { clear:both; padding:10px; text-align:center; - font-size:9pt; + font-size:0.9em; } table { vertical-align:top; } @@ -484,8 +489,6 @@ table.list { background:#ccc; margin: 2px 0; border-bottom: 1px solid #ccc; - font-family:arial, helvetica, sans-serif; - font-size:10pt; } table.list caption { @@ -505,9 +508,10 @@ table.list thead th { } table.list th a { - text-decoration:none; color:#000; + margin: -4px -5px; + padding: 4px 5px; } table.list thead th a { padding-right: 15px; display: block; white-space: nowrap; color: #000; background: url('../images/asc_desc.gif') 100% 50% no-repeat; } @@ -521,7 +525,7 @@ table.list tbody td { vertical-align:top; } -table.list tbody td { background: #fff; padding: 1px 3px; vertical-align: top; } +table.list tbody td { background: #fff; padding: 4px 3px; vertical-align: top; } table.list tbody tr:nth-child(2n+1) td { background-color: #f0faff; } table.list tbody tr:hover td { background: #ffe; } table.list tbody tr:nth-child(2n+1):hover td { background: #ffd; } @@ -595,28 +599,15 @@ a.print { background-image:url(../images/icons/printer.gif); } -.btn { - padding:3px 10px; - background:url(../images/btn_bg.png) top left repeat-x #ccc; - border:1px solid #777; - color:#000; -} - -#actions button, .button { padding:2px 5px 3px; margin-right:10px; color:#333;} - .btn_sm { padding:2px 5px; - font-size:9pt; + font-size:0.9em; background:url(../images/btn_sm_bg.png) top left repeat-x #f90; border:1px solid #777; color:#fff; font-weight:bold; } -.btn:hover, .btn_sm:hover { - background-position: bottom left; -} - .search label { display:block; line-height:25px; @@ -642,18 +633,34 @@ input[type=search] { } .table tr.header td, -.table th { - font-weight: bold; +.table tr.header th, +.table > thead th { + font-weight: 400; + font-size: 1.3em; text-align: left; - height: 24px; - background: #f0f0f0; + min-height: 24px; +} +.table tbody:not(:first-child) th { + padding-top: 1.4em; } -.table tr { +.table tr:not(:last-child):not(.header) { border-bottom:1px dotted #ddd; } +.table tr.header { + border-bottom: 1px dotted #777; +} .table td:not(:empty) { - height: 24px; + padding: 5px; +} +.table.two-column tbody tr td:first-child { + width: 25%; +} +.table > tbody > tr.header + tr td { + padding-top: 10px; +} +.table td .pull-right { + margin-right: 15px; } .form_table { @@ -664,6 +671,7 @@ input[type=search] { .form_table td { border-bottom:1px solid #ddd; + padding: 4px; } .form_table td:not(:empty) { height: 20px; @@ -707,7 +715,7 @@ td.multi-line { } .form_table em { - font-weight:normal; + font-weight:400; color:#666; } @@ -768,8 +776,8 @@ div.section-break h3 { } .settings_table h4 a span { - font-size:12pt; - line-height:14px; + font-size:1.2em; + line-height:1.15em; display:inline-block; width:14px; height:14px; @@ -784,7 +792,7 @@ div.section-break h3 { h2 { margin:0 0 0.7em; padding:0; - font-size:12pt; + font-size:1.2em; color:#0A568E; } @@ -800,7 +808,6 @@ h2 span { color:#000; } h3 { margin:10px 0 0 0; padding:5px 0; - font-size:10pt; color:#444; } @@ -1062,6 +1069,22 @@ ul.tabs li.active { bottom: 0; box-shadow: 4px -1px 6px -3px rgba(0,0,0,0.2); } +li.error { + border-top: 2px solid rgba(255, 0, 0, 0.3) !important; +} +li.error.active { + border-top-color: rgba(255, 0, 0, 0.7) !important; + +} +li.error a:before { + background-color: rgba(255,0,0,0.06); + top: 0; + left: 0; + bottom: 0; + right: 0; + content: ""; + position: absolute; +} ul.tabs li:not(.active) { box-shadow: inset 0 -5px 10px -9px rgba(0,0,0,0.2); } @@ -1070,9 +1093,10 @@ ul.tabs.clean li.active { } ul.tabs li a { - font-weight: normal; + font-weight: 300; line-height: 20px; color: #444; + color: rgba(0,0,0,0.6); display: block; outline: none; padding: 5px 10px; @@ -1083,6 +1107,8 @@ ul.tabs li a:hover { ul.tabs li.active a { font-weight: bold; + color: #222; + color: rgba(0,0,0,0.8); } ul.tabs li.empty { @@ -1141,6 +1167,29 @@ ul.tabs.vertical li a { padding: 5px; } +ul.tabs.alt { + background-color:initial; + border-bottom:2px solid #ccc; + border-bottom-color: rgba(0,0,0,0.1); + box-shadow:none; +} + +ul.tabs.alt li { + width:auto; + border:none; + min-width:0; + box-shadow:none; + bottom: 1px; + height: auto; +} + +ul.tabs.alt li.active { + border:none; + box-shadow:none; + background-color: transparent; + border-bottom:2px solid #81a9d7; +} + #response_options .reply_tab.tell { color:#a00 !important; background-image:url(../images/reminder.png); @@ -1240,15 +1289,15 @@ ul.tabs.vertical li a { -moz-border-radius:5px; -webkit-border-radius:5px; border-radius:5px; - -moz-box-shadow: 3px 3px 3px #666; - -webkit-box-shadow: 3px 3px 3px #666; - box-shadow: 3px 3px 3px #666; + -moz-box-shadow: 5px 5px 10px -2px rgba(0,0,0,0.5); + -webkit-box-shadow: 5px 5px 10px -2px rgba(0,0,0,0.5); + box-shadow: 5px 5px 10px -2px rgba(0,0,0,0.5); z-index:3; position:absolute; top:0; left:-1px; min-width:400px; - line-height: 1.15rem; + line-height: 1.45rem; } .tip_content .links { @@ -1298,7 +1347,7 @@ ul.tabs.vertical li a { padding:5px 0; border-top:1px solid #aaa; height:16px; - font-size:9pt; + font-size:0.9em; } .tip_menu li { @@ -1341,14 +1390,14 @@ ul.tabs.vertical li a { width:auto !important; width:295px; text-align:right; - line-height:24px; + line-height:1.5em; } .tip_content h1 { - font-size: 13pt; + font-size: 1.3em; margin-top: 0; - margin-bottom: 0.5em; - padding-bottom: 0.2em; + margin-bottom: 0.4em; + padding-bottom: 0.5em; border-bottom: 1px solid #ddd; padding-right: 1.5em; } @@ -1375,8 +1424,8 @@ caption:hover > i.help-tip { } h2 > i.help-tip { - vertical-align: baseline; - font-size: 11pt; + vertical-align: middle; + font-size: 1.1em; } .form_table th h4 i.help-tip { color: white; @@ -1565,7 +1614,7 @@ time.faq { } .redactor-editor { - font-size: 11pt; + font-size: 1.1em; } .dialog#advanced-search { @@ -1620,11 +1669,10 @@ time.faq { } .custom-field .field-label { - margin-left: 3px; - margin-right: 3px; + margin: 0 3px 4px; } .custom-field + .custom-field { - margin-top: 5px; + margin-top: 8px; } .dialog label.fixed-size { width:100px; @@ -1699,30 +1747,144 @@ time.faq { margin-left: 45px; } -.dialog input[type="submit"], -.dialog input[type="reset"], -.dialog input[type="button"], -.dialog button.button { +input[type="submit"], +input[type="reset"], +input[type="button"], +.action-button, +.button { + cursor: pointer; + box-sizing: content-box; display:inline-block; - margin:0; - height:24px; - line-height:24px; - font-weight:bold; - border:1px solid #666666; - padding:0 10px; - background: url('../images/grey_btn_bg.png?1312910883') top left repeat-x; - color: #333; + vertical-align:bottom; + margin:0 4px; + height:22px; + line-height: 22px; + border: none; + box-shadow: 0 0 0 1px rgba(0,0,0,0.25) inset; + padding:2px 11px; + color: #555; + background-color: #f0f0f0; + background-color: rgba(0,0,0,0.02); + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + font-family: inherit; + font-size: 0.95em; + text-decoration: none; +-webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +-webkit-transition: opacity 0.1s ease, background-color 0.1s ease, box-shadow 0.1s ease, color 0.1s ease, background 0.1s ease; + transition: opacity 0.1s ease, background-color 0.1s ease, box-shadow 0.1s ease, color 0.1s ease, background 0.1s ease; +} +input[type="submit"] i, +input[type="reset"] i, +input[type="button"] i, +.action-button i, +.button i { + margin-right: 0.1em; +} +select + .action-button, +select + .button { + vertical-align: middle; +} + +.white.button { + background-color: rgba(255,255,255,0.6); + border-color: #555; +} +.white.button:hover { + background-color: rgba(255,255,255,0.8); + border-color: black; +} + +.button.attached { + margin-left: -4px; + box-shadow: none !important; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border: 1px solid #999; + border-left: none; + padding: 0 9px; +} +.input.attached { + height: 24px; + box-sizing: border-box; + display: inline-block; +} +.input.attached input { + height: 100%; + box-sizing: border-box; +} +.input.attached .button.attached { + height: 100%; + box-sizing: border-box; +} + +.green.button:hover { + background-color: inherit; + box-shadow: 0 0 0 2px #16ab39 inset; + color: #16ab39; +} + +.red.button:hover { + background-color: inherit; + box-shadow: 0 0 0 2px #d01919 inset; + color: #d01919; +} + +button[type=submit], input[type="submit"], .primary.button { + font-weight: bold; + box-shadow: 0 0 0 1px rgba(0,0,0,0.45) inset; + background-color: rgba(0,0,0,0.07); +} + +.save.pending { + background-color: rgba(255, 174, 0, 0.63); + box-shadow: 0 0 0 2px rgba(255, 174, 0, 1) inset; } -.dialog input[type="reset"], -.dialog input[type="button"] { - opacity:0.7; +.button:hover, .button:active, +.action-button:hover, .action-button:active, +input[type=button]:hover, input[type=button]:active, +input[type=reset]:hover, input[type=reset]:active { + color: black; + box-shadow: 0 0 0 2px rgba(0,0,0,0.5) inset; + background-color: #ddd; + background-color: rgba(0, 0, 0, 0.08); } -.dialog input[type=submit]:hover, .dialog input[type=submit]:active, -.dialog input[type=button]:hover, .dialog input[type=button]:active, -.dialog input[type=reset]:hover, .dialog input[type=reset]:active { - background-position:bottom left; +button[type=submit]:hover, input[type=submit]:hover, input[type=submit]:active { + color: white; + box-shadow: 0 0 0 2px rgba(0,0,0,0.7) inset; + background-color: #888; + background-color: rgba(0, 0, 0, 0.5); +} + +.save.pending:hover { + box-shadow: 0 0 0 2px rgba(242, 165, 0, 1) inset; + background-color: rgba(255, 174, 0, 0.79); + color: black; +} + +input[type=button].small, .small.button, input[type=submit].small { + font-size: 0.8em; + height: 18px; + line-height: 100%; + font-weight: normal; +} + +.action-button.muted { + box-shadow: 0 0 0 1px rgba(0,0,0,0.08) inset; +} + +.action-button.muted i.icon-caret-down { + border: none; +} + +.action-button.inline, .button.inline { + vertical-align: middle; } /* Dynamic forms in dialogs */ @@ -1771,12 +1933,12 @@ time.faq { /* Upgrader */ #upgrader { width: 100%; height: auto; clear: both;} .sidebar { width: 220px; padding: 10px; border: 1px solid #C8DDFA; float: right; background: #F7FBFE; } -.sidebar h3 { font-size: 10pt; margin: 0 0 5px 0; padding: 0; text-indent: 32px; background: url('../images/cog.png?1312913866') top left no-repeat; line-height: 24px; color: #2a67ac; } +.sidebar h3 { margin: 0 0 5px 0; padding: 0; text-indent: 32px; background: url('../images/cog.png?1312913866') top left no-repeat; line-height: 24px; color: #2a67ac; } #upgrader #main { width: 680px; float: left;} -#upgrader #main h1 { margin: 0; padding: 0; font-size: 21pt; font-weight: normal; } -#upgrader #main h2 { font-size: 12pt; margin: 0; padding: 0; color:#E65524; } -#upgrader #main h3 { font-size: 10pt; margin: 0; padding: 0; } +#upgrader #main h1 { margin: 0; padding: 0; font-size: 1.6em; font-weight: normal; } +#upgrader #main h2 { font-size: 1.2em; margin: 0; padding: 0; color:#E65524; } +#upgrader #main h3 { margin: 0; padding: 0; } #upgrader #main div#intro { padding-bottom: 5px; margin-bottom:10px; border-bottom: 1px solid #aaaaaa; } #upgrader #main { padding-bottom: 20px; } @@ -1789,8 +1951,6 @@ ul.progress li.yes small {color:green; } ul.progress li.no small {color:red;} #bar { clear: both; padding-top: 10px; height: 24px; line-height: 24px; text-align: center; border-top: 1px solid #aaaaaa; } -#bar a, #bar .btn { display: inline-block; margin: 0; height: 24px; line-height: 24px; font-weight: bold; border: 1px solid #666666; text-decoration: none; padding: 0 10px; background: url('../images/grey_btn_bg.png?1312910883') top left repeat-x; color: #333; } -#bar a:hover, #bar .btn:hover, #bar .btnover { background-position: bottom left; } #bar a.unstyled, #bar a.unstyled:hover { font-weight: normal; background: none; border: none; text-decoration: underline; color: #2a67ac; } #bar.error { background: #ffd; text-align: center; color: #a00; font-weight: bold; } @@ -2171,9 +2331,9 @@ button a:hover { font-style: italic; } -.form_table tr:hover i.help-tip, -.form_table tr i.help-tip.warning { - opacity: 1; +tr:hover i.help-tip, +tr i.help-tip.warning { + opacity: 0.8 !important; color: #ffc20f; } @@ -2323,6 +2483,13 @@ td.indented { .sticky.bar.fixed .notsticky { display: none !important; } + +.sticky.bar.fixed .inline { + float:left; + display:inline; + margin:5px 10px 0 0; +} + .sticky.bar.opaque { background-color: white; } @@ -2446,3 +2613,102 @@ td.indented { margin-bottom: 0.3em; font-size: 1.1em; } + +/* Form simple grid sizing */ +.iblock { + display: inline-block; +} +form .inset { + padding: 10px; +} +.dialog form .quick-add { + min-height: 150px; +} +.span12 { + width: 100%; +} +.span6 { + width: 48%; + width: calc(50% - 10px); +} +.span6 + .span6 { + margin-left: 1%; + margin-left: calc(0 + 10px); +} +.form.footer { + margin-top: 50px; +} +label.checkbox { + display: block; + padding-left: 1.2rem; + text-indent: -1.2rem; +} +input[type=checkbox] { + width: 0.9rem; + height: 0.9rem; + padding: 0; + margin:0; + vertical-align: bottom; + position: relative; + top: -1px; + *overflow: hidden; +} +.vertical-pad { + margin-top: 3px; +} + +input, textarea { + padding: 3px 5px; + font-size: 0.95em; + font-family: inherit; +} + +small { + font-weight: 300; + letter-spacing: 0.01px; +} + +/* Form layouts */ +table.grid.form { + width: 100%; + table-layout: fixed; +} +table.grid.form caption { + font-size: 1.3em; + font-weight: bold; + text-align: start; + padding: 0 9px; +} +.grid.form .cell { + vertical-align: top; +} +.grid.form .field { + padding: 5px; +} +.grid.form .field input:not([type=checkbox]), +.grid.form .field textarea, +.grid.form .field select { + width: 100%; + display: block; +} +.grid.form .field > label { + display: block; + margin-bottom: 5px; +} + +#basic_search { + background-color: #f4f4f4; + margin: -10px 0; + margin-bottom: 5px; + padding: 8px; + box-shadow: inset 0 4px 12px -10px black; + border-bottom: 1px dotted #aaa; + border-radius: 0 0 5px 5px; +} + +#basic-ticket-search { + border: 1px solid #999; + border-color: rgba(0,0,0,0.45); + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} diff --git a/scp/css/translatable.css b/scp/css/translatable.css index 3bece152ac700f39a6e7ff6404e3e7ae14814044..6601345e90f328102be8a818974ea1d342ad63be 100644 --- a/scp/css/translatable.css +++ b/scp/css/translatable.css @@ -100,12 +100,7 @@ div.translatable { box-shadow: inset 0 1px 1px rgba(0,0,0,0.05); display: inline-block; white-space: nowrap; - border-top-left-radius: 3px; - border-bottom-left-radius: 3px; border-right: none; - padding: 1px 5px 1px; - margin-left: 2px; - width: auto; background-color: white; line-height: 16px; } @@ -121,9 +116,7 @@ div.translatable.focus { outline-color: -webkit-focus-ring-color; } div.translatable .flag { - position: relative; - top: 1px; - margin-left: 2px; + margin-right: 4px; } div.translatable.textarea .flag { vertical-align: top; @@ -132,10 +125,9 @@ div.translatable.textarea .flag { textarea.translatable, input.translatable { border: none !important; - padding: 0 !important; + padding: 2px 5px !important; margin: 0 !important; background: none; - font-family: sans-serif; } textarea.translatable, input.translatable:focus { @@ -143,14 +135,15 @@ input.translatable:focus { } button.translatable { - margin: 0; - padding: 3px 5px 4px; + margin: -1px 0; + padding: 4px 5px 5px; background-color: #444; background:linear-gradient(0deg, #444 0, #888 100%); color: white; border: none; border-radius: 0 2px 2px 0; cursor: pointer; + vertical-align: top; } div.translatable.textarea + button.translatable { diff --git a/scp/emailtest.php b/scp/emailtest.php index cd9fbae9d67afcab14e96ed180d5490faab6bae9..5bd65e3199bc17506116ac31717b251692b158fe 100644 --- a/scp/emailtest.php +++ b/scp/emailtest.php @@ -126,7 +126,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); </tr> </tbody> </table> -<p style="padding-left:225px;"> +<p style="text-align:center;"> <input type="submit" name="submit" value="<?php echo __('Send Message');?>"> <input type="reset" name="reset" value="<?php echo __('Reset');?>"> <input type="button" name="cancel" value="<?php echo __('Cancel');?>" onclick='window.location.href="emails.php"'> diff --git a/scp/faq.php b/scp/faq.php index bac34c7ceefe4b8cd1c30bd396c93cab2745bc02..7881dbc18edb42484653846f04a18ad9df66ea60 100644 --- a/scp/faq.php +++ b/scp/faq.php @@ -140,17 +140,16 @@ else { } } -$role = $thisstaff->getRole(); $inc='faq-categories.inc.php'; //FAQs landing page. if($faq) { $inc='faq-view.inc.php'; if ($_REQUEST['a']=='edit' - && $role->hasPerm(FAQ::PERM_MANAGE)) + && $thisstaff->hasPerm(FAQ::PERM_MANAGE)) $inc='faq.inc.php'; elseif ($_REQUEST['a'] == 'print') return $faq->printPdf(); }elseif($_REQUEST['a']=='add' - && $role->hasPerm(FAQ::PERM_MANAGE)) { + && $thisstaff->hasPerm(FAQ::PERM_MANAGE)) { $inc='faq.inc.php'; } elseif($category && $_REQUEST['a']!='search') { $inc='faq-category.inc.php'; diff --git a/scp/groups.php b/scp/groups.php deleted file mode 100644 index 494824bb108c2c83b5477ab56ed95d073f772361..0000000000000000000000000000000000000000 --- a/scp/groups.php +++ /dev/null @@ -1,142 +0,0 @@ -<?php -/********************************************************************* - groups.php - - User Groups. - - Peter Rotich <peter@osticket.com> - Copyright (c) 2006-2013 osTicket - http://www.osticket.com - - Released under the GNU General Public License WITHOUT ANY WARRANTY. - See LICENSE.TXT for details. - - vim: expandtab sw=4 ts=4 sts=4: -**********************************************************************/ -require('admin.inc.php'); - -$group=null; -if($_REQUEST['id'] && !($group=Group::lookup($_REQUEST['id']))) - $errors['err']=sprintf(__('%s: Unknown or invalid ID.'), __('group')); - -if($_POST){ - switch(strtolower($_POST['do'])){ - case 'update': - if (!$group) { - $errors['err']=sprintf(__('%s: Unknown or invalid'), __('group')); - } 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']) { - $errors['err']=sprintf(__('Unable to update %s. Correct error(s) below and try again!'), - __('this group')); - } - break; - 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']) { - $errors['err']=sprintf(__('Unable to add %s. Correct error(s) below and try again.'), - __('this group')); - } - break; - case 'mass_process': - $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']) - && 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($action) { - case 'enable': - $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)); - else - $warn = sprintf(__('%1$d of %2$d %3$s activated'), $num, $count, - _N('selected group', 'selected groups', $count)); - } else { - $errors['err'] = sprintf(__('Unable to activate %s'), - _N('selected group', 'selected groups', $count)); - } - break; - case 'disable': - $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)); - else - $warn = sprintf(__('%1$d of %2$d %3$s disabled'), $num, $count, - _N('selected group', 'selected groups', $count)); - } else { - $errors['err'] = sprintf(__('Unable to disable %s'), - _N('selected group', 'selected groups', $count)); - } - break; - case 'delete': - foreach($_POST['ids'] as $k=>$v) { - if(($g=Group::lookup($v)) && $g->delete()) - $i++; - } - - if($i && $i==$count) - $msg = sprintf(__('Successfully deleted %s'), - _N('selected group', 'selected groups', $count)); - elseif($i>0) - $warn = sprintf(__('%1$d of %2$d %3$s deleted'), $i, $count, - _N('selected group', 'selected groups', $count)); - elseif(!$errors['err']) - $errors['err'] = sprintf(__('Unable to delete %s'), - _N('selected group', 'selected groups', $count)); - break; - default: - $errors['err'] = __('Unknown action - get technical help.'); - } - } - break; - default: - $errors['err']=__('Unknown action'); - break; - } -} - -$page='groups.inc.php'; -$tip_namespace = 'staff.groups'; -if($group || ($_REQUEST['a'] && !strcasecmp($_REQUEST['a'],'add'))) { - $page='group.inc.php'; -} - -$nav->setTabActive('staff'); -$ost->addExtraHeader('<meta name="tip-namespace" content="' . $tip_namespace . '" />', - "$('#content').data('tipNamespace', '".$tip_namespace."');"); -require(STAFFINC_DIR.'header.inc.php'); -require(STAFFINC_DIR.$page); -include(STAFFINC_DIR.'footer.inc.php'); -?> diff --git a/scp/js/bootstrap-typeahead.js b/scp/js/bootstrap-typeahead.js index 7ddc01f61c56312e657d858e286c0b3c5a03401a..d53630e4bcaa48f6295c813bb0d6d6bba36131cf 100644 --- a/scp/js/bootstrap-typeahead.js +++ b/scp/js/bootstrap-typeahead.js @@ -34,6 +34,7 @@ this.onselect = this.options.onselect this.strings = true this.shown = false + this.deferred = null this.listen() } @@ -78,6 +79,11 @@ return this } + , fetch: function() { + var value = this.source(this, this.query) + if (value) this.process(value) + } + , lookup: function (event) { var that = this , items @@ -87,8 +93,9 @@ this.query = this.$element.val(); /*Check if we have a match on the current source?? */ if (typeof this.source == "function") { - value = this.source(this, this.query) - if (value) this.process(value) + if (!this.options.delay) return this.fetch() + if (this.deferred) clearTimeout(this.deferred) + this.deferred = setTimeout(this.fetch.bind(this), this.options.delay) } else { this.process(this.source) } @@ -337,6 +344,7 @@ , render: 'info' , minLength: 1 , scroll: false + , delay: 200 } $.fn.typeahead.Constructor = Typeahead diff --git a/scp/js/jquery.dropdown.js b/scp/js/jquery.dropdown.js index b516ea9d97d1f610a0acc7bdbda2c90a47c87660..84a843dfd0e120660bfa1a3376a94d1c5ebb46f0 100644 --- a/scp/js/jquery.dropdown.js +++ b/scp/js/jquery.dropdown.js @@ -55,7 +55,7 @@ if(jQuery) (function($) { dropdown.css({ left: -offset.left + (dropdown.hasClass('anchor-right') ? - trigger.offset().left - (dropdown.outerWidth() - trigger.outerWidth() - 7) : trigger.offset().left), + trigger.offset().left - (dropdown.outerWidth() - trigger.outerWidth() - 4) : trigger.offset().left), top: -offset.top + trigger.offset().top + trigger.outerHeight() }).show(); trigger.addClass('dropdown-open'); diff --git a/scp/js/jquery.translatable.js b/scp/js/jquery.translatable.js index 08f754a1a5e85c0d50f9759edb458b1a2e7e8a7e..64331b166423735fa904d77ff6a0f374e36688e9 100644 --- a/scp/js/jquery.translatable.js +++ b/scp/js/jquery.translatable.js @@ -36,7 +36,7 @@ decorate: function() { this.$translations = $('<ul class="translations"></ul>'); - this.$status = $('<li class="status"><i class="icon-spinner icon-spin"></i> Loading ...</li>') + this.$status = $('<li class="status"><i class="icon-spinner icon-spin"></i> '+__('Loading')+' ...</li>') .appendTo(this.$translations); this.$footer = $('<div class="add-translation"></div>'); this.$select = $('<select name="locale"></select>'); @@ -44,8 +44,10 @@ this.$container = $('<div class="translatable"></div>') .prependTo(this.$element.parent()) .append(this.$element); - this.$container.wrap('<div style="display:inline-block;position:relative;width:auto"></div>'); - this.$button = $(this.options.button).insertAfter(this.$container); + if (this.$element.width() > 100) + this.$element.width(this.$element.width()-35); + this.$container.wrap('<div style="display:inline-block;position:relative;width:auto;white-space:nowrap;"></div>'); + this.$button = $(this.options.button).appendTo(this.$container); this.$menu.append($('<span class="close"><i class="icon-remove"></i></span>') .on('click', $.proxy(this.hide, this))); if (this.$element.is('textarea')) { @@ -72,7 +74,7 @@ this.$footer .append($('<form method="post"></form>') .append(this.$select) - .append($('<button type="button"><i class="icon-plus-sign"></i> Add</button>') + .append($('<button type="button"><i class="icon-plus-sign"></i> '+__('Add')+'</button>') .on('click', $.proxy(this.define, this)) ) ); @@ -94,7 +96,7 @@ self.add(k, v); }); if (!Object.keys(json).length) { - self.$status.text('Not currently translated'); + self.$status.text(__('Not currently translated')); } else self.$status.remove(); @@ -131,7 +133,7 @@ showCommit: function(e) { if (this.$commit) { - this.$commit.find('button').empty().text(' Save') + this.$commit.find('button').empty().text(' '+__('Save')) .prepend($('<i>').addClass('fa icon-save')); return !this.$commit.is(':visible') ? this.$commit.slideDown() : true; @@ -139,7 +141,7 @@ return this.$commit = $('<div class="language-commit"></div>') .hide() .insertAfter(this.$translations) - .append($('<button type="button" class="commit"><i class="fa fa-save icon-save"></i> Save</button>') + .append($('<button type="button" class="white button commit"><i class="fa fa-save icon-save"></i> '+__('Save')+'</button>') .on('click', $.proxy(this.commit, this)) ) .slideDown(); @@ -154,7 +156,7 @@ changes[$(this).data('lang')] = trans; }); this.$commit.prop('disabled', true); - this.$commit.find('button').empty().text(' Saving') + this.$commit.find('button').empty().text(' '+__('Saving')) .prepend($('<i>').addClass('fa icon-spin icon-spinner')); $.ajax('ajax.php/i18n/translate/' + this.$element.data('translateTag'), { type: 'post', diff --git a/scp/js/scp.js b/scp/js/scp.js index dfa6ed4e08beb7a497e2ab57710186153d3b449e..fee30477431bd42982dc70250182d34af6c8f5bb 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -84,14 +84,18 @@ var scp_prep = function() { return false; }); - $('#actions :submit.button:not(.no-confirm)').bind('click', function(e) { - - var formObj = $(this).closest('form'); - e.preventDefault(); - if($('.dialog#confirm-action p#'+this.name+'-confirm').length == 0) { - alert('Unknown action '+this.name+' - get technical help.'); + $('#actions :submit.button:not(.no-confirm), #actions .confirm').bind('click', function(e) { + + var formObj, + name = this.name || $(this).data('name'); + if ($(this).data('formId')) + formObj = $('#' + $(this).data('formId')); + else + formObj = $(this).closest('form'); + if($('.dialog#confirm-action p#'+name+'-confirm').length === 0) { + alert('Unknown action '+name+' - get technical help.'); } else if(checkbox_checker(formObj, 1)) { - var action = this.name; + var action = name; $('.dialog#confirm-action').undelegate('.confirm'); $('.dialog#confirm-action').delegate('input.confirm', 'click.confirm', function(e) { e.preventDefault(); @@ -103,11 +107,11 @@ var scp_prep = function() { }); $.toggleOverlay(true); $('.dialog#confirm-action .confirm-action').hide(); - $('.dialog#confirm-action p#'+this.name+'-confirm') + $('.dialog#confirm-action p#'+name+'-confirm') .show() .parent('div').show().trigger('click'); } - + e.preventDefault(); return false; }); @@ -132,7 +136,7 @@ var scp_prep = function() { var fObj = el.closest('form'); if(!fObj.data('changed')){ fObj.data('changed', true); - $('input[type=submit]', fObj).css('color', 'red'); + $('input[type=submit]', fObj).addClass('save pending'); $(window).bind('beforeunload', function(e) { return __('Are you sure you want to leave? Any changes or info you\'ve entered will be discarded!'); }); @@ -149,7 +153,7 @@ var scp_prep = function() { $("form#save :input[type=reset]").click(function() { var fObj = $(this).closest('form'); if(fObj.data('changed')){ - $('input[type=submit]', fObj).removeAttr('style'); + $('input[type=submit]', fObj).removeClass('save pending'); $('label', fObj).removeAttr('style'); $('label', fObj).removeClass('strike'); fObj.data('changed', false); @@ -347,72 +351,6 @@ var scp_prep = function() { left : ($(window).width() - $("#loading").outerWidth()) / 2 }); - $('#advanced-search').delegate('#statusId, #flag', 'change', function() { - switch($(this).children('option:selected').data('state')) { - case 'closed': - $('select#assignee') - .attr('disabled','disabled') - .find('option:first') - .attr('selected', 'selected'); - $('select#flag') - .attr('disabled','disabled') - .find('option:first') - .attr('selected', 'selected'); - $('select#staffId').removeAttr('disabled'); - break; - case 'open': - $('select#staffId') - .attr('disabled','disabled') - .find('option:first') - .attr('selected', 'selected'); - $('select#assignee').removeAttr('disabled'); - $('select#flag').removeAttr('disabled'); - break; - default: - $('select#staffId').removeAttr('disabled'); - $('select#assignee').removeAttr('disabled'); - $('select#flag').removeAttr('disabled'); - } - }); - - $('#advanced-search form#search').submit(function(e) { - e.preventDefault(); - var fObj = $(this); - var elem = $('#advanced-search'); - $('#result-count').html(''); - fixupDatePickers.call(this); - $.ajax({ - url: "ajax.php/tickets/search", - data: fObj.serialize(), - dataType: 'json', - beforeSend: function ( xhr ) { - $('.buttons', elem).hide(); - $('.spinner', elem).show(); - return true; - }, - success: function (resp) { - - if(resp.success) { - $('#result-count').html('<div class="success">' + resp.success +'</div>'); - } else if (resp.fail) { - $('#result-count').html('<div class="fail">' + resp.fail +'</div>'); - } else { - $('#result-count').html('<div class="fail">Unknown error</div>'); - } - } - }) - .done( function () { - }) - .fail( function () { - $('#result-count').html('<div class="fail">' - + __('Advanced search failed - try again!') + '</div>'); - }) - .always( function () { - $('.spinner', elem).hide(); - $('.buttons', elem).show(); - }); - }); - // Return a helper with preserved width of cells var fixHelper = function(e, ui) { ui.children().each(function() { @@ -516,6 +454,11 @@ var scp_prep = function() { }); }); + $('div.tab_content[id] div.error:not(:empty)').each(function() { + var div = $(this).closest('.tab_content'); + $('a[href^=#'+div.attr('id')+']').parent().addClass('error'); + }); + $('[data-toggle="tooltip"]').tooltip() }; @@ -657,7 +600,7 @@ $.dialog = function (url, codes, cb, options) { $('div#popup-loading', $popup).show() .find('h1').css({'margin-top':function() { return $popup.height()/3-$(this).height()/3}}); $popup.resize().show(); - $('div.body', $popup).load(url, function () { + $('div.body', $popup).load(url, options.data, function () { $('div#popup-loading', $popup).hide(); $('div.body', $popup).slideDown({ duration: 300, @@ -720,6 +663,16 @@ $.dialog = function (url, codes, cb, options) { }); if (options.onload) { options.onload(); } }; +$(document).on('click', 'a[data-dialog]', function(event) { + event.preventDefault(); + event.stopImmediatePropagation(); + var link = $(this); + $.dialog($(this).data('dialog'), 201, function() { + if (link.attr('href').length > 1) $.pjax.click(event, '#pjax-container'); + else $.pjax.reload('#pjax-container'); + }); + return false; +}); $.sysAlert = function (title, msg, cb) { var $dialog = $('.dialog#alert'); @@ -827,7 +780,9 @@ $(document).on('click.tab', 'ul.tabs li a', function(e) { } else { $tab.addClass('tab_content'); - $.changeHash($(this).attr('href'), true); + // Don't change the URL hash for nested tabs or in dialogs + if ($(this).closest('.tab_content, .dialog').length == 0) + $.changeHash($(this).attr('href'), true); } if ($tab.length) { @@ -956,6 +911,29 @@ if ($.support.pjax) { }) } +// Quick-Add dialogs +$(document).on('change', 'select[data-quick-add]', function() { + var $select = $(this), + selected = $select.find('option:selected'), + type = selected.parent().closest('[data-quick-add]').data('quickAdd'); + if (!type || (selected.data('quickAdd') === undefined && selected.val() !== ':new:')) + return; + $.dialog('ajax.php/admin/quick-add/' + type, 201, + function(xhr, data) { + data = JSON.parse(data); + if (data && data.id && data.name) { + var id = data.id; + if (selected.data('idPrefix')) + id = selected.data('idPrefix') + id; + $('<option>') + .attr('value', id) + .text(data.name) + .insertBefore($('select[data-quick-add="'+type+'"] option[data-quick-add]')); + $select.val(id); + } + }); +}); + // Quick note interface $(document).on('click.note', '.quicknote .action.edit-note', function() { var note = $(this).closest('.quicknote'), diff --git a/scp/profile.php b/scp/profile.php index 0ceaf50651e6dc18d2693cc8e7149bd7c337f2d7..f787324a4b2f7a34a59eccaa3cba728db780a6d0 100644 --- a/scp/profile.php +++ b/scp/profile.php @@ -25,7 +25,7 @@ if($_POST && $_POST['id']!=$thisstaff->getId()) { //Check dummy ID used on the f if(!$staff) $errors['err']=sprintf(__('%s: Unknown or invalid'), __('agent')); - elseif($thisstaff->updateProfile($_POST,$errors)){ + elseif($staff->updateProfile($_POST,$errors)){ $msg=__('Profile updated successfully'); }elseif(!$errors['err']) $errors['err']=__('Profile update error. Try correcting the errors below and try again!'); @@ -33,7 +33,14 @@ if($_POST && $_POST['id']!=$thisstaff->getId()) { //Check dummy ID used on the f //Forced password Change. if($thisstaff->forcePasswdChange() && !$errors['err']) - $errors['err']=sprintf(__('<b>Hi %s</b> - You must change your password to continue!'),$thisstaff->getFirstName()); + $errors['err'] = str_replace( + '<a>', + sprintf('<a data-dialog="ajax.php/staff/%d/change-password" href="#">', $thisstaff->getId()), + sprintf( + __('<b>Hi %s</b> - You must <a>change your password to continue</a>!'), + $thisstaff->getFirstName() + ) + ); elseif($thisstaff->onVacation() && !$warn) $warn=sprintf(__("<b>Welcome back %s</b>! You are listed as 'on vacation' Please let your manager know that you are back."),$thisstaff->getFirstName()); diff --git a/scp/slas.php b/scp/slas.php index f57ba70e7266952253b442695cb56dc6c8e128aa..62ba66c9294424b7ee3c0d6be37d38c5121bd98b 100644 --- a/scp/slas.php +++ b/scp/slas.php @@ -57,7 +57,8 @@ if($_POST){ $num = SLA::objects()->filter(array( 'id__in' => $_POST['ids'] ))->update(array( - 'isactive' => 1 + 'flags' => SqlExpression::bitor( + new SqlField('flags'), SLA::FLAG_ACTIVE) )); if ($num) { if($num==$count) @@ -75,7 +76,8 @@ if($_POST){ $num = SLA::objects()->filter(array( 'id__in' => $_POST['ids'] ))->update(array( - 'isactive' => 0 + 'flags' => SqlExpression::bitand( + new SqlField('flags'), ~SLA::FLAG_ACTIVE) )); if ($num) { diff --git a/scp/staff.inc.php b/scp/staff.inc.php index e17314959df935b19883410b89a1a64353e44d65..edbc813006964649e43d1ed6e40cc03282b9fda0 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -35,7 +35,6 @@ define('KB_PREMADE_TABLE',TABLE_PREFIX.'kb_premade'); /* include what is needed on staff control panel */ require_once(INCLUDE_DIR.'class.staff.php'); -require_once(INCLUDE_DIR.'class.group.php'); require_once(INCLUDE_DIR.'class.csrf.php'); /* First order of the day is see if the user is logged in and with a valid session. diff --git a/scp/staff.php b/scp/staff.php index 17a106e93c41cbe16da59755ffb0b7fdefd9563b..81ac0e0ee54f93345cb3bbec7d6a02e71c656698 100644 --- a/scp/staff.php +++ b/scp/staff.php @@ -34,7 +34,14 @@ if($_POST){ break; case 'create': $staff = Staff::create(); + // Unpack the data from the set-password dialog (if used) + if (isset($_SESSION['new-agent-passwd'])) { + foreach ($_SESSION['new-agent-passwd'] as $k=>$v) + if (!isset($_POST[$k])) + $_POST[$k] = $v; + } if ($staff->update($_POST,$errors)) { + unset($_SESSION['new-agent-passwd']); $msg=sprintf(__('Successfully added %s'),Format::htmlchars($_POST['firstname'])); $_REQUEST['a']=null; }elseif(!$errors['err']){ @@ -46,18 +53,18 @@ if($_POST){ if(!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { $errors['err'] = sprintf(__('You must select at least %s.'), __('one agent')); - } elseif(in_array($thisstaff->getId(),$_POST['ids'])) { + } elseif(in_array($_POST['a'], array('disable', 'delete')) + && in_array($thisstaff->getId(),$_POST['ids']) + ) { $errors['err'] = __('You can not disable/delete yourself - you could be the only admin!'); } else { - $count=count($_POST['ids']); + $count = count($_POST['ids']); + $members = Staff::objects()->filter(array( + 'staff_id__in' => $_POST['ids'] + )); switch(strtolower($_POST['a'])) { case 'enable': - $num = Staff::objects()->filter(array( - 'staff_id__in' => $_POST['ids'] - ))->update(array( - 'isactive' => 1 - )); - + $num = $members->update(array('isactive' => 1)); if ($num) { if($num==$count) $msg = sprintf('Successfully activated %s', @@ -70,13 +77,9 @@ if($_POST){ _N('selected agent', 'selected agents', $count)); } break; - case 'disable': - $num = Staff::objects()->filter(array( - 'staff_id__in' => $_POST['ids'] - ))->update(array( - 'isactive' => 0 - )); + case 'disable': + $num = $members->update(array('isactive' => 0)); if ($num) { if($num==$count) $msg = sprintf('Successfully disabled %s', @@ -89,10 +92,11 @@ if($_POST){ _N('selected agent', 'selected agents', $count)); } break; + case 'delete': $i = 0; - foreach($_POST['ids'] as $k=>$v) { - if($v!=$thisstaff->getId() && ($s=Staff::lookup($v)) && $s->delete()) + foreach($members as $s) { + if ($s->staff_id != $thisstaff->getId() && $s->delete()) $i++; } @@ -106,6 +110,48 @@ if($_POST){ $errors['err'] = sprintf(__('Unable to delete %s'), _N('selected agent', 'selected agents', $count)); break; + + case 'permissions': + foreach ($members as $s) + if ($s->updatePerms($_POST['perms'], $errors) && $s->save()) + $i++; + + if($i && $i==$count) + $msg = sprintf(__('Successfully updated %s'), + _N('selected agent', 'selected agents', $count)); + elseif($i>0) + $warn = sprintf(__('%1$d of %2$d %3$s updated'), $i, $count, + _N('selected agent', 'selected agents', $count)); + elseif(!$errors['err']) + $errors['err'] = sprintf(__('Unable to update %s'), + _N('selected agent', 'selected agents', $count)); + break; + + case 'department': + if (!$_POST['dept_id'] || !$_POST['role_id'] + || !Dept::lookup($_POST['dept_id']) + || !Role::lookup($_POST['role_id']) + ) { + $errors['err'] = 'Internal error.'; + break; + } + foreach ($members as $s) { + $s->setDepartmentId((int) $_POST['dept_id'], $_POST['eavesdrop']); + $s->role_id = (int) $_POST['role_id']; + if ($s->save() && $s->dept_access->saveAll()) + $i++; + } + if($i && $i==$count) + $msg = sprintf(__('Successfully updated %s'), + _N('selected agent', 'selected agents', $count)); + elseif($i>0) + $warn = sprintf(__('%1$d of %2$d %3$s updated'), $i, $count, + _N('selected agent', 'selected agents', $count)); + elseif(!$errors['err']) + $errors['err'] = sprintf(__('Unable to update %s'), + _N('selected agent', 'selected agents', $count)); + break; + default: $errors['err'] = __('Unknown action - get technical help.'); } diff --git a/scp/teams.php b/scp/teams.php index 28e72adbf8e850a6c59a3f85b846514b2222f2d2..791181487cd471b8687b39b4a704a4bdd2e60630 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']){ diff --git a/scp/tickets.php b/scp/tickets.php index 8e1d45185b6eace1655b12fe900be0a825cd95ad..f3bbe8d7c00f6409974251bf5326aa7c9ec670ff 100644 --- a/scp/tickets.php +++ b/scp/tickets.php @@ -318,7 +318,7 @@ if($_POST && !$errors): } break; case 'banemail': - if (!$role->hasPerm(Email::PERM_BANLIST)) { + if (!$thisstaff->hasPerm(Email::PERM_BANLIST)) { $errors['err']=__('Permission Denied. You are not allowed to ban emails'); } elseif(BanList::includes($ticket->getEmail())) { $errors['err']=__('Email already in banlist'); @@ -329,7 +329,7 @@ if($_POST && !$errors): } break; case 'unbanemail': - if (!$role->hasPerm(Email::PERM_BANLIST)) { + if (!$thisstaff->hasPerm(Email::PERM_BANLIST)) { $errors['err'] = __('Permission Denied. You are not allowed to remove emails from banlist.'); } elseif(Banlist::remove($ticket->getEmail())) { $msg = __('Email removed from banlist'); @@ -364,7 +364,7 @@ if($_POST && !$errors): case 'open': $ticket=null; if (!$thisstaff || - !$thisstaff->hasPerm(TicketModel::PERM_CREATE)) { + !$thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) { $errors['err'] = sprintf('%s %s', sprintf(__('You do not have permission %s.'), __('to create tickets')), @@ -483,7 +483,7 @@ if (isset($_SESSION['advsearch'])) { (!$_REQUEST['status'] || $_REQUEST['status']=='search')); } -if ($thisstaff->hasPerm(TicketModel::PERM_CREATE)) { +if ($thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) { $nav->addSubMenu(array('desc'=>__('New Ticket'), 'title'=> __('Open a New Ticket'), 'href'=>'tickets.php?a=open', @@ -513,7 +513,7 @@ if($ticket) { } else { $inc = 'tickets.inc.php'; if ($_REQUEST['a']=='open' && - $thisstaff->hasPerm(TicketModel::PERM_CREATE)) + $thisstaff->hasPerm(TicketModel::PERM_CREATE, false)) $inc = 'ticket-open.inc.php'; elseif($_REQUEST['a'] == 'export') { $ts = strftime('%Y%m%d'); diff --git a/scp/users.php b/scp/users.php index 4506941a08a777cbd8e6961045159197e0db99ec..8e88d72a1f00e3debded552b1bb3b98918bffd7b 100644 --- a/scp/users.php +++ b/scp/users.php @@ -14,7 +14,7 @@ **********************************************************************/ require('staff.inc.php'); -if (!$thisstaff->getRole()->hasPerm(User::PERM_DIRECTORY)) +if (!$thisstaff->hasPerm(User::PERM_DIRECTORY)) Http::redirect('index.php'); require_once INCLUDE_DIR.'class.note.php'; @@ -28,7 +28,7 @@ if ($_POST) { case 'update': if (!$user) { $errors['err']=sprintf(__('%s: Unknown or invalid'), _N('end user', 'end users', 1)); - } elseif (!$thisstaff->getRole()->hasPerm(User::PERM_EDIT)) { + } elseif (!$thisstaff->hasPerm(User::PERM_EDIT)) { $errors['err'] = __('Action denied. Contact admin for access'); } elseif(($acct = $user->getAccount()) && !$acct->update($_POST, $errors)) { diff --git a/setup/inc/class.installer.php b/setup/inc/class.installer.php index c10b1ed9eedb718e63a05c58946b4c08bf45d285..3986191a94c517f3cbdb39be38b00cc4081bf1cf 100644 --- a/setup/inc/class.installer.php +++ b/setup/inc/class.installer.php @@ -53,8 +53,8 @@ class Installer extends SetupWizard { $f['lname'] = array('type'=>'string', 'required'=>1, 'error'=>__('Last name required')); $f['admin_email'] = array('type'=>'email', 'required'=>1, 'error'=>__('Valid email required')); $f['username'] = array('type'=>'username', 'required'=>1, 'error'=>__('Username required')); - $f['passwd'] = array('type'=>'password', 'required'=>1, 'error'=>__('Password required')); - $f['passwd2'] = array('type'=>'password', 'required'=>1, 'error'=>__('Confirm Password')); + $f['passwd'] = array('type'=>'string', 'required'=>1, 'error'=>__('Password required')); + $f['passwd2'] = array('type'=>'string', 'required'=>1, 'error'=>__('Confirm Password')); $f['prefix'] = array('type'=>'string', 'required'=>1, 'error'=>__('Table prefix required')); $f['dbhost'] = array('type'=>'string', 'required'=>1, 'error'=>__('Host name required')); $f['dbname'] = array('type'=>'string', 'required'=>1, 'error'=>__('Database name required')); @@ -73,6 +73,14 @@ class Installer extends SetupWizard { //Admin's pass confirmation. if(!$this->errors && strcasecmp($vars['passwd'],$vars['passwd2'])) $this->errors['passwd2']=__('Password(s) do not match'); + try { + require_once INCLUDE_DIR.'class.auth.php'; + PasswordPolicy::checkPassword($vars['passwd'], null); + } + catch (BadPassword $e) { + $this->errors['passwd'] = $e->getMessage(); + } + //Check table prefix underscore required at the end! if($vars['prefix'] && substr($vars['prefix'], -1)!='_') $this->errors['prefix']=__('Bad prefix. Must have underscore (_) at the end. e.g \'ost_\''); @@ -110,8 +118,9 @@ class Installer extends SetupWizard { } } - //bailout on errors. - if($this->errors) return false; + // bailout on errors. + if ($this->errors) + return false; /*************** We're ready to install ************************/ define('ADMIN_EMAIL',$vars['admin_email']); //Needed to report SQL errors during install. @@ -151,111 +160,125 @@ class Installer extends SetupWizard { } } - if(!$this->errors) { - - // TODO: Use language selected from install worksheet - $i18n = new Internationalization($vars['lang_id']); - $i18n->loadDefaultData(); - - Signal::send('system.install', $this); - - $sql='SELECT `id` FROM `'.TABLE_PREFIX.'sla` ORDER BY `id` LIMIT 1'; - $sla_id_1 = db_result(db_query($sql, false)); - - $sql='SELECT `id` FROM `'.TABLE_PREFIX.'department` ORDER BY `id` LIMIT 1'; - $dept_id_1 = db_result(db_query($sql, false)); - - $sql='SELECT `tpl_id` FROM `'.TABLE_PREFIX.'email_template_group` ORDER BY `tpl_id` LIMIT 1'; - $template_id_1 = db_result(db_query($sql, false)); - - $sql='SELECT `id` FROM `'.TABLE_PREFIX.'group` ORDER BY `id` LIMIT 1'; - $group_id_1 = db_result(db_query($sql, false)); - - $sql='SELECT `id` FROM `'.TABLE_PREFIX.'role` ORDER BY `id` LIMIT 1'; - $role_id_1 = db_result(db_query($sql, false)); + if ($this->errors) + return false; - //Create admin user. - $sql='INSERT INTO '.TABLE_PREFIX.'staff SET created=NOW() ' - .', isactive=1, isadmin=1, max_page_size=25 ' - .', group_id='.db_input($group_id_1) - .', dept_id='.db_input($dept_id_1) - .', role_id='.db_input($role_id_1) - .', email='.db_input($vars['admin_email']) - .', firstname='.db_input($vars['fname']) - .', lastname='.db_input($vars['lname']) - .', username='.db_input($vars['username']) - .', passwd='.db_input(Passwd::hash($vars['passwd'])); - if(!db_query($sql, false) || !($uid=db_insert_id())) - $this->errors['err']=__('Unable to create admin user (#6)'); + // TODO: Use language selected from install worksheet + $i18n = new Internationalization($vars['lang_id']); + $i18n->loadDefaultData(); + + Signal::send('system.install', $this); + + list($sla_id) = Sla::objects()->order_by('id')->values_flat('id')->first(); + list($dept_id) = Dept::objects()->order_by('id')->values_flat('id')->first(); + list($role_id) = Role::objects()->order_by('id')->values_flat('id')->first(); + + $sql='SELECT `tpl_id` FROM `'.TABLE_PREFIX.'email_template_group` ORDER BY `tpl_id` LIMIT 1'; + $template_id_1 = db_result(db_query($sql, false)); + + // Create admin user. + $staff = Staff::create(array( + 'isactive' => 1, + 'isadmin' => 1, + 'max_page_size' => 25, + 'dept_id' => $dept_id, + 'role_id' => $role_id, + 'email' => $vars['admin_email'], + 'firstname' => $vars['fname'], + 'lastname' => $vars['lname'], + 'username' => $vars['username'], + )); + $staff->updatePerms(array( + User::PERM_CREATE, + User::PERM_EDIT, + User::PERM_DELETE, + User::PERM_MANAGE, + User::PERM_DIRECTORY, + Organization::PERM_CREATE, + Organization::PERM_EDIT, + Organization::PERM_DELETE, + FAQ::PERM_MANAGE, + Email::PERM_BANLIST, + ), $errors); + $staff->setPassword($vars['passwd']); + if (!$staff->save()) { + $this->errors['err'] = __('Unable to create admin user (#6)'); + return false; } - if(!$this->errors) { - //Create default emails! - $email = $vars['email']; - list(,$domain)=explode('@',$vars['email']); - $sql='INSERT INTO '.TABLE_PREFIX.'email (`name`,`email`,`created`,`updated`) VALUES ' - ." ('Support','$email',NOW(),NOW())" - .",('osTicket Alerts','alerts@$domain',NOW(),NOW())" - .",('','noreply@$domain',NOW(),NOW())"; - $support_email_id = db_query($sql, false) ? db_insert_id() : 0; - - - $sql='SELECT `email_id` FROM '.TABLE_PREFIX."email WHERE `email`='alerts@$domain' LIMIT 1"; - $alert_email_id = db_result(db_query($sql, false)); - - //Create config settings---default settings! - $defaults = array( - 'default_email_id'=>$support_email_id, - 'alert_email_id'=>$alert_email_id, - 'default_dept_id'=>$dept_id_1, 'default_sla_id'=>$sla_id_1, - 'default_template_id'=>$template_id_1, - 'default_timezone' => $vars['timezone'] ?: date_default_timezone_get(), - 'admin_email'=>$vars['admin_email'], - 'schema_signature'=>$streams['core'], - 'helpdesk_url'=>URL, - 'helpdesk_title'=>$vars['name']); - $config = new Config('core'); - if (!$config->updateAll($defaults)) - $this->errors['err']=__('Unable to create config settings').' (#7)'; - - // Set company name - require_once(INCLUDE_DIR.'class.company.php'); - $company = new Company(); - $company->getForm()->setAnswer('name', $vars['name']); - $company->getForm()->save(); - - foreach ($streams as $stream=>$signature) { - if ($stream != 'core') { - $config = new Config($stream); - if (!$config->update('schema_signature', $signature)) - $this->errors['err']=__('Unable to create config settings').' (#8)'; - } - } + // Create default emails! + $email = $vars['email']; + list(,$domain) = explode('@', $vars['email']); + foreach (array( + "Support" => $email, + "osTicket Alerts" => "alerts@$domain", + '' => "noreply@$domain", + ) as $name => $mailbox) { + $mb = Email::create(array( + 'name' => $name, + 'email' => $mailbox, + 'dept_id' => $dept_id, + )); + $mb->save(); + if ($mailbox == $email) + $support_email_id = $mb->email_id; + if ($mailbox == "alerts@$domain") + $alert_email_id = $mb->email_id; } - if($this->errors) return false; //Abort on internal errors. + //Create config settings---default settings! + $defaults = array( + 'default_email_id'=>$support_email_id, + 'alert_email_id'=>$alert_email_id, + 'default_dept_id'=>$dept_id, 'default_sla_id'=>$sla_id, + 'default_template_id'=>$template_id_1, + 'default_timezone' => $vars['timezone'] ?: date_default_timezone_get(), + 'admin_email'=>$vars['admin_email'], + 'schema_signature'=>$streams['core'], + 'helpdesk_url'=>URL, + 'helpdesk_title'=>$vars['name'] + ); + + $config = new Config('core'); + if (!$config->updateAll($defaults)) + $this->errors['err']=__('Unable to create config settings').' (#7)'; + + // Set company name + require_once(INCLUDE_DIR.'class.company.php'); + $company = new Company(); + $company->getForm()->setAnswer('name', $vars['name']); + $company->getForm()->save(); + + foreach ($streams as $stream => $signature) { + if ($stream != 'core') { + $config = new Config($stream); + if (!$config->update('schema_signature', $signature)) + $this->errors['err'] = __('Unable to create config settings').' (#8)'; + } + } + + if ($this->errors) + return false; //Abort on internal errors. //Rewrite the config file - MUST be done last to allow for installer recovery. - $configFile= str_replace("define('OSTINSTALLED',FALSE);","define('OSTINSTALLED',TRUE);",$configFile); - $configFile= str_replace('%ADMIN-EMAIL',$vars['admin_email'],$configFile); - $configFile= str_replace('%CONFIG-DBHOST',$vars['dbhost'],$configFile); - $configFile= str_replace('%CONFIG-DBNAME',$vars['dbname'],$configFile); - $configFile= str_replace('%CONFIG-DBUSER',$vars['dbuser'],$configFile); - $configFile= str_replace('%CONFIG-DBPASS',$vars['dbpass'],$configFile); - $configFile= str_replace('%CONFIG-PREFIX',$vars['prefix'],$configFile); - $configFile= str_replace('%CONFIG-SIRI',Misc::randCode(32),$configFile); - if(!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) { + $configFile = strtr($configFile, array( + "define('OSTINSTALLED',FALSE);" => "define('OSTINSTALLED',TRUE);", + '%ADMIN-EMAIL' => $vars['admin_email'], + '%CONFIG-DBHOST' => $vars['dbhost'], + '%CONFIG-DBNAME' => $vars['dbname'], + '%CONFIG-DBUSER' => $vars['dbuser'], + '%CONFIG-DBPASS' => $vars['dbpass'], + '%CONFIG-PREFIX' => $vars['prefix'], + '%CONFIG-SIRI' => Misc::randCode(32), + )); + if (!$fp || !ftruncate($fp,0) || !fwrite($fp,$configFile)) { $this->errors['err']=__('Unable to write to config file. Permission denied! (#5)'); return false; } @fclose($fp); /************* Make the system happy ***********************/ - - $sql='UPDATE '.TABLE_PREFIX."email SET dept_id=$dept_id_1"; - db_query($sql, false); - global $cfg; $cfg = new OsticketConfig(); @@ -266,9 +289,9 @@ class Installer extends SetupWizard { $ticket = Ticket::create($ticket_vars, $errors, 'api', false, false); if ($ticket - && ($org = Organization::objects()->order_by('id')->one())) { - - $user=User::lookup($ticket->getOwnerId()); + && ($org = Organization::objects()->order_by('id')->one()) + ) { + $user = User::lookup($ticket->getOwnerId()); $user->setOrganization($org); } diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index 368b1bf1cf5af4c948d43ac56e67b497299d9a35..105d0420a28ce9d3755328854fdbc2dc22e45c63 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -37,8 +37,6 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%faq` ( `answer` text NOT NULL, `keywords` tinytext, `notes` text, - `views` int(10) unsigned NOT NULL default '0', - `score` int(10) NOT NULL default '0', `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`faq_id`), @@ -418,16 +416,6 @@ CREATE TABLE `%TABLE_PREFIX%role` ( 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 `role_id` (`role_id`) -) DEFAULT CHARSET=utf8; - DROP TABLE IF EXISTS `%TABLE_PREFIX%help_topic`; CREATE TABLE `%TABLE_PREFIX%help_topic` ( `topic_id` int(11) unsigned NOT NULL auto_increment, @@ -533,7 +521,6 @@ CREATE TABLE `%TABLE_PREFIX%session` ( DROP TABLE IF EXISTS `%TABLE_PREFIX%staff`; CREATE TABLE `%TABLE_PREFIX%staff` ( `staff_id` int(11) unsigned NOT NULL auto_increment, - `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', `username` varchar(32) NOT NULL default '', @@ -562,6 +549,7 @@ CREATE TABLE `%TABLE_PREFIX%staff` ( `default_signature_type` ENUM( 'none', 'mine', 'dept' ) NOT NULL DEFAULT 'none', `default_paper_size` ENUM( 'Letter', 'Legal', 'Ledger', 'A4', 'A3' ) NOT NULL DEFAULT 'Letter', `extra` text, + `permissions` text, `created` datetime NOT NULL, `lastlogin` datetime default NULL, `passwdreset` datetime default NULL, @@ -569,8 +557,17 @@ CREATE TABLE `%TABLE_PREFIX%staff` ( PRIMARY KEY (`staff_id`), UNIQUE KEY `username` (`username`), KEY `dept_id` (`dept_id`), - KEY `issuperuser` (`isadmin`), - KEY `group_id` (`group_id`,`staff_id`) + KEY `issuperuser` (`isadmin`) +) DEFAULT CHARSET=utf8; + +DROP TABLE IF EXISTS `%TABLE_PREFIX%staff_dept_access`; +CREATE TABLE `%TABLE_PREFIX%staff_dept_access` ( + `staff_id` int(10) unsigned NOT NULL DEFAULT '0', + `dept_id` int(10) unsigned NOT NULL DEFAULT '0', + `role_id` int(10) unsigned NOT NULL DEFAULT '0', + `flags` int(10) unsigned NOT NULL DEFAULT '1', + PRIMARY KEY `staff_dept` (`staff_id`,`dept_id`), + KEY `dept_id` (`dept_id`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%syslog`; @@ -605,7 +602,7 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%team_member`; CREATE TABLE `%TABLE_PREFIX%team_member` ( `team_id` int(10) unsigned NOT NULL default '0', `staff_id` int(10) unsigned NOT NULL, - `updated` datetime NOT NULL, + `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`team_id`,`staff_id`) ) DEFAULT CHARSET=utf8; @@ -615,6 +612,8 @@ CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%thread` ( `object_id` int(11) unsigned NOT NULL, `object_type` char(1) NOT NULL, `extra` text, + `lastresponse` datetime DEFAULT NULL, + `lastmessage` datetime DEFAULT NULL, `created` datetime NOT NULL, PRIMARY KEY (`id`), KEY `object_id` (`object_id`), @@ -682,8 +681,6 @@ CREATE TABLE `%TABLE_PREFIX%ticket` ( `est_duedate` datetime default NULL, `reopened` datetime default NULL, `closed` datetime default NULL, - `lastmessage` datetime default NULL, - `lastresponse` datetime default NULL, `lastupdate` datetime default NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, @@ -772,7 +769,8 @@ CREATE TABLE `%TABLE_PREFIX%thread_collaborator` ( `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`), - UNIQUE KEY `collab` (`thread_id`,`user_id`) + UNIQUE KEY `collab` (`thread_id`,`user_id`), + KEY `user_id` (`user_id`) ) DEFAULT CHARSET=utf8; DROP TABLE IF EXISTS `%TABLE_PREFIX%task`; @@ -787,6 +785,7 @@ CREATE TABLE `%TABLE_PREFIX%task` ( `lock_id` int(11) unsigned NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', `duedate` datetime DEFAULT NULL, + `closed` datetime DEFAULT NULL, `created` datetime NOT NULL, `updated` datetime NOT NULL, PRIMARY KEY (`id`),