diff --git a/bootstrap.php b/bootstrap.php index 832fe96e50a6b89f9c7d952c5fa355295fcd8ca5..b8d9052f2475e58192b9f0e301dbe446e9c52cd0 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -72,6 +72,8 @@ class Bootstrap { define('USER_EMAIL_TABLE',$prefix.'user_email'); define('USER_ACCOUNT_TABLE',$prefix.'user_account'); + define('ORGANIZATION_TABLE', $prefix.'organization'); + define('STAFF_TABLE',$prefix.'staff'); define('TEAM_TABLE',$prefix.'team'); define('TEAM_MEMBER_TABLE',$prefix.'team_member'); @@ -174,6 +176,7 @@ class Bootstrap { function loadCode() { #include required files require(INCLUDE_DIR.'class.signal.php'); + require(INCLUDE_DIR.'class.user.php'); require(INCLUDE_DIR.'class.auth.php'); require(INCLUDE_DIR.'class.pagenate.php'); //Pagenate helper! require(INCLUDE_DIR.'class.log.php'); diff --git a/include/ajax.orgs.php b/include/ajax.orgs.php new file mode 100644 index 0000000000000000000000000000000000000000..def54cf3c549d98188a801a78199e52fd6b9e1ab --- /dev/null +++ b/include/ajax.orgs.php @@ -0,0 +1,154 @@ +<?php +/********************************************************************* + ajax.orgs.php + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2014 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +if(!defined('INCLUDE_DIR')) die('403'); + +include_once(INCLUDE_DIR.'class.ticket.php'); + +class OrgsAjaxAPI extends AjaxController { + + function search($type = null) { + + if(!isset($_REQUEST['q'])) { + Http::response(400, 'Query argument is required'); + } + + $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; + $orgs=array(); + + $escaped = db_input(strtolower($_REQUEST['q']), false); + $sql='SELECT DISTINCT org.id, org.name ' + .' FROM '.ORGANIZATION_TABLE.' org ' + .' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_type=\'O\' AND entry.object_id = org.id) + LEFT JOIN '.FORM_ANSWER_TABLE.' value ON (value.entry_id=entry.id) ' + .' WHERE org.name LIKE \'%'.$escaped.'%\' OR value.value LIKE \'%'.$escaped.'%\'' + .' ORDER BY org.created ' + .' LIMIT '.$limit; + + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id, $name)=db_fetch_row($res)) { + $orgs[] = array('name' => Format::htmlchars($name), 'info' => $name, + 'id' => $id, '/bin/true' => $_REQUEST['q']); + } + } + + return $this->json_encode(array_values($orgs)); + + } + + function editOrg($id) { + global $thisstaff; + + if(!$thisstaff) + Http::response(403, 'Login Required'); + elseif(!($org = Organization::lookup($id))) + Http::response(404, 'Unknown organization'); + + $info = array( + 'title' => sprintf('Update %s', $org->getName()) + ); + + $forms = $org->getForms(); + + include(STAFFINC_DIR . 'templates/org.tmpl.php'); + } + + function updateOrg($id) { + global $thisstaff; + + if(!$thisstaff) + Http::response(403, 'Login Required'); + elseif(!($org = Organization::lookup($id))) + Http::response(404, 'Unknown organization'); + + $errors = array(); + if($org->update($_POST, $errors)) + Http::response(201, $org->to_json()); + + $forms = $org->getForms(); + include(STAFFINC_DIR . 'templates/org.tmpl.php'); + } + + + function delete($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login Required'); + elseif (!($org = Organization::lookup($id))) + Http::response(404, 'Unknown organization'); + + $info = array(); + if ($_SERVER['REQUEST_METHOD'] == 'DELETE') { + if ($org->delete()) + Http::response(204, 'Organization deleted successfully'); + else + $info['error'] = 'Unable to delete organization - try again!'; + } + + include(STAFFINC_DIR . 'templates/org-delete.tmpl.php'); + } + + + function addOrg() { + + $info = array(); + + if ($_POST) { + $form = OrganizationForm::getDefaultForm()->getForm($_POST); + if (($org = Organization::fromForm($form))) + Http::response(201, $org->to_json()); + + $info = array('error' =>'Error adding organization - try again!'); + } + + $info['title'] = 'Add New Organization'; + $info['search'] = false; + + return self::_lookupform($form, $info); + } + + function lookup() { + return self::_lookupform(); + } + + function selectOrg($id) { + + if ($id) $org = Organization::lookup($id); + + $info = array('title' => 'Select Organization'); + + ob_start(); + include(STAFFINC_DIR . 'templates/org-lookup.tmpl.php'); + $resp = ob_get_contents(); + ob_end_clean(); + return $resp; + + } + + static function _lookupform($form=null, $info=array()) { + + if (!$info or !$info['title']) + $info += array('title' => 'Organization Lookup'); + + ob_start(); + include(STAFFINC_DIR . 'templates/org-lookup.tmpl.php'); + $resp = ob_get_contents(); + ob_end_clean(); + + return $resp; + } +} +?> diff --git a/include/ajax.users.php b/include/ajax.users.php index 2e5a83e32dfda68542fe3186ef66734a9c40b25b..05b4094e4fd0449bb2c144e686ffa9276799d425 100644 --- a/include/ajax.users.php +++ b/include/ajax.users.php @@ -22,7 +22,7 @@ include_once(INCLUDE_DIR.'class.ticket.php'); class UsersAjaxAPI extends AjaxController { /* Assumes search by emal for now */ - function search() { + function search($type = null) { if(!isset($_REQUEST['q'])) { Http::response(400, 'Query argument is required'); @@ -31,40 +31,46 @@ class UsersAjaxAPI extends AjaxController { $limit = isset($_REQUEST['limit']) ? (int) $_REQUEST['limit']:25; $users=array(); $emails=array(); - foreach (StaffAuthenticationBackend::searchUsers($_REQUEST['q']) as $u) { - $name = "{$u['first']} {$u['last']}"; - $users[] = array('email' => $u['email'], 'name'=>$name, - 'info' => "{$u['email']} - $name (remote)", - 'id' => "auth:".$u['id'], "/bin/true" => $_REQUEST['q']); - $emails[] = $u['email']; + + if (!$type || !strcasecmp($type, 'remote')) { + foreach (StaffAuthenticationBackend::searchUsers($_REQUEST['q']) as $u) { + $name = "{$u['first']} {$u['last']}"; + $users[] = array('email' => $u['email'], 'name'=>$name, + 'info' => "{$u['email']} - $name (remote)", + 'id' => "auth:".$u['id'], "/bin/true" => $_REQUEST['q']); + $emails[] = $u['email']; + } } - $remote_emails = ($emails = array_filter($emails)) - ? ' OR email.address IN ('.implode(',',db_input($emails)).') ' - : ''; - - $escaped = db_input(strtolower($_REQUEST['q']), false); - $sql='SELECT DISTINCT user.id, email.address, name ' - .' FROM '.USER_TABLE.' user ' - .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id ' - .' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_type=\'U\' AND entry.object_id = user.id) - LEFT JOIN '.FORM_ANSWER_TABLE.' value ON (value.entry_id=entry.id) ' - .' WHERE email.address LIKE \'%'.$escaped.'%\' - OR user.name LIKE \'%'.$escaped.'%\' - OR value.value LIKE \'%'.$escaped.'%\''.$remote_emails - .' ORDER BY user.created ' - .' LIMIT '.$limit; - - if(($res=db_query($sql)) && db_num_rows($res)){ - while(list($id,$email,$name)=db_fetch_row($res)) { - foreach ($users as $i=>$u) { - if ($u['email'] == $email) { - unset($users[$i]); - break; + + if (!$type || !strcasecmp($type, 'local')) { + $remote_emails = ($emails = array_filter($emails)) + ? ' OR email.address IN ('.implode(',',db_input($emails)).') ' + : ''; + + $escaped = db_input(strtolower($_REQUEST['q']), false); + $sql='SELECT DISTINCT user.id, email.address, name ' + .' FROM '.USER_TABLE.' user ' + .' JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id ' + .' LEFT JOIN '.FORM_ENTRY_TABLE.' entry ON (entry.object_type=\'U\' AND entry.object_id = user.id) + LEFT JOIN '.FORM_ANSWER_TABLE.' value ON (value.entry_id=entry.id) ' + .' WHERE email.address LIKE \'%'.$escaped.'%\' + OR user.name LIKE \'%'.$escaped.'%\' + OR value.value LIKE \'%'.$escaped.'%\''.$remote_emails + .' ORDER BY user.created ' + .' LIMIT '.$limit; + + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$email,$name)=db_fetch_row($res)) { + foreach ($users as $i=>$u) { + if ($u['email'] == $email) { + unset($users[$i]); + break; + } } + $name = Format::htmlchars($name); + $users[] = array('email'=>$email, 'name'=>$name, 'info'=>"$email - $name", + "id" => $id, "/bin/true" => $_REQUEST['q']); } - $name = Format::htmlchars($name); - $users[] = array('email'=>$email, 'name'=>$name, 'info'=>"$email - $name", - "id" => $id, "/bin/true" => $_REQUEST['q']); } } @@ -104,6 +110,91 @@ class UsersAjaxAPI extends AjaxController { include(STAFFINC_DIR . 'templates/user.tmpl.php'); } + function register($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login Required'); + elseif (!($user = User::lookup($id))) + Http::response(404, 'Unknown user'); + + $errors = $info = array(); + if ($_POST) { + // Register user on post + if ($user->getAccount()) + $info['error'] = 'User already registered'; + elseif ($user->register($_POST, $errors)) + Http::response(201, 'Account created successfully'); + + // Unable to create user. + $info = Format::htmlchars($_POST); + if ($errors['err']) + $info['error'] = $errors['err']; + else + $info['error'] = 'Unable to register user - try again!'; + } + + include(STAFFINC_DIR . 'templates/user-register.tmpl.php'); + } + + function manage($id, $target=null) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login Required'); + elseif (!($user = User::lookup($id))) + Http::response(404, 'Unknown user'); + + if (!($account = $user->getAccount())) + return self::register($id); + + $errors = array(); + $info = $account->getInfo(); + + if ($_POST) { + if ($account->update($_POST, $errors)) + Http::response(201, 'Account updated successfully'); + + // Unable to update account + $info = Format::htmlchars($_POST); + + if ($errors['err']) + $info['error'] = $errors['err']; + else + $info['error'] = 'Unable to update account - try again!'; + } + + $info['_target'] = $target; + + include(STAFFINC_DIR . 'templates/user-account.tmpl.php'); + } + + function delete($id) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login Required'); + elseif (!($user = User::lookup($id))) + Http::response(404, 'Unknown user'); + + //Switch to end user so we can get ticket stats + // fixme: use orm to get ticket count at the user model level. + $user = new EndUser($user); + + $info = array(); + if ($_SERVER['REQUEST_METHOD'] == 'DELETE') { + + if ($user->getNumTickets()) + $info['error'] = 'You cannot delete a user with tickets!'; + elseif ($user->delete()) + Http::response(204, 'User deleted successfully'); + else + $info['error'] = 'Unable to delete user - try again!'; + } + + include(STAFFINC_DIR . 'templates/user-delete.tmpl.php'); + } + function getUser($id=false) { if(($user=User::lookup(($id) ? $id : $_REQUEST['id']))) @@ -116,11 +207,19 @@ class UsersAjaxAPI extends AjaxController { function addUser() { - $form = UserForm::getUserForm()->getForm($_POST); - if (($user = User::fromForm($form))) - Http::response(201, $user->to_json()); + $info = array(); + + if ($_POST) { - $info = array('error' =>'Error adding user - try again!'); + $form = UserForm::getUserForm()->getForm($_POST); + if (($user = User::fromForm($form))) + Http::response(201, $user->to_json()); + + $info = array('error' =>'Error adding user - try again!'); + } else { + $info['lookuptype'] = remote; + $info['title'] = 'Add New User'; + } return self::_lookupform($form, $info); } @@ -196,5 +295,55 @@ class UsersAjaxAPI extends AjaxController { } return $this->json_encode($users); } + + function updateOrg($id, $orgId = 0) { + global $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Login Required'); + elseif (!($user = User::lookup($id)) + || !($account=$user->getAccount())) + Http::response(404, 'Unknown user account'); + + $info['title'] = 'Organization for '.$user->getName(); + $info['action'] = '#users/'.$user->getId().'/org'; + $info['onselect'] = 'ajax.php/users/'.$user->getId().'/org'; + + if ($_POST) { + if ($_POST['orgid']) { //Existing org. + if (!($org = Organization::lookup($_POST['orgid']))) + $info['error'] = 'Unknown organization selected'; + } else { //Creating new org. + $form = OrganizationForm::getDefaultForm()->getForm($_POST); + if (!($org = Organization::fromForm($form))) + $info['error'] = 'Unable to create organization - try again!'; + } + + if ($org && $account->setOrganization($org)) + Http::response(201, $org->to_json()); + + $info['error'] = 'Unable to user account - try again!'; + + } elseif ($orgId) + $org = Organization::lookup($orgId); + elseif ($org = $account->getOrganization()) { + $info['title'] = $org->getName(); + $info['action'] = $info['onselect'] = ''; + $tmpl = 'org.tmpl.php'; + } + + if ($org && $account->getOrgId() && $org->getId() != $account->getOrgId()) + $info['warning'] = 'Are you sure you want to change user\'s organization?'; + + $tmpl = $tmpl ?: 'org-lookup.tmpl.php'; + + ob_start(); + include(STAFFINC_DIR . "templates/$tmpl"); + $resp = ob_get_contents(); + ob_end_clean(); + + return $resp; + } + } ?> diff --git a/include/class.auth.php b/include/class.auth.php index dcde257c8ed86d3a83983fbfb4e7ff5f8bc71164..213730e67b06748a6057afb10e34393b18124a18 100644 --- a/include/class.auth.php +++ b/include/class.auth.php @@ -497,6 +497,11 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend { $user->refreshSession(true); //set the hash. + if (($acct = $user->getAccount()) && ($tid = $acct->get('timezone_id'))) { + $_SESSION['TZ_OFFSET'] = Timezone::getOffsetById($tid); + $_SESSION['TZ_DST'] = $acct->get('dst'); + } + //Log login info... $msg=sprintf('%s (%s) logged in [%s]', $user->getUserName(), $user->getId(), $_SERVER['REMOTE_ADDR']); diff --git a/include/class.client.php b/include/class.client.php index 7d1dfbcb7c93e6852070098c10774cee7f891dab..707418456e3e15b1ebefc53d2f10d0ba360b258d 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -269,43 +269,7 @@ class EndUser extends AuthenticatedUser { } } -require_once INCLUDE_DIR.'class.orm.php'; -class ClientAccountModel extends VerySimpleModel { - static $meta = array( - 'table' => USER_ACCOUNT_TABLE, - 'pk' => array('id'), - 'joins' => array( - 'user' => array( - 'null' => false, - 'constraint' => array('user_id' => 'UserModel.id') - ), - ), - ); -} - -class ClientAccount extends ClientAccountModel { - var $_options = null; - var $timezone; - - const CONFIRMED = 0x0001; - const LOCKED = 0x0002; - const PASSWD_RESET_REQUIRED = 0x0004; - - function __onload() { - if ($this->get('timezone_id')) { - $this->timezone = Timezone::getOffsetById($this->ht['timezone_id']); - $_SESSION['TZ_OFFSET'] = $this->timezone; - $_SESSION['TZ_DST'] = $this->get('dst'); - } - } - - function getId() { - return $this->get('id'); - } - - function getUserId() { - return $this->get('user_id'); - } +class ClientAccount extends UserAccount { function checkPassword($password, $autoupdate=true) { @@ -331,87 +295,6 @@ class ClientAccount extends ClientAccountModel { return $this->checkPassword($password, false); } - function hasPassword() { - return (bool) $this->get('passwd'); - } - - function sendResetEmail($template='pwreset-client') { - global $ost, $cfg; - - $token = Misc::randCode(48); // 290-bits - - $email = $cfg->getDefaultEmail(); - $content = Page::lookup(Page::getIdByType($template)); - - if (!$email || !$content) - return new Error('Unable to retrieve password reset email template'); - - $vars = array( - 'url' => $ost->getConfig()->getBaseUrl(), - 'token' => $token, - 'user' => $this->getUser(), - 'recipient' => $this->getUser(), - 'link' => sprintf( - "%s/pwreset.php?token=%s", - $ost->getConfig()->getBaseUrl(), - $token), - ); - $vars['reset_link'] = &$vars['link']; - - $info = array('email' => $email, 'vars' => &$vars, 'log'=>true); - Signal::send('auth.pwreset.email', $this, $info); - - $msg = $ost->replaceTemplateVariables(array( - 'subj' => $content->getName(), - 'body' => $content->getBody(), - ), $vars); - - $_config = new Config('pwreset'); - $_config->set($vars['token'], $this->user->getId()); - - $email->send($this->user->default_email->get('address'), - Format::striptags($msg['subj']), $msg['body']); - } - - function confirm() { - $this->_setStatus(self::CONFIRMED); - return $this->save(); - } - - function isConfirmed() { - return $this->_getStatus(self::CONFIRMED); - } - - function lock() { - $this->_setStatus(self::LOCKED); - $this->save(); - } - - function isLocked() { - return $this->_getStatus(self::LOCKED); - } - - function forcePasswdReset() { - $this->_setStatus(self::PASSWD_RESET_REQUIRED); - return $this->save(); - } - - function isPasswdResetForced() { - return $this->_getStatus(self::PASSWD_RESET_REQUIRED); - } - - function _getStatus($flag) { - return 0 !== ($this->get('status') & $flag); - } - - function _clearStatus($flag) { - return $this->set('status', $this->get('status') & ~$flag); - } - - function _setStatus($flag) { - return $this->set('status', $this->get('status') | $flag); - } - function cancelResetTokens() { // TODO: Drop password-reset tokens from the config table for // this user id @@ -424,17 +307,11 @@ class ClientAccount extends ClientAccountModel { } function getInfo() { - $base = $this->ht; + $base = parent::getInfo(); $base['tz_offset'] = $this->timezone; return $base; } - function getUser() { - $user = User::lookup($this->get('user_id')); - $user->set('account', $this); - return $user; - } - function update($vars, &$errors) { $rtoken = $_SESSION['_client']['reset-token']; if ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd'] || $rtoken) { @@ -490,20 +367,6 @@ class ClientAccount extends ClientAccountModel { return $this->save(); } - - static function createForUser($user) { - return static::create(array('user_id'=>$user->getId())); - } - - static function lookupByUsername($username) { - if (strpos($username, '@') !== false) - $user = self::lookup(array('user__emails__address'=>$username)); - else - $user = self::lookup(array('username'=>$username)); - - return $user; - } } -ClientAccount::_inspect(); ?> diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index 01682d00cf20c9f71976f7821a73076a98881f60..c58a22f4644e3d768caf172579fe5b2c2310ff4a 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -18,6 +18,7 @@ **********************************************************************/ require_once(INCLUDE_DIR . 'class.orm.php'); require_once(INCLUDE_DIR . 'class.forms.php'); +require_once(INCLUDE_DIR . 'class.filter.php'); require_once(INCLUDE_DIR . 'class.signal.php'); /** @@ -36,6 +37,7 @@ class DynamicForm extends VerySimpleModel { static $types = array( 'T' => 'Ticket Information', 'U' => 'User Information', + 'O' => 'Organization Information', ); var $_form; @@ -562,11 +564,21 @@ class DynamicFormEntry extends VerySimpleModel { return DynamicFormEntry::objects() ->filter(array('object_id'=>$user_id, 'object_type'=>'U')); } + function setClientId($user_id) { $this->object_type = 'U'; $this->object_id = $user_id; } + function setObjectId($object_id) { + $this->object_id = $object_id; + } + + function forOrganization($org_id) { + return DynamicFormEntry::objects() + ->filter(array('object_id'=>$org_id, 'object_type'=>'O')); + } + function render($staff=true, $title=false) { return $this->getForm()->render($staff, $title); } @@ -596,14 +608,23 @@ class DynamicFormEntry extends VerySimpleModel { // Add to list of answers $this->_values[] = $a; $this->_fields[] = $field; + $this->_form = null; + // Omit fields without data // For user entries, the name and email fields should not be // saved with the rest of the data - if (!($this->object_type == 'U' + if ($this->object_type == 'U' && in_array($field->get('name'), array('name','email'))) - && $field->hasData()) - $a->save(); - $this->_form = null; + continue; + + if ($this->object_type == 'O' + && in_array($field->get('name'), array('name'))) + continue; + + if (!$field->hasData()) + continue; + + $a->save(); } // Sort the form the way it is declared to be sorted if ($this->_fields) @@ -623,6 +644,11 @@ class DynamicFormEntry extends VerySimpleModel { if ($this->object_type == 'U' && in_array($field->get('name'), array('name','email'))) continue; + + if ($this->object_type == 'O' + && in_array($field->get('name'), array('name'))) + continue; + $val = $field->to_database($field->getClean()); if (is_array($val)) { $a->set('value', $val[0]); diff --git a/include/class.nav.php b/include/class.nav.php index 6e0179890938e6f7a933f7b089b2368dd8e0afa2..992daf107f8f8c212d4fc5528a9912e0f5e21e1b 100644 --- a/include/class.nav.php +++ b/include/class.nav.php @@ -96,9 +96,10 @@ class StaffNav { if(!$this->tabs) { $this->tabs=array(); - $this->tabs['dashboard']=array('desc'=>'Dashboard','href'=>'dashboard.php','title'=>'Staff Dashboard'); - $this->tabs['tickets']=array('desc'=>'Tickets','href'=>'tickets.php','title'=>'Ticket Queue'); - $this->tabs['kbase']=array('desc'=>'Knowledgebase','href'=>'kb.php','title'=>'Knowledgebase'); + $this->tabs['dashboard'] = array('desc'=>'Dashboard','href'=>'dashboard.php','title'=>'Staff Dashboard'); + $this->tabs['users'] = array('desc' => 'Users', 'href' => 'users.php', 'title' => 'User Directory'); + $this->tabs['tickets'] = array('desc'=>'Tickets','href'=>'tickets.php','title'=>'Ticket Queue'); + $this->tabs['kbase'] = array('desc'=>'Knowledgebase','href'=>'kb.php','title'=>'Knowledgebase'); } return $this->tabs; @@ -134,6 +135,10 @@ class StaffNav { $subnav[]=array('desc'=>'Staff Directory','href'=>'directory.php','iconclass'=>'teams'); $subnav[]=array('desc'=>'My Profile','href'=>'profile.php','iconclass'=>'users'); break; + case 'users': + $subnav[] = array('desc' => 'User Directory', 'href' => 'users.php', 'iconclass' => 'teams'); + $subnav[] = array('desc' => 'Organizations', 'href' => 'orgs.php', 'iconclass' => 'departments'); + break; case 'kbase': $subnav[]=array('desc'=>'FAQs','href'=>'kb.php', 'urls'=>array('faq.php'), 'iconclass'=>'kb'); if($staff) { diff --git a/include/class.organization.php b/include/class.organization.php new file mode 100644 index 0000000000000000000000000000000000000000..18b9f537d92735ad613b1fbc57af0f0e73f38284 --- /dev/null +++ b/include/class.organization.php @@ -0,0 +1,256 @@ +<?php +/********************************************************************* + class.organization.php + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2014 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once(INCLUDE_DIR . 'class.orm.php'); +require_once(INCLUDE_DIR . 'class.forms.php'); +require_once(INCLUDE_DIR . 'class.dynamic_forms.php'); + +class OrganizationModel extends VerySimpleModel { + static $meta = array( + 'table' => ORGANIZATION_TABLE, + 'pk' => array('id'), + 'joins' => array( + 'users' => array( + 'reverse' => 'UserAccountModel.org', + ), + ) + ); + + var $users; + + static function objects() { + $qs = parent::objects(); + + return $qs; + } + + function getId() { + return $this->id; + } +} + +class Organization extends OrganizationModel { + var $_entries; + var $_forms; + + function __construct($ht) { + parent::__construct($ht); + } + + //XXX: Shouldn't getName use magic get method to figure this out? + function getName() { + return $this->name; + } + + function getUpdateDate() { + return $this->updated; + } + + function getCreateDate() { + return $this->created; + } + + function addDynamicData($data) { + + $of = OrganizationForm::getInstance($this->id); + foreach ($of->getFields() as $f) + if (isset($data[$f->get('name')])) + $of->setAnswer($f->get('name'), $data[$f->get('name')]); + + $of->save(); + + return $of; + } + + function getDynamicData() { + if (!isset($this->_entries)) { + $this->_entries = DynamicFormEntry::forOrganization($this->id)->all(); + if (!$this->_entries) { + $g = OrganizationForm::getInstance($this->id); + $g->save(); + $this->_entries[] = $g; + } + } + + return $this->_entries; + } + + function getForms($data=null) { + + if (!isset($this->_forms)) { + $this->_forms = array(); + foreach ($this->getDynamicData() as $cd) { + $cd->addMissingFields(); + if(!$data + && ($form = $cd->getForm()) + && $form->get('type') == 'O' ) { + foreach ($cd->getFields() as $f) { + if ($f->get('name') == 'name') + $f->value = $this->getName(); + } + } + + $this->_forms[] = $cd->getForm(); + } + } + + return $this->_forms; + } + + function to_json() { + + $info = array( + 'id' => $this->getId(), + 'name' => (string) $this->getName() + ); + + return JsonDataEncoder::encode($info); + } + + + function __toString() { + return (string) $this->getName(); + } + + function delete() { + //TODO: delete or reset intrumented list. + return parent::delete(); + } + + function update($vars, &$errors) { + + $valid = true; + $forms = $this->getForms($vars); + foreach ($forms as $cd) { + if (!$cd->isValid()) + $valid = false; + if ($cd->get('type') == 'O' + && ($form= $cd->getForm($vars)) + && ($f=$form->getField('name')) + && $f->getClean() + && ($o=Organization::lookup(array('name'=>$f->getClean()))) + && $o->id != $this->getId()) { + $valid = false; + $f->addError('Organization with the same name already exists'); + } + } + + if (!$valid) + return false; + + foreach ($this->getDynamicData() as $cd) { + if (($f=$cd->getForm()) + && ($f->get('type') == 'O') + && ($name = $f->getField('name'))) { + $this->name = $name->getClean(); + $this->save(); + } + $cd->save(); + } + + return true; + } + + static function fromVars($vars) { + + if (!($org = Organization::lookup(array('name' => $vars['name'])))) { + $org = Organization::create(array( + 'name' => $vars['name'], + 'created' => new SqlFunction('NOW'), + 'updated' => new SqlFunction('NOW'), + )); + $org->save(true); + $org->addDynamicData($vars); + } + + return $org; + } + + static function fromForm($form) { + + if(!$form) return null; + + //Validate the form + $valid = true; + if (!$form->isValid()) + $valid = false; + + //Make sure the email is not in-use + if (($field=$form->getField('name')) + && $field->getClean() + && Organization::lookup(array('name' => $field->getClean()))) { + $field->addError('Organization with the same name already exists'); + $valid = false; + } + + return $valid ? self::fromVars($form->getClean()) : null; + } + +} + +class OrganizationForm extends DynamicForm { + static $instance; + static $form; + + static function objects() { + $os = parent::objects(); + return $os->filter(array('type'=>'O')); + } + + static function getDefaultForm() { + if (!isset(static::$form)) { + if (($o = static::objects()) && $o[0]) + static::$form = $o[0]; + else //TODO: Remove the code below and move it to task?? + static::$form = self::__loadDefaultForm(); + } + + return static::$form; + } + + static function getInstance($object_id=0) { + if (!isset(static::$instance)) + static::$instance = static::getDefaultForm()->instanciate(); + + static::$instance->object_type = 'O'; + + if ($object_id) + static::$instance->object_id = $object_id; + + return static::$instance; + } + + static function __loadDefaultForm() { + require_once(INCLUDE_DIR.'class.i18n.php'); + + $i18n = new Internationalization(); + $tpl = $i18n->getTemplate('form.yaml'); + foreach ($tpl->getData() as $f) { + if ($f['type'] == 'O') { + $form = DynamicForm::create($f); + $form->save(); + break; + } + } + + $o =static::objects(); + + return $o[0]; + } + +} + +//Organization::_inspect(); + +?> diff --git a/include/class.user.php b/include/class.user.php index d23d1e9f1b857bd343f74c42bc7348580703df56..77131657e62e097fe36c980e254b08cef0bd54c9 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -15,6 +15,7 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ require_once(INCLUDE_DIR . 'class.orm.php'); +require_once(INCLUDE_DIR . 'class.organization.php'); class UserEmailModel extends VerySimpleModel { static $meta = array( @@ -38,7 +39,7 @@ class UserModel extends VerySimpleModel { ), 'account' => array( 'list' => false, - 'reverse' => 'ClientAccount.user', + 'reverse' => 'UserAccount.user', ), 'default_email' => array( 'null' => true, @@ -73,6 +74,8 @@ class User extends UserModel { var $_entries; var $_forms; + var $_account; + function __construct($ht) { parent::__construct($ht); // TODO: Make this automatic with select_related() @@ -147,10 +150,6 @@ class User extends UserModel { return $this->created; } - function getAccount() { - return $this->account; - } - function to_json() { $info = array( @@ -194,9 +193,11 @@ class User extends UserModel { if (!$this->_entries) { $g = UserForm::getInstance(); $g->setClientId($this->id); + $g->save(); $this->_entries[] = $g; } } + return $this->_entries; } @@ -224,6 +225,44 @@ class User extends UserModel { return $this->_forms; } + function getAccount() { + // XXX: return $this->account; + + if (!isset($this->_account)) + $this->_account = UserAccount::lookup(array('user_id'=>$this->getId())); + + return $this->_account; + } + + function getAccountStatus() { + + if (!($account=$this->getAccount())) + return 'Guest'; + + if ($account->isLocked()) + return 'Locked (Administrative)'; + + if (!$account->isConfirmed()) + return 'Locked (Pending Activation)'; + + return 'Active'; + } + + function register($vars, &$errors) { + + // user already registered? + if ($this->getAccount()) + return true; + + return UserAccount::register($this, $vars, $errors); + } + + //TODO: Add organization support + function getOrg() { + return ''; + } + + function updateInfo($vars, &$errors) { $valid = true; @@ -288,16 +327,22 @@ class User extends UserModel { $this->name = mb_convert_case($this->name, MB_CASE_TITLE); } - if (count($this->dirty)) + if (count($this->dirty)) //XXX: doesn't work?? $this->set('updated', new SqlFunction('NOW')); return parent::save($refetch); } function delete() { - return parent::delete() && $this->default_email->delete(); + //TODO: See about deleting other associated models. + + // Delete email + if ($this->default_email) + $this->default_email->delete(); + + // Delete user + return parent::delete(); } } -User::_inspect(); class PersonsName { var $parts; @@ -482,6 +527,287 @@ class UserEmail extends UserEmailModel { } +class UserAccountModel extends VerySimpleModel { + static $meta = array( + 'table' => USER_ACCOUNT_TABLE, + 'pk' => array('id'), + 'joins' => array( + 'user' => array( + 'null' => false, + 'constraint' => array('user_id' => 'UserModel.id') + ), + ), + ); +} + +class UserAccount extends UserAccountModel { + var $_options = null; + var $_user; + var $_org; + + const CONFIRMED = 0x0001; + const LOCKED = 0x0002; + const PASSWD_RESET_REQUIRED = 0x0004; + + protected function hasStatus($flag) { + return 0 !== ($this->get('status') & $flag); + } + + protected function clearStatus($flag) { + return $this->set('status', $this->get('status') & ~$flag); + } + + protected function setStatus($flag) { + return $this->set('status', $this->get('status') | $flag); + } + + function confirm() { + $this->setStatus(self::CONFIRMED); + return $this->save(); + } + + function isConfirmed() { + return $this->hasStatus(self::CONFIRMED); + } + + function lock() { + $this->setStatus(self::LOCKED); + $this->save(); + } + + function isLocked() { + return $this->hasStatus(self::LOCKED); + } + + function forcePasswdReset() { + $this->setStatus(self::PASSWD_RESET_REQUIRED); + return $this->save(); + } + + function isPasswdResetForced() { + return $this->hasStatus(self::PASSWD_RESET_REQUIRED); + } + + function hasPassword() { + return (bool) $this->get('passwd'); + } + + function getStatus() { + return $this->get('status'); + } + + function getInfo() { + return $this->ht; + } + + function getId() { + return $this->get('id'); + } + + function getUserId() { + return $this->get('user_id'); + } + + function getUser() { + + if (!isset($this->_user)) { + if ($this->_user = User::lookup($this->getUserId())) + $this->_user->set('account', $this); + } + return $this->_user; + } + + function getOrgId() { + return $this->get('org_id'); + } + + function getOrganization() { + + if (!isset($this->_org)) + $this->_org = Organization::lookup($this->getOrgId()); + + return $this->_org; + } + + function setOrganization($org) { + if (!$org instanceof Organization) + return false; + + $this->set('org_id', $org->getId()); + $this->_org = null; + $this->save(); + + return true; + } + + + function sendResetEmail() { + return static::sendUnlockEmail('pwreset-client') === true; + } + + function sendConfirmEmail() { + return static::sendUnlockEmail('registration-client') === true; + } + + protected function sendUnlockEmail($template) { + global $ost, $cfg; + + $token = Misc::randCode(48); // 290-bits + + $email = $cfg->getDefaultEmail(); + $content = Page::lookup(Page::getIdByType($template)); + + if (!$email || !$content) + return new Error($template.': Unable to retrieve template'); + + $vars = array( + 'url' => $ost->getConfig()->getBaseUrl(), + 'token' => $token, + 'user' => $this->getUser(), + 'recipient' => $this->getUser(), + 'link' => sprintf( + "%s/pwreset.php?token=%s", + $ost->getConfig()->getBaseUrl(), + $token), + ); + $vars['reset_link'] = &$vars['link']; + + $info = array('email' => $email, 'vars' => &$vars, 'log'=>true); + Signal::send('auth.pwreset.email', $this, $info); + + $msg = $ost->replaceTemplateVariables(array( + 'subj' => $content->getName(), + 'body' => $content->getBody(), + ), $vars); + + $_config = new Config('pwreset'); + $_config->set($vars['token'], $this->getUser()->getId()); + + $email->send($this->getUser()->getEmail(), + Format::striptags($msg['subj']), $msg['body']); + + return true; + } + + function __toString() { + return (string) $this->getStatus(); + } + + /* + * This assumes the staff is doing the update + */ + function update($vars, &$errors) { + global $thisstaff; + + + if (!$thisstaff) { + $errors['err'] = 'Access Denied'; + return false; + } + + // TODO: Make sure the username is unique + + if (!$vars['timezone_id']) + $errors['timezone_id'] = 'Time zone required'; + + // Changing password? + if ($vars['passwd1'] || $vars['passwd2']) { + if (!$vars['passwd1']) + $errors['passwd1'] = 'New password required'; + elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6) + $errors['passwd1'] = 'Must be at least 6 characters'; + elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) + $errors['passwd2'] = 'Password(s) do not match'; + } + + if ($errors) return false; + + $this->set('timezone_id', $vars['timezone_id']); + $this->set('dst', isset($vars['dst']) ? 1 : 0); + + // Make sure the username is not an email. + if ($vars['username'] && Validator::is_email($vars['username'])) + $vars['username'] = ''; + + $this->set('username', $vars['username']); + + if ($vars['passwd1']) { + $this->set('passwd', Passwd::hash($vars['passwd'])); + $this->setStatus(self::CONFIRMED); + } + + // Set flags + if ($vars['pwreset-flag']) + $this->setStatus(self::PASSWD_RESET_REQUIRED); + else + $this->clearStatus(self::PASSWD_RESET_REQUIRED); + + if ($vars['locked-flag']) + $this->setStatus(self::LOCKED); + else + $this->clearStatus(self::LOCKED); + + return $this->save(true); + } + + static function createForUser($user) { + return static::create(array('user_id'=>$user->getId())); + } + + static function lookupByUsername($username) { + if (strpos($username, '@') !== false) + $user = static::lookup(array('user__emails__address'=>$username)); + else + $user = static::lookup(array('username'=>$username)); + + return $user; + } + + static function register($user, $vars, &$errors) { + + if (!$user || !$vars) + return false; + + //Require temp password. + if (!isset($vars['sendemail'])) { + if (!$vars['passwd1']) + $errors['passwd1'] = 'Temp. password required'; + elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6) + $errors['passwd1'] = 'Must be at least 6 characters'; + elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) + $errors['passwd2'] = 'Password(s) do not match'; + } + + if ($errors) return false; + + $account = UserAccount::create(array('user_id' => $user->getId())); + if (!$account) + return false; + + $account->set('dst', isset($vars['dst'])?1:0); + $account->set('timezone_id', $vars['timezone_id']); + + if ($vars['username'] && strcasecmp($vars['username'], $user->getEmail())) + $account->set('username', $vars['username']); + + if ($vars['passwd1'] && !$vars['sendemail']) { + $account->set('passwd', Password::hash($vars['passwd1'])); + $account->setStatus(self::CONFIRMED); + if ($vars['pwreset-flag']) + $account->setStatus(self::PASSWD_RESET_REQUIRED); + } + + $account->save(true); + + if ($vars['sendemail']) + $account->sendConfirmEmail(); + + return $account; + } + +} + + /* * Generic user list. */ @@ -531,5 +857,6 @@ class UserList implements IteratorAggregate, ArrayAccess { return $list ? implode(', ', $list) : ''; } } +User::_inspect(); ?> diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml index 244374279f619144b466304f2d2641810f454564..a5030c0333383fc972837aabe8c48add5550b775 100644 --- a/include/i18n/en_US/form.yaml +++ b/include/i18n/en_US/form.yaml @@ -146,3 +146,26 @@ html: false maxlength: 100 +- type: O # notrans + title: Organization Information + instructions: Details on user organization + deletable: false + fields: + - type: text # notrans + name: name # notrans + label: Name + required: true + sort: 1 + edit_mask: 15 + configuration: + size: 40 + length: 64 + + - type: memo # notrans + name: notes + label: Internal Notes + required: false + sort: 2 + configuration: + rows: 4 + cols: 40 diff --git a/include/staff/org-view.inc.php b/include/staff/org-view.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..89a8ee39df6d587cc7db14297e25c49ff9d18581 --- /dev/null +++ b/include/staff/org-view.inc.php @@ -0,0 +1,85 @@ +<?php +if(!defined('OSTSCPINC') || !$thisstaff || !is_object($org)) die('Invalid path'); + +?> +<table width="940" cellpadding="2" cellspacing="0" border="0"> + <tr> + <td width="50%" class="has_bottom_border"> + <h2><a href="orgs.php?id=<?php echo $org->getId(); ?>" + title="Reload"><i class="icon-refresh"></i> <?php echo $org->getName(); ?></a></h2> + </td> + <td width="50%" class="right_align has_bottom_border"> + <a id="org-delete" class="action-button org-action" + href="#orgs/<?php echo $org->getId(); ?>/delete"><i class="icon-trash"></i> Delete Organization</a> + </td> + </tr> +</table> +<table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> + <tr> + <td width="50"> + <table border="0" cellspacing="" cellpadding="4" width="100%"> + <tr> + <th width="100">Name:</th> + <td><b><a href="#orgs/<?php echo $org->getId(); + ?>/edit" class="org-action"><i + class="icon-edit"></i> <?php echo + $org->getName(); + ?></a></td> + </tr> + <tr> + <th>Users:</th> + <td> {num-here} + </td> + </tr> + </table> + </td> + <td width="50%" style="vertical-align:top"> + <table border="0" cellspacing="" cellpadding="4" width="100%"> + <tr> + <th>Created:</th> + <td><?php echo Format::db_datetime($org->getCreateDate()); ?></td> + </tr> + <tr> + <th>Updated:</th> + <td><?php echo Format::db_datetime($org->getUpdateDate()); ?></td> + </tr> + </table> + </td> + </tr> +</table> +<br> +<div class="clear"></div> +<ul class="tabs"> + <li><a class="active" id="users_tab" href="#users"><i + class="icon-user"></i> Users</a></li> + <li><a id="tickets_tab" href="#tickets"><i + class="icon-list-alt"></i> Tickets</a></li> +</ul> +<div class="tab_content" id="users"> +<?php +include STAFFINC_DIR . 'templates/users.tmpl.php'; +?> +</div> +<div class="tab_content" id="tickets" style="display:none;"> +<?php +include STAFFINC_DIR . 'templates/tickets.tmpl.php'; +?> +</div> + +<script type="text/javascript"> +$(function() { + $(document).on('click', 'a.org-action', function(e) { + e.preventDefault(); + var url = 'ajax.php/'+$(this).attr('href').substr(1); + $.dialog(url, [201, 204], function (xhr) { + if (xhr.status == 204) + window.location.href = 'orgs.php'; + else + window.location.href = window.location.href; + }, { + onshow: function() { $('#org-search').focus(); } + }); + return false; + }); +}); +</script> diff --git a/include/staff/orgs.inc.php b/include/staff/orgs.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..0df382c6d9aa02f431f2bb952ca7683cb03c56e0 --- /dev/null +++ b/include/staff/orgs.inc.php @@ -0,0 +1,164 @@ +<?php +if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); + +$qstr=''; + +$select = 'SELECT org.* '; + +$from = 'FROM '.ORGANIZATION_TABLE.' org '; + +$where = ' WHERE 1 '; + +if ($_REQUEST['query']) { + + $from .=' LEFT JOIN '.FORM_ENTRY_TABLE.' entry + ON (entry.object_type=\'O\' AND entry.object_id = org.id) + LEFT JOIN '.FORM_ANSWER_TABLE.' value + ON (value.entry_id=entry.id) '; + + $search = db_input(strtolower($_REQUEST['query']), false); + $where .= ' AND ( + org.name LIKE \'%'.$search.'%\' OR value.value LIKE \'%'.$search.'%\' + )'; + + $qstr.='&query='.urlencode($_REQUEST['query']); +} + +$sortOptions = array('name' => 'org.name', + 'users' => 'users', + 'create' => 'org.created', + 'update' => 'org.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 ?: 'org.name'; + +if ($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) + $order = $orderWays[strtoupper($_REQUEST['order'])]; + +$order=$order ?: 'ASC'; +if ($order_column && strpos($order_column,',')) + $order_column = str_replace(','," $order,",$order_column); + +$x=$sort.'_sort'; +$$x=' class="'.strtolower($order).'" '; +$order_by="$order_column $order "; + +$total=db_count('SELECT count(DISTINCT org.id) '.$from.' '.$where); +$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$pageNav=new Pagenate($total,$page,PAGE_LIMIT); +$pageNav->setURL('orgs.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +//Ok..lets roll...create the actual query +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); + +$select .= ', count(DISTINCT user.id) as users '; + +$from .= ' LEFT JOIN '.USER_ACCOUNT_TABLE.' user ON (user.org_id = org.id) '; + + +$query="$select $from $where GROUP BY org.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); +//echo $query; +?> +<h2>Organizations</h2> +<div style="width:700px; float:left;"> + <form action="orgs.php" method="get"> + <?php csrf_token(); ?> + <input type="hidden" name="a" value="search"> + <table> + <tr> + <td><input type="text" id="basic-org-search" name="query" size=30 value="<?php echo Format::htmlchars($_REQUEST['query']); ?>" + autocomplete="off" autocorrect="off" autocapitalize="off"></td> + <td><input type="submit" name="basic_search" class="button" value="Search"></td> + <!-- <td> <a href="" id="advanced-user-search">[advanced]</a></td> --> + </tr> + </table> + </form> + </div> + <div style="float:right;text-align:right;padding-right:5px;"> + <b><a href="#orgs/add" class="Icon newDepartment add-org">Add New Organization</a></b></div> +<div class="clear"></div> +<?php +$showing = $search ? 'Search Results: ' : ''; +$res = db_query($query); +if($res && ($num=db_num_rows($res))) + $showing .= $pageNav->showing(); +else + $showing .= 'No organizations found!'; +?> +<form action="orgs.php" method="POST" name="staff" > + <?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="400"><a <?php echo $name_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=name">Name</a></th> + <th width="100"><a <?php echo $users_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=users">Users</a></th> + <th width="150"><a <?php echo $create_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=create">Created</a></th> + <th width="145"><a <?php echo $update_sort; ?> href="orgs.php?<?php echo $qstr; ?>&sort=update">Updated</a></th> + </tr> + </thead> + <tbody> + <?php + if($res && db_num_rows($res)): + $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; + while ($row = db_fetch_array($res)) { + + $sel=false; + if($ids && in_array($row['id'], $ids)) + $sel=true; + ?> + <tr id="<?php echo $row['id']; ?>"> + <td> <a href="orgs.php?id=<?php echo $row['id']; ?>"><?php echo $row['name']; ?></a> </td> + <td> <?php echo $row['users']; ?></td> + <td><?php echo Format::db_date($row['created']); ?></td> + <td><?php echo Format::db_datetime($row['updated']); ?> </td> + </tr> + <?php + } //end of while. + endif; ?> + <tfoot> + <tr> + <td colspan="5"> </td> + </tr> + </tfoot> +</table> +<?php +if($res && $num): //Show options.. + echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; +endif; +?> +</form> + +<script type="text/javascript"> +$(function() { + $('input#basic-org-search').typeahead({ + source: function (typeahead, query) { + $.ajax({ + url: "ajax.php/orgs/search?q="+query, + dataType: 'json', + success: function (data) { + typeahead.process(data); + } + }); + }, + onselect: function (obj) { + window.location.href = 'orgs.php?id='+obj.id; + }, + property: "/bin/true" + }); + + $(document).on('click', 'a.add-org', function(e) { + e.preventDefault(); + $.orgLookup('ajax.php/orgs/add', function (org) { + window.location.href = 'orgs.php?id='+org.id; + }); + + return false; + }); +}); +</script> diff --git a/include/staff/templates/org-delete.tmpl.php b/include/staff/templates/org-delete.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..17ab2bc6a94577e0967059c64050124ff2d910fa --- /dev/null +++ b/include/staff/templates/org-delete.tmpl.php @@ -0,0 +1,56 @@ +<?php + +if (!$info['title']) + $info['title'] = 'Delete '.Format::htmlchars($org->getName()); + +$info['warn'] = 'Deleted organization CANNOT be recovered'; + +?> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<hr/> +<?php + +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warn']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warn']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> + +<div id="org-profile" style="margin:5px;"> + <i class="icon-group icon-4x pull-left icon-border"></i> + <div><b> <?php echo Format::htmlchars($org->getName()); ?></b></div> + <table style="margin-top: 1em;"> +<?php foreach ($org->getDynamicData() as $entry) { +?> + <tr><td colspan="2" style="border-bottom: 1px dotted black"><strong><?php + echo $entry->getForm()->get('title'); ?></strong></td></tr> +<?php foreach ($entry->getAnswers() as $a) { ?> + <tr style="vertical-align:top"><td style="width:30%;border-bottom: 1px dotted #ccc"><?php echo Format::htmlchars($a->getField()->get('label')); + ?>:</td> + <td style="border-bottom: 1px dotted #ccc"><?php echo $a->display(); ?></td> + </tr> +<?php } +} +?> + </table> + <div class="clear"></div> + <hr> + <form method="delete" class="org" + action="#orgs/<?php echo $org->getId(); ?>/delete"> + <input type="hidden" name="id" value="<?php echo $org->getId(); ?>" /> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="close" + value="No, Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Yes, Delete"> + </span> + </p> + </form> +</div> +<div class="clear"></div> diff --git a/include/staff/templates/org-lookup.tmpl.php b/include/staff/templates/org-lookup.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..a7c82f5daa4f40149b2bd802df2eb3cb8b7374ee --- /dev/null +++ b/include/staff/templates/org-lookup.tmpl.php @@ -0,0 +1,123 @@ +<?php + +if (!$info['title']) + $info['title'] = 'Organization Lookup'; + +$msg_info = 'Search existing organizations or add a new one'; +if ($info['search'] === false) + $msg_info = 'Complete the form below to add a new organization'; + +?> +<div id="the-lookup-form"> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<hr/> +<div><p id="msg_info"><i class="icon-info-sign"></i> <?php echo $msg_info; ?></p></div> +<?php +if ($info['search'] !== false) { ?> +<div style="margin-bottom:10px;"> + <input type="text" class="search-input" style="width:100%;" + placeholder="Search by name" id="org-search" autocorrect="off" autocomplete="off"/> +</div> +<?php +} + +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warning']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warning']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> +<div id="selected-org-info" style="display:<?php echo $org ? 'block' :'none'; ?>;margin:5px;"> +<form method="post" class="org" action="<?php echo $info['action'] ?: '#orgs/lookup'; ?>"> + <input type="hidden" id="org-id" name="orgid" value="<?php echo $org ? $org->getId() : 0; ?>"/> + <i class="icon-group icon-4x pull-left icon-border"></i> + <a class="action-button pull-right" style="overflow:inherit" + id="unselect-org" href="#"><i class="icon-remove"></i> Add New Organization</a> + <div><strong id="org-name"><?php echo $org ? Format::htmlchars($org->getName()) : ''; ?></strong></div> +<?php if ($org) { ?> + <table style="margin-top: 1em;"> +<?php foreach ($org->getDynamicData() as $entry) { ?> + <tr><td colspan="2" style="border-bottom: 1px dotted black"><strong><?php + echo $entry->getForm()->get('title'); ?></strong></td></tr> +<?php foreach ($entry->getAnswers() as $a) { ?> + <tr style="vertical-align:top"><td style="width:30%;border-bottom: 1px dotted #ccc"><?php echo Format::htmlchars($a->getField()->get('label')); + ?>:</td> + <td style="border-bottom: 1px dotted #ccc"><?php echo $a->display(); ?></td> + </tr> +<?php } + } ?> + </table> + <?php + } ?> +<div class="clear"></div> +<hr> +<p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" name="cancel" class="close" value="Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Continue"> + </span> + </p> +</form> +</div> +<div id="new-org-form" style="display:<?php echo $org ? 'none' :'block'; ?>;"> +<form method="post" class="org" action="<?php echo $info['action'] ?: '#orgs/add'; ?>"> + <table width="100%" class="fixed"> + <?php + if (!$form) $form = OrganizationForm::getInstance(); + $form->render(true, 'Create New Organization'); ?> + </table> + <hr> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="<?php echo $org ? 'cancel' : 'close' ?>" value="Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Add Organization"> + </span> + </p> +</form> +</div> +<div class="clear"></div> +</div> +<script type="text/javascript"> +$(function() { + var last_req; + $('#org-search').typeahead({ + source: function (typeahead, query) { + if (last_req) last_req.abort(); + last_req = $.ajax({ + url: "ajax.php/orgs/search?q="+query, + dataType: 'json', + success: function (data) { + typeahead.process(data); + } + }); + }, + onselect: function (obj) { + $('#the-lookup-form').load( + '<?php echo $info['onselect'] ?: 'ajax.php/orgs/select'; ?>/'+encodeURIComponent(obj.id) + ); + }, + property: "/bin/true" + }); + + $('a#unselect-org').click( function(e) { + e.preventDefault(); + $('div#selected-org-info').hide(); + $('div#new-org-form').fadeIn({start: function(){ $('#org-search').focus(); }}); + return false; + }); + + $(document).on('click', 'form.org input.cancel', function (e) { + e.preventDefault(); + $('div#new-org-form').hide(); + $('div#selected-org-info').fadeIn({start: function(){ $('#org-search').focus(); }}); + return false; + }); +}); +</script> diff --git a/include/staff/templates/org.tmpl.php b/include/staff/templates/org.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..b1d58427a0b46c7245c5263bc319839c608cd5eb --- /dev/null +++ b/include/staff/templates/org.tmpl.php @@ -0,0 +1,88 @@ +<?php +if (!$info['title']) + $info['title'] = Format::htmlchars($org->getName()); +?> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> +<div id="org-profile" style="display:<?php echo $forms ? 'none' : 'block'; ?>;margin:5px;"> + <i class="icon-group icon-4x pull-left icon-border"></i> + <?php + if ($account) { ?> + <a class="action-button pull-right user-action" style="overflow:inherit" + href="#users/<?php echo $account->getUserId(); ?>/org/<?php echo $org->getId(); ?>" ><i class="icon-user"></i> Change Organization</a> + <?php + } ?> + <div><b><a href="#" id="editorg"><i class="icon-edit"></i> <?php + echo Format::htmlchars($org->getName()); ?></a></b></div> + <table style="margin-top: 1em;"> +<?php foreach ($org->getDynamicData() as $entry) { +?> + <tr><td colspan="2" style="border-bottom: 1px dotted black"><strong><?php + echo $entry->getForm()->get('title'); ?></strong></td></tr> +<?php foreach ($entry->getAnswers() as $a) { ?> + <tr style="vertical-align:top"><td style="width:30%;border-bottom: 1px dotted #ccc"><?php echo Format::htmlchars($a->getField()->get('label')); + ?>:</td> + <td style="border-bottom: 1px dotted #ccc"><?php echo $a->display(); ?></td> + </tr> +<?php } +} +?> + </table> + <div class="clear"></div> + <hr> + <div class="faded">Last updated <b><?php echo Format::db_datetime($org->getUpdateDate()); ?> </b></div> +</div> +<div id="org-form" style="display:<?php echo $forms ? 'block' : 'none'; ?>;"> +<div><p id="msg_info"><i class="icon-info-sign"></i> Please note that updates will be reflected system-wide.</p></div> +<?php +$action = $info['action'] ? $info['action'] : ('#orgs/'.$org->getId()); +if ($ticket && $ticket->getOwnerId() == $user->getId()) + $action = '#tickets/'.$ticket->getId().'/user'; +?> +<form method="post" class="org" action="<?php echo $action; ?>"> + <input type="hidden" name="id" value="<?php echo $org->getId(); ?>" /> + <table width="100%"> + <?php + if (!$forms) $forms = $org->getForms(); + foreach ($forms as $form) + $form->render(); + ?> + </table> + <hr> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="<?php + echo $account ? 'cancel' : 'close'; ?>" value="Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Update Organization"> + </span> + </p> +</form> +</div> +<div class="clear"></div> +<script type="text/javascript"> +$(function() { + $('a#editorg').click( function(e) { + e.preventDefault(); + $('div#org-profile').hide(); + $('div#org-form').fadeIn(); + return false; + }); + + $(document).on('click', 'form.org input.cancel', function (e) { + e.preventDefault(); + $('div#org-form').hide(); + $('div#org-profile').fadeIn(); + return false; + }); +}); +</script> diff --git a/include/staff/templates/tickets.tmpl.php b/include/staff/templates/tickets.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..bdd5625786e86815aca2540e6955e5bae92517df --- /dev/null +++ b/include/staff/templates/tickets.tmpl.php @@ -0,0 +1,174 @@ +<?php + +$select ='SELECT ticket.ticket_id,ticket.`number`,ticket.dept_id,ticket.staff_id,ticket.team_id, ticket.user_id ' + .' ,dept.dept_name,ticket.status,ticket.source,ticket.isoverdue,ticket.isanswered,ticket.created ' + .' ,CAST(GREATEST(IFNULL(ticket.lastmessage, 0), IFNULL(ticket.reopened, 0), ticket.created) as datetime) as effective_date ' + .' ,CONCAT_WS(" ", staff.firstname, staff.lastname) as staff, team.name as team ' + .' ,IF(staff.staff_id IS NULL,team.name,CONCAT_WS(" ", staff.lastname, staff.firstname)) as assigned ' + .' ,IF(ptopic.topic_pid IS NULL, topic.topic, CONCAT_WS(" / ", ptopic.topic, topic.topic)) as helptopic ' + .' ,cdata.priority_id, cdata.subject, user.name, email.address as email'; + +$from =' FROM '.TICKET_TABLE.' ticket ' + .' LEFT JOIN '.USER_TABLE.' user ON user.id = ticket.user_id ' + .' LEFT JOIN '.USER_EMAIL_TABLE.' email ON user.id = email.user_id ' + .' LEFT JOIN '.USER_ACCOUNT_TABLE.' account ON (ticket.user_id=account.user_id) ' + .' LEFT JOIN '.DEPT_TABLE.' dept ON ticket.dept_id=dept.dept_id ' + .' LEFT JOIN '.STAFF_TABLE.' staff ON (ticket.staff_id=staff.staff_id) ' + .' LEFT JOIN '.TEAM_TABLE.' team ON (ticket.team_id=team.team_id) ' + .' LEFT JOIN '.TOPIC_TABLE.' topic ON (ticket.topic_id=topic.topic_id) ' + .' LEFT JOIN '.TOPIC_TABLE.' ptopic ON (ptopic.topic_id=topic.topic_pid) ' + .' LEFT JOIN '.TABLE_PREFIX.'ticket__cdata cdata ON (cdata.ticket_id = ticket.ticket_id) ' + .' LEFT JOIN '.PRIORITY_TABLE.' pri ON (pri.priority_id = cdata.priority_id)'; + +if ($user) + $where = 'WHERE ticket.user_id = '.db_input($user->getId()); +elseif ($org) + $where = 'WHERE account.org_id = '.db_input($org->getId()); + + +TicketForm::ensureDynamicDataView(); + +$query ="$select $from $where ORDER BY ticket.created DESC"; + +// Fetch the results +$results = array(); +$res = db_query($query); +while ($row = db_fetch_array($res)) + $results[$row['ticket_id']] = $row; + +if ($results) { + $counts_sql = 'SELECT ticket.ticket_id, + count(DISTINCT attach.attach_id) as attachments, + count(DISTINCT thread.id) as thread_count, + count(DISTINCT collab.id) as collaborators + FROM '.TICKET_TABLE.' ticket + LEFT JOIN '.TICKET_ATTACHMENT_TABLE.' attach ON (ticket.ticket_id=attach.ticket_id) ' + .' LEFT JOIN '.TICKET_THREAD_TABLE.' thread ON ( ticket.ticket_id=thread.ticket_id) ' + .' LEFT JOIN '.TICKET_COLLABORATOR_TABLE.' collab + ON ( ticket.ticket_id=collab.ticket_id) ' + .' WHERE ticket.ticket_id IN ('.implode(',', db_input(array_keys($results))).') + GROUP BY ticket.ticket_id'; + $ids_res = db_query($counts_sql); + while ($row = db_fetch_array($ids_res)) { + $results[$row['ticket_id']] += $row; + } +} +?> +<div style="width:700px; float:left;"> + <?php + if ($results) { + echo sprintf('<strong>Showing 1 - %d of %s</strong>', count($results), count($results)); + } else { + echo sprintf('%s does not have any tickets', $user? 'User' : 'Organization'); + } + ?> +</div> +<div style="float:right;text-align:right;padding-right:5px;"> + <?php + if ($user) { ?> + <b><a class="Icon newTicket" href="tickets.php?a=open&uid=<?php echo $user->getId(); ?>"> Create New Ticket</a></b> + <?php + } ?> +</div> +<br/> +<div> +<?php +if ($results) { ?> +<form action="users.php" method="POST" name='tickets' style="padding-top:10px;"> +<?php csrf_token(); ?> + <input type="hidden" name="a" value="mass_process" > + <input type="hidden" name="do" id="action" value="" > + <table class="list" border="0" cellspacing="1" cellpadding="2" width="940"> + <thead> + <tr> + <?php + if (0) {?> + <th width="8px"> </th> + <?php + } ?> + <th width="70">Ticket</th> + <th width="100">Date</th> + <th width="100">Status</th> + <th width="300">Subject</th> + <?php + if ($user) { ?> + <th width="200">Department</th> + <th width="200">Assignee</th> + <?php + } else { ?> + <th width="400">User</th> + <?php + } ?> + </tr> + </thead> + <tbody> + <?php + foreach($results as $row) { + $flag=null; + if ($row['lock_id']) + $flag='locked'; + elseif ($row['isoverdue']) + $flag='overdue'; + + $assigned=''; + if ($row['staff_id']) + $assigned=sprintf('<span class="Icon staffAssigned">%s</span>',Format::truncate($row['staff'],40)); + elseif ($row['team_id']) + $assigned=sprintf('<span class="Icon teamAssigned">%s</span>',Format::truncate($row['team'],40)); + else + $assigned=' '; + + $status = ucfirst($row['status']); + if(!strcasecmp($row['status'], 'open')) + $status = "<b>$status</b>"; + + $tid=$row['number']; + $subject = Format::htmlchars(Format::truncate($row['subject'],40)); + $threadcount=$row['thread_count']; + ?> + <tr id="<?php echo $row['ticket_id']; ?>"> + <?php + //Implement mass action....if need be. + if (0) { ?> + <td align="center" class="nohover"> + <input class="ckb" type="checkbox" name="tids[]" value="<?php echo $row['ticket_id']; ?>" <?php echo $sel?'checked="checked"':''; ?>> + </td> + <?php + } ?> + <td align="center" nowrap> + <a class="Icon <?php echo strtolower($row['source']); ?>Ticket ticketPreview" title="Preview Ticket" + href="tickets.php?id=<?php echo $row['ticket_id']; ?>"><?php echo $tid; ?></a></td> + <td align="center" nowrap><?php echo Format::db_datetime($row['effective_date']); ?></td> + <td><?php echo $status; ?></td> + <td><a <?php if ($flag) { ?> class="Icon <?php echo $flag; ?>Ticket" title="<?php echo ucfirst($flag); ?> Ticket" <?php } ?> + href="tickets.php?id=<?php echo $row['ticket_id']; ?>"><?php echo $subject; ?></a> + <?php + if ($threadcount>1) + echo "<small>($threadcount)</small> ".'<i + class="icon-fixed-width icon-comments-alt"></i> '; + if ($row['collaborators']) + echo '<i class="icon-fixed-width icon-group faded"></i> '; + if ($row['attachments']) + echo '<i class="icon-fixed-width icon-paperclip"></i> '; + ?> + </td> + <?php + if ($user) { ?> + <td><?php echo Format::truncate($row['dept_name'], 40); ?></td> + <td> <?php echo $assigned; ?></td> + <?php + } else { ?> + <td> <?php echo sprintf('<a href="users.php?id=%d">%s <em> <%s></em></a>', + $row['user_id'], $row['name'], $row['email']); ?></td> + <?php + } ?> + </tr> + <?php + } + ?> + </tbody> +</table> +</form> +<?php + } ?> +</div> diff --git a/include/staff/templates/user-account.tmpl.php b/include/staff/templates/user-account.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..5a08e8e4cf3696c44efbdcd59d3a5530b7850dfd --- /dev/null +++ b/include/staff/templates/user-account.tmpl.php @@ -0,0 +1,173 @@ +<?php +$account = $user->getAccount(); +$access = (isset($info['_target']) && $info['_target'] == 'access'); + +if (!$info['title']) + $info['title'] = Format::htmlchars($user->getName()); +?> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> +<ul class="tabs"> + <li><a href="#user-account" <?php echo !$access? 'class="active"' : ''; ?> + ><i class="icon-user"></i> User Information</a></li> + <li><a href="#user-access" <?php echo $access? 'class="active"' : ''; ?> + ><i class="icon-fixed-width icon-lock faded"></i> Manage Access</a></li> +</ul> + + +<form method="post" class="user" action="#users/<?php echo $user->getId(); ?>/manage" > + <input type="hidden" name="id" value="<?php echo $user->getId(); ?>" /> + <div class="tab_content" id="user-account" style="display:<?php echo $access? 'none' : 'block'; ?>; margin:5px;"> + <form method="post" class="user" action="#users/<?php echo $user->getId(); ?>/manage" > + <input type="hidden" name="id" value="<?php echo $user->getId(); ?>" /> + <table width="100%"> + <tbody> + <tr> + <th colspan="2"> + <em><strong>User Information</strong></em> + </th> + </tr> + <tr> + <td width="180"> + Name: + </td> + <td> <?php echo $user->getName(); ?> </td> + </tr> + <tr> + <td width="180"> + Email: + </td> + <td> <?php echo $user->getEmail(); ?> </td> + </tr> + <tr> + <td width="180"> + Organization: + </td> + <td> + <input type="text" size="35" name="org" value="<?php echo $info['org']; ?>"> + <span class="error"> <?php echo $errors['org']; ?></span> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="2"><em><strong>User Preferences</strong></em></th> + </tr> + <td>Time Zone:</td> + <td> + <select name="timezone_id" id="timezone_id"> + <?php + $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$offset, $tz)=db_fetch_row($res)){ + $sel=($info['timezone_id']==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz); + } + } + ?> + </select> + <span class="error"><?php echo $errors['timezone_id']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + Daylight Saving: + </td> + <td> + <input type="checkbox" name="dst" value="1" <?php echo $info['dst']?'checked="checked"':''; ?>> + Observe daylight saving + </td> + </tr> + </tbody> + </table> + </div> + <div class="tab_content" id="user-access" style="display:<?php echo $access? 'block' : 'none'; ?>; margin:5px;"> + <table width="100%"> + <tbody> + <tr> + <th colspan="2"><em><strong>Account Access</strong></em></th> + </tr> + <tr> + <td width="180"> Status: </td> + <td> <?php echo $user->getAccountStatus(); ?> </td> + </tr> + <tr> + <td width="180"> + Username: + </td> + <td> + <input type="text" size="35" name="username" value="<?php echo $info['username'] ?: $user->getEmail(); ?>"> + <span class="error"> <?php echo $errors['username']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + New Password: + </td> + <td> + <input type="password" size="35" name="passwd1" value="<?php echo $info['passwd1']; ?>"> + <span class="error"> <?php echo + $errors['passwd1']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + Confirm Password: + </td> + <td> + <input type="password" size="35" name="passwd2" value="<?php echo $info['passwd2']; ?>"> + <span class="error"> <?php echo $errors['passwd2']; ?></span> + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="2"><em><strong>Account Flags</strong></em></th> + </tr> + <tr> + <td colspan="2"> + <?php + echo sprintf('<div><input type="checkbox" name="locked-flag" %s + value="1"> Administratively Locked</div>', + $account->isLocked() ? 'checked="checked"' : '' + ); + ?> + <div><input type="checkbox" name="pwreset-flag" value="1" <?php + echo $account->isPasswdResetForced() ? + 'checked="checked"' : ''; ?>> Password Reset Required</div> + </td> + </tr> + </tbody> + </table> + </div> + <hr> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="close" value="Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" + value="Save Changes"> + </span> + </p> +</form> +<div class="clear"></div> +<script type="text/javascript"> +$(function() { + $(document).on('click', 'input#sendemail', function(e) { + if ($(this).prop('checked')) + $('tbody#password').hide(); + else + $('tbody#password').show(); + }); +}); +</script> diff --git a/include/staff/templates/user-delete.tmpl.php b/include/staff/templates/user-delete.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..661bfa70ee08148f1291238337edbf686ccdd77c --- /dev/null +++ b/include/staff/templates/user-delete.tmpl.php @@ -0,0 +1,93 @@ +<?php + +if (!$info['title']) + $info['title'] = 'Delete User: '.Format::htmlchars($user->getName()); + +$info['warn'] = 'Deleted users and tickets CANNOT be recovered'; + +?> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<hr/> +<?php + +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['warn']) { + echo sprintf('<p id="msg_warning">%s</p>', $info['warn']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> + +<div id="user-profile" style="margin:5px;"> + <i class="icon-user icon-4x pull-left icon-border"></i> + <?php + // TODO: Implement change of ownership + if (0 && $user->getNumTickets()) { ?> + <a class="action-button pull-right change-user" style="overflow:inherit" + href="#users/<?php echo $user->getId(); ?>/replace" ><i + class="icon-user"></i> Change Tickets Ownership</a> + <?php + } ?> + <div><b> <?php echo Format::htmlchars($user->getName()->getOriginal()); ?></b></div> + <div><<?php echo $user->getEmail(); ?>></div> + <table style="margin-top: 1em;"> +<?php foreach ($user->getDynamicData() as $entry) { +?> + <tr><td colspan="2" style="border-bottom: 1px dotted black"><strong><?php + echo $entry->getForm()->get('title'); ?></strong></td></tr> +<?php foreach ($entry->getAnswers() as $a) { ?> + <tr style="vertical-align:top"><td style="width:30%;border-bottom: 1px dotted #ccc"><?php echo Format::htmlchars($a->getField()->get('label')); + ?>:</td> + <td style="border-bottom: 1px dotted #ccc"><?php echo $a->display(); ?></td> + </tr> +<?php } +} +?> + </table> + <div class="clear"></div> + <hr> + <?php + if ($user->getNumTickets()) { + echo sprintf('<div><input type="checkbox" name="deletetickets" value="1" > + <strong>Delete <a href="tickets.php?a=search&uid=%d" target="_blank">%d + %s</a> and any associated attachments and data.</strong></div><hr>', + $user->getId(), + $user->getNumTickets(), + ($user->getNumTickets() >1) ? 'tickets' : 'ticket' + ); + } + ?> + <form method="delete" class="user" + action="#users/<?php echo $user->getId(); ?>/delete"> + <input type="hidden" name="id" value="<?php echo $user->getId(); ?>" /> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="close" + value="No, Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Yes, Delete User"> + </span> + </p> + </form> +</div> +<div class="clear"></div> +<script type="text/javascript"> +$(function() { + $('a#edituser').click( function(e) { + e.preventDefault(); + $('div#user-profile').hide(); + $('div#user-form').fadeIn(); + return false; + }); + + $(document).on('click', 'form.user input.cancel', function (e) { + e.preventDefault(); + $('div#user-form').hide(); + $('div#user-profile').fadeIn(); + return false; + }); +}); +</script> diff --git a/include/staff/templates/user-lookup.tmpl.php b/include/staff/templates/user-lookup.tmpl.php index 3604ed5a29ddff2246343d69a41a32a9cad810fc..39a617e01d4672c661cd65a2699d559c202dcb68 100644 --- a/include/staff/templates/user-lookup.tmpl.php +++ b/include/staff/templates/user-lookup.tmpl.php @@ -3,7 +3,11 @@ <b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> <hr/> <div><p id="msg_info"><i class="icon-info-sign"></i> Search existing users or add a new user.</p></div> -<div style="margin-bottom:10px;"><input type="text" class="search-input" style="width:100%;" placeholder="Search by email, phone or name" id="user-search" autocorrect="off" autocomplete="off"/></div> +<div style="margin-bottom:10px;"> + <input type="text" class="search-input" style="width:100%;" + placeholder="Search by email, phone or name" id="user-search" + autocorrect="off" autocomplete="off"/> +</div> <?php if ($info['error']) { echo sprintf('<p id="msg_error">%s</p>', $info['error']); @@ -73,7 +77,8 @@ $(function() { source: function (typeahead, query) { if (last_req) last_req.abort(); last_req = $.ajax({ - url: "ajax.php/users?q="+query, + url: "ajax.php/users<?php + echo $info['lookuptype'] ? "/{$info['lookuptype']}" : '' ?>?q="+query, dataType: 'json', success: function (data) { typeahead.process(data); diff --git a/include/staff/templates/user-register.tmpl.php b/include/staff/templates/user-register.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..8f87c41a3453add53001dc3d1bf62f2671a07bf4 --- /dev/null +++ b/include/staff/templates/user-register.tmpl.php @@ -0,0 +1,149 @@ +<?php +global $cfg; + +if (!$info['title']) + $info['title'] = 'Register: '.Format::htmlchars($user->getName()); + +if (!$_POST) { + + $info['sendemail'] = true; // send email confirmation. + + if (!isset($info['timezone_id'])) + $info['timezone_id'] = $cfg->getDefaultTimezoneId(); + + if (!isset($info['dst'])) + $info['dst'] = $cfg->observeDaylightSaving(); +} + +?> +<h3><?php echo $info['title']; ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<?php +if ($info['error']) { + echo sprintf('<p id="msg_error">%s</p>', $info['error']); +} elseif ($info['msg']) { + echo sprintf('<p id="msg_notice">%s</p>', $info['msg']); +} ?> +<div><p id="msg_info"><i class="icon-info-sign"></i> Complete the form +below to create a user account for <b><?php echo +$user->getName()->getOriginal(); ?></b>.</p></div> +<div id="user-registration" style="display:block; margin:5px;"> + <form method="post" class="user" + action="#users/<?php echo $user->getId(); ?>/register"> + <input type="hidden" name="id" value="<?php echo $user->getId(); ?>" /> + <table width="100%"> + <tbody> + <tr> + <th colspan="2"> + <em><strong>User Account Login</strong></em> + </th> + </tr> + <tr> + <td width="180"> + Status: + </td> + <td> + <input type="checkbox" id="sendemail" name="sendemail" value="1" + <?php echo $info['sendemail'] ? 'checked="checked"' : ''; ?> > + Send account activation email to <?php echo $user->getEmail(); ?>. + </td> + </tr> + <tr> + <td width="180"> + Username: + </td> + <td> + <input type="text" size="35" name="username" value="<?php echo $info['username'] ?: $user->getEmail(); ?>"> + <span class="error"> <?php echo $errors['username']; ?></span> + </td> + </tr> + </tbody> + <tbody id="password" + style="<?php echo $info['sendemail'] ? 'display:none;' : ''; ?>" + > + <tr> + <td width="180"> + Temp. Password: + </td> + <td> + <input type="password" size="35" name="passwd1" value="<?php echo $info['passwd1']; ?>"> + <span class="error"> <?php echo + $errors['passwd1']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + Confirm Password: + </td> + <td> + <input type="password" size="35" name="passwd2" value="<?php echo $info['passwd2']; ?>"> + <span class="error"> <?php echo $errors['passwd2']; ?></span> + </td> + </tr> + <tr> + <td> + Password Change: + </td> + <td colspan=2> + <input type="checkbox" name="pwreset-flag" value="1" <?php + echo $info['pwreset-flag'] ? 'checked="checked"' : ''; ?>> Require password change on login + </td> + </tr> + </tbody> + <tbody> + <tr> + <th colspan="2"><em><strong>User Preferences</strong></em></th> + </tr> + <td>Time Zone:</td> + <td> + <select name="timezone_id" id="timezone_id"> + <?php + $sql='SELECT id, offset, timezone FROM '.TIMEZONE_TABLE.' ORDER BY id'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id, $offset, $tz) = db_fetch_row($res)) { + $sel=($info['timezone_id']==$id) ? 'selected="selected"' : ''; + echo sprintf('<option value="%d" %s>GMT %s - %s</option>', + $id, $sel, $offset, $tz); + } + } + ?> + </select> + <span class="error"><?php echo $errors['timezone_id']; ?></span> + </td> + </tr> + <tr> + <td width="180"> + Daylight Saving: + </td> + <td> + <input type="checkbox" name="dst" value="1" <?php echo $info['dst'] ? 'checked="checked"' : ''; ?>> + Observe daylight saving + </td> + </tr> + </tbody> + </table> + <hr> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="reset" value="Reset"> + <input type="button" name="cancel" class="close" value="Cancel"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="Create Account"> + </span> + </p> + </form> +</div> +<div class="clear"></div> +<script type="text/javascript"> +$(function() { + $(document).on('click', 'input#sendemail', function(e) { + if ($(this).prop('checked')) + $('tbody#password').hide(); + else + $('tbody#password').show(); + }); +}); +</script> diff --git a/include/staff/templates/users.tmpl.php b/include/staff/templates/users.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..6b22d3db9ed111fb2a9ba72b86e3f4558e8e57f1 --- /dev/null +++ b/include/staff/templates/users.tmpl.php @@ -0,0 +1,131 @@ +<?php + +$qstr=''; +$select = 'SELECT user.*, email.address as email '; + +$from = 'FROM '.USER_TABLE.' user ' + . 'LEFT JOIN '.USER_ACCOUNT_TABLE.' account ON (user.id = account.user_id) ' + . 'LEFT JOIN '.USER_EMAIL_TABLE.' email ON (user.id = email.user_id) '; + +$where='WHERE account.org_id='.db_input($org->getId()); + + +$sortOptions = array('name' => 'user.name', + 'email' => 'email.address', + 'create' => 'user.created', + 'update' => 'user.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 ?: 'user.name'; + +if ($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) + $order = $orderWays[strtoupper($_REQUEST['order'])]; + +$order=$order ?: 'ASC'; +if ($order_column && strpos($order_column,',')) + $order_column = str_replace(','," $order,",$order_column); + +$x=$sort.'_sort'; +$$x=' class="'.strtolower($order).'" '; +$order_by="$order_column $order "; + +$total=db_count('SELECT count(DISTINCT user.id) '.$from.' '.$where); +$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$pageNav=new Pagenate($total,$page,PAGE_LIMIT); +$pageNav->setURL('users.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +//Ok..lets roll...create the actual query +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); + +$select .= ', count(DISTINCT ticket.ticket_id) as tickets '; + +$from .= ' LEFT JOIN '.TICKET_TABLE.' ticket ON (ticket.user_id = user.id) '; + + +$query="$select $from $where GROUP BY user.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); +//echo $query; + +$showing = $search ? 'Search Results: ' : ''; +$res = db_query($query); +if($res && ($num=db_num_rows($res))) + $showing .= $pageNav->showing(); +else + $showing .= 'No users found!'; + +?> +<div style="width:700px; float:left;"><b><?php echo $showing; ?></b></div> +<div style="float:right;text-align:right;padding-right:5px;"> + <b><a href="#orgs/<?php echo $org->getId(); ?>/add-user" class="Icon newstaff add-user">Add New User</a></b></div> +<div class="clear"></div> +<br/> +<form action="users.php" method="POST" name="staff" > + <?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"> + <thead> + <tr> + <th width="350"> Name</th> + <th width="300"> Email</th> + <th width="100"> Status</th> + <th width="100"> Created</th> + </tr> + </thead> + <tbody> + <?php + if($res && db_num_rows($res)): + $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; + while ($row = db_fetch_array($res)) { + + $name = new PersonsName($row['name']); + $status = 'Active'; + $sel=false; + if($ids && in_array($row['id'], $ids)) + $sel=true; + ?> + <tr id="<?php echo $row['id']; ?>"> + <td> + <a href="users.php?id=<?php echo $row['id']; ?>"><?php echo $name; ?></a> + + <?php + if ($row['tickets']) + echo sprintf('<i class="icon-fixed-width icon-file-text-alt"></i> + <small>(%d)</small>', $row['tickets']); + ?> + </td> + <td><?php echo $row['email']; ?></td> + <td><?php echo $status; ?></td> + <td><?php echo Format::db_date($row['created']); ?></td> + </tr> + <?php + } //end of while. + endif; ?> + <tfoot> + <tr> + <td colspan="4"> </td> + </tr> + </tfoot> +</table> +<?php +if($res && $num): //Show options.. + echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; +endif; +?> +</form> + +<script type="text/javascript"> +$(function() { + $(document).on('click', 'a.add-user', function(e) { + e.preventDefault(); + $.userLookup('ajax.php/users/add', function (user) { + window.location.href = 'users.php?id='+user.id; + }); + + return false; + }); +}); +</script> + diff --git a/include/staff/ticket-view.inc.php b/include/staff/ticket-view.inc.php index 9568dbf656c5513a28980f101ee35089368643f3..fc636f515dee5388adaa6ee20d554137238f3b0a 100644 --- a/include/staff/ticket-view.inc.php +++ b/include/staff/ticket-view.inc.php @@ -54,7 +54,7 @@ if($ticket->isOverdue()) <?php } ?> <?php if($thisstaff->canDeleteTickets()) { ?> - <a id="ticket-delete" class="action-button" href="#delete"><i class="icon-trash"></i> Delete</a> + <a id="ticket-delete" class="action-button confirm-action" href="#delete"><i class="icon-trash"></i> Delete</a> <?php } ?> <?php if($thisstaff->canCloseTickets()) { @@ -74,7 +74,7 @@ if($ticket->isOverdue()) } ?> <?php if($ticket->isOpen() && !$ticket->isAssigned() && $thisstaff->canAssignTickets()) {?> - <a id="ticket-claim" class="action-button" href="#claim"><i class="icon-user"></i> Claim</a> + <a id="ticket-claim" class="action-button confirm-action" href="#claim"><i class="icon-user"></i> Claim</a> <?php }?> @@ -91,30 +91,32 @@ if($ticket->isOverdue()) if($ticket->isOpen() && ($dept && $dept->isManager($thisstaff))) { if($ticket->isAssigned()) { ?> - <li><a id="ticket-release" href="#release"><i class="icon-user"></i> Release (unassign) Ticket</a></li> + <li><a class="confirm-action" id="ticket-release" href="#release"><i class="icon-user"></i> Release (unassign) Ticket</a></li> <?php } if(!$ticket->isOverdue()) { ?> - <li><a id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> Mark as Overdue</a></li> + <li><a class="confirm-action" id="ticket-overdue" href="#overdue"><i class="icon-bell"></i> Mark as Overdue</a></li> <?php } if($ticket->isAnswered()) { ?> - <li><a id="ticket-unanswered" href="#unanswered"><i class="icon-circle-arrow-left"></i> Mark as Unanswered</a></li> + <li><a class="confirm-action" id="ticket-unanswered" href="#unanswered"><i class="icon-circle-arrow-left"></i> Mark as Unanswered</a></li> <?php } else { ?> - <li><a id="ticket-answered" href="#answered"><i class="icon-circle-arrow-right"></i> Mark as Answered</a></li> + <li><a class="confirm-action" id="ticket-answered" href="#answered"><i class="icon-circle-arrow-right"></i> Mark as Answered</a></li> <?php } } if($thisstaff->canBanEmails()) { if(!$emailBanned) {?> - <li><a id="ticket-banemail" href="#banemail"><i class="icon-ban-circle"></i> Ban Email (<?php echo $ticket->getEmail(); ?>)</a></li> + <li><a class="confirm-action" id="ticket-banemail" + href="#banemail"><i class="icon-ban-circle"></i> Ban Email (<?php echo $ticket->getEmail(); ?>)</a></li> <?php } elseif($unbannable) { ?> - <li><a id="ticket-banemail" href="#unbanemail"><i class="icon-undo"></i> Unban Email (<?php echo $ticket->getEmail(); ?>)</a></li> + <li><a class="confirm-action" id="ticket-banemail" + href="#unbanemail"><i class="icon-undo"></i> Unban Email (<?php echo $ticket->getEmail(); ?>)</a></li> <?php } }?> diff --git a/include/staff/user-view.inc.php b/include/staff/user-view.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..5ad4478c7fea8e9954a304eef9fffc4a1bd130a6 --- /dev/null +++ b/include/staff/user-view.inc.php @@ -0,0 +1,180 @@ +<?php +if(!defined('OSTSCPINC') || !$thisstaff || !is_object($user)) die('Invalid path'); + +$account = $user->getAccount(); +$org = $account ? $account->getOrganization() : null; + + +?> +<table width="940" cellpadding="2" cellspacing="0" border="0"> + <tr> + <td width="50%" class="has_bottom_border"> + <h2><a href="users.php?id=<?php echo $user->getId(); ?>" + title="Reload"><i class="icon-refresh"></i> <?php echo $user->getName(); ?></a></h2> + </td> + <td width="50%" class="right_align has_bottom_border"> + <?php + if ($account) { ?> + <span class="action-button" data-dropdown="#action-dropdown-more"> + <span ><i class="icon-cog"></i> More</span> + <i class="icon-caret-down"></i> + </span> + <?php + } ?> + <a id="user-delete" class="action-button user-action" + href="#users/<?php echo $user->getId(); ?>/delete"><i class="icon-trash"></i> Delete User</a> + <?php + if ($account) { ?> + <a id="user-manage" class="action-button user-action" + href="#users/<?php echo $user->getId(); ?>/manage"><i class="icon-edit"></i> Manage Account</a> + <?php + } else { ?> + <a id="user-register" class="action-button user-action" + href="#users/<?php echo $user->getId(); ?>/register"><i class="icon-edit"></i> Register</a> + <?php + } ?> + <div id="action-dropdown-more" class="action-dropdown anchor-right"> + <ul> + <?php + if ($account) { + if (!$account->isConfirmed()) { + ?> + <li><a class="confirm-action" href="#confirmlink"><i + class="icon-envelope"></i> Send Activation Email</a></li> + <?php + } else { ?> + <li><a class="confirm-action" href="#pwreset"><i + class="icon-envelope"></i> Send Password Reset Email</a></li> + <?php + } ?> + <li><a class="user-action" + href="#users/<?php echo $user->getId(); ?>/manage/access"><i + class="icon-lock"></i> Manage Account Access</a></li> + <?php + + } ?> + </ul> + </div> + </td> + </tr> +</table> +<table class="ticket_info" cellspacing="0" cellpadding="0" width="940" border="0"> + <tr> + <td width="50"> + <table border="0" cellspacing="" cellpadding="4" width="100%"> + <tr> + <th width="100">Name:</th> + <td><b><a href="#users/<?php echo $user->getId(); + ?>/edit" class="user-action"><i + class="icon-edit"></i> <?php echo + $user->getName()->getOriginal(); + ?></a></td> + </tr> + <tr> + <th>Email:</th> + <td> + <span id="user-<?php echo $user->getId(); ?>-email"><?php echo $user->getEmail(); ?></span> + </td> + </tr> + <tr> + <th>Organization:</th> + <td> + <span id="user-<?php echo $user->getId(); ?>-org"> + <?php + if ($org) + echo sprintf('<a href="#users/%d/org" + class="user-action">%s</a>', + $user->getId(), $org->getName()); + elseif ($account) + echo sprintf('<a href="#users/%d/org" + class="user-action">Add Organization</a>', + $user->getId()); + else + echo ' '; + ?> + </span> + </td> + </tr> + </table> + </td> + <td width="50%" style="vertical-align:top"> + <table border="0" cellspacing="" cellpadding="4" width="100%"> + <tr> + <th>Status:</th> + <td> <span id="user-<?php echo $user->getId(); + ?>-status"><?php echo $user->getAccountStatus(); ?></span></td> + </tr> + <tr> + <th>Created:</th> + <td><?php echo Format::db_datetime($user->getCreateDate()); ?></td> + </tr> + <tr> + <th>Updated:</th> + <td><?php echo Format::db_datetime($user->getUpdateDate()); ?></td> + </tr> + </table> + </td> + </tr> +</table> +<br> +<div class="clear"></div> +<ul class="tabs"> + <li><a class="active" id="tickets_tab" href="#tickets"><i + class="icon-list-alt"></i> User Tickets</a></li> +</ul> +<div id="tickets"> +<?php +include STAFFINC_DIR . 'templates/tickets.tmpl.php'; +?> +</div> + +<div style="display:none;" class="dialog" id="confirm-action"> + <h3>Please Confirm</h3> + <a class="close" href=""><i class="icon-remove-circle"></i></a> + <hr/> + <p class="confirm-action" style="display:none;" id="banemail-confirm"> + Are you sure want to <b>ban</b> <?php echo $user->getEmail(); ?>? <br><br> + New tickets from the email address will be auto-rejected. + </p> + <p class="confirm-action" style="display:none;" id="confirmlink-confirm"> + Are you sure want to send <b>Account Activation Link</b> to <em><?php echo $user->getEmail()?></em>? + </p> + <p class="confirm-action" style="display:none;" id="pwreset-confirm"> + Are you sure want to send <b>Password Reset Link</b> to <em><?php echo $user->getEmail()?></em>? + </p> + <div>Please confirm to continue.</div> + <form action="users.php?id=<?php echo $user->getId(); ?>" method="post" id="confirm-form" name="confirm-form"> + <?php csrf_token(); ?> + <input type="hidden" name="id" value="<?php echo $user->getId(); ?>"> + <input type="hidden" name="a" value="process"> + <input type="hidden" name="do" id="action" value=""> + <hr style="margin-top:1em"/> + <p class="full-width"> + <span class="buttons" style="float:left"> + <input type="button" value="Cancel" class="close"> + </span> + <span class="buttons" style="float:right"> + <input type="submit" value="OK"> + </span> + </p> + </form> + <div class="clear"></div> +</div> + +<script type="text/javascript"> +$(function() { + $(document).on('click', 'a.user-action', function(e) { + e.preventDefault(); + var url = 'ajax.php/'+$(this).attr('href').substr(1); + $.dialog(url, [201, 204], function (xhr) { + if (xhr.status == 204) + window.location.href = 'users.php'; + else + window.location.href = window.location.href; + }, { + onshow: function() { $('#user-search').focus(); } + }); + return false; + }); +}); +</script> diff --git a/include/staff/users.inc.php b/include/staff/users.inc.php new file mode 100644 index 0000000000000000000000000000000000000000..dab7d730527eddcc74afb20865285af5d154a3a9 --- /dev/null +++ b/include/staff/users.inc.php @@ -0,0 +1,181 @@ +<?php +if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied'); + +$qstr=''; + +$select = 'SELECT user.*, email.address as email '; + +$from = 'FROM '.USER_TABLE.' user ' + . 'JOIN '.USER_EMAIL_TABLE.' email ON (user.id = email.user_id) '; + +$where='WHERE 1 '; + + +if ($_REQUEST['query']) { + + $from .=' LEFT JOIN '.FORM_ENTRY_TABLE.' entry + ON (entry.object_type=\'U\' AND entry.object_id = user.id) + LEFT JOIN '.FORM_ANSWER_TABLE.' value + ON (value.entry_id=entry.id) '; + + $search = db_input(strtolower($_REQUEST['query']), false); + $where .= ' AND ( + email.address LIKE \'%'.$search.'%\' + OR user.name LIKE \'%'.$search.'%\' + OR value.value LIKE \'%'.$search.'%\' + )'; + + $qstr.='&query='.urlencode($_REQUEST['query']); +} + +$sortOptions = array('name' => 'user.name', + 'email' => 'email.address', + 'create' => 'user.created', + 'update' => 'user.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 ?: 'user.name'; + +if ($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])]) + $order = $orderWays[strtoupper($_REQUEST['order'])]; + +$order=$order ?: 'ASC'; +if ($order_column && strpos($order_column,',')) + $order_column = str_replace(','," $order,",$order_column); + +$x=$sort.'_sort'; +$$x=' class="'.strtolower($order).'" '; +$order_by="$order_column $order "; + +$total=db_count('SELECT count(DISTINCT user.id) '.$from.' '.$where); +$page=($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1; +$pageNav=new Pagenate($total,$page,PAGE_LIMIT); +$pageNav->setURL('users.php',$qstr.'&sort='.urlencode($_REQUEST['sort']).'&order='.urlencode($_REQUEST['order'])); +//Ok..lets roll...create the actual query +$qstr.='&order='.($order=='DESC'?'ASC':'DESC'); + +$select .= ', count(DISTINCT ticket.ticket_id) as tickets '; + +$from .= ' LEFT JOIN '.TICKET_TABLE.' ticket ON (ticket.user_id = user.id) '; + + +$query="$select $from $where GROUP BY user.id ORDER BY $order_by LIMIT ".$pageNav->getStart().",".$pageNav->getLimit(); +//echo $query; +?> +<h2>User Directory</h2> +<div style="width:700px; float:left;"> + <form action="users.php" method="get"> + <?php csrf_token(); ?> + <input type="hidden" name="a" value="search"> + <table> + <tr> + <td><input type="text" id="basic-user-search" name="query" size=30 value="<?php echo Format::htmlchars($_REQUEST['query']); ?>" + autocomplete="off" autocorrect="off" autocapitalize="off"></td> + <td><input type="submit" name="basic_search" class="button" value="Search"></td> + <!-- <td> <a href="" id="advanced-user-search">[advanced]</a></td> --> + </tr> + </table> + </form> + </div> + <div style="float:right;text-align:right;padding-right:5px;"> + <b><a href="#users/add" class="Icon newstaff add-user">Add New User</a></b></div> +<div class="clear"></div> +<?php +$showing = $search ? 'Search Results: ' : ''; +$res = db_query($query); +if($res && ($num=db_num_rows($res))) + $showing .= $pageNav->showing(); +else + $showing .= 'No users found!'; +?> +<form action="users.php" method="POST" name="staff" > + <?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="300"><a <?php echo $name_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=name">Name</a></th> + <th width="300"><a <?php echo $email_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=email">Email</a></th> + <th width="100"><a <?php echo $status_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=status">Status</a></th> + <th width="100"><a <?php echo $create_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=create">Created</a></th> + <th width="145"><a <?php echo $update_sort; ?> href="users.php?<?php echo $qstr; ?>&sort=update">Updated</a></th> + </tr> + </thead> + <tbody> + <?php + if($res && db_num_rows($res)): + $ids=($errors && is_array($_POST['ids']))?$_POST['ids']:null; + while ($row = db_fetch_array($res)) { + + $name = new PersonsName($row['name']); + $status = 'Active'; + $sel=false; + if($ids && in_array($row['id'], $ids)) + $sel=true; + ?> + <tr id="<?php echo $row['id']; ?>"> + <td> + <a href="users.php?id=<?php echo $row['id']; ?>"><?php echo $name; ?></a> + + <?php + if ($row['tickets']) + echo sprintf('<i class="icon-fixed-width icon-file-text-alt"></i> + <small>(%d)</small>', $row['tickets']); + ?> + </td> + <td><?php echo $row['email']; ?></td> + <td><?php echo $status; ?></td> + <td><?php echo Format::db_date($row['created']); ?></td> + <td><?php echo Format::db_datetime($row['updated']); ?> </td> + </tr> + <?php + } //end of while. + endif; ?> + <tfoot> + <tr> + <td colspan="5"> </td> + </tr> + </tfoot> +</table> +<?php +if($res && $num): //Show options.. + echo '<div> Page:'.$pageNav->getPageLinks().' </div>'; +endif; +?> +</form> + +<script type="text/javascript"> +$(function() { + $('input#basic-user-search').typeahead({ + source: function (typeahead, query) { + $.ajax({ + url: "ajax.php/users/local?q="+query, + dataType: 'json', + success: function (data) { + typeahead.process(data); + } + }); + }, + onselect: function (obj) { + window.location.href = 'users.php?id='+obj.id; + }, + property: "/bin/true" + }); + + $(document).on('click', 'a.add-user', function(e) { + e.preventDefault(); + $.userLookup('ajax.php/users/add', function (user) { + window.location.href = 'users.php?id='+user.id; + }); + + return false; + }); +}); +</script> + diff --git a/scp/ajax.php b/scp/ajax.php index 9ef736ef7999896da99e8c889e97dde6a7b682d2..b13822d17c0ebefd04d02a89d4af76c79d8c9980 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -64,17 +64,43 @@ $dispatcher = patterns('', )), url('^/users', patterns('ajax.users.php:UsersAjaxAPI', url_get('^$', 'search'), + url_get('^/local$', 'search', array('local')), + url_get('^/remote$', 'search', array('remote')), url_get('^/(?P<id>\d+)$', 'getUser'), url_post('^/(?P<id>\d+)$', 'updateUser'), url_get('^/(?P<id>\d+)/edit$', 'editUser'), url('^/lookup$', 'getUser'), url_get('^/lookup/form$', 'getLookupForm'), url_post('^/lookup/form$', 'addUser'), + url_get('^/add$', 'addUser'), url_get('^/select$', 'selectUser'), url_get('^/select/(?P<id>\d+)$', 'selectUser'), url_get('^/select/auth:(?P<bk>\w+):(?P<id>.+)$', 'addRemoteUser'), + url_get('^/(?P<id>\d+)/register$', 'register'), + url_post('^/(?P<id>\d+)/register$', 'register'), + url_get('^/(?P<id>\d+)/delete$', 'delete'), + url_delete('^/(?P<id>\d+)/delete$', 'delete'), + url_get('^/(?P<id>\d+)/manage(?:/(?P<target>\w+))?$', 'manage'), + url_post('^/(?P<id>\d+)/manage(?:/(?P<target>\w+))?$', 'manage'), + url_get('^/(?P<id>\d+)/org(?:/(?P<orgid>\d+))?$', 'updateOrg'), + url_post('^/(?P<id>\d+)/org$', 'updateOrg'), url_get('^/staff$', 'searchStaff') )), + url('^/orgs', patterns('ajax.orgs.php:OrgsAjaxAPI', + url_get('^$', 'search'), + url_get('^/search$', 'search'), + url_get('^/(?P<id>\d+)$', 'getOrg'), + url_post('^/(?P<id>\d+)$', 'updateOrg'), + url_get('^/(?P<id>\d+)/edit$', 'editOrg'), + url_get('^/lookup/form$', 'lookup'), + url_post('^/lookup/form$', 'addOrg'), + url_get('^/add$', 'addOrg'), + url_post('^/add$', 'addOrg'), + url_get('^/select$', 'selectOrg'), + url_get('^/select/(?P<id>\d+)$', 'selectOrg'), + url_get('^/(?P<id>\d+)/delete$', 'delete'), + url_delete('^/(?P<id>\d+)/delete$', 'delete') + )), url('^/tickets/', patterns('ajax.tickets.php:TicketsAjaxAPI', url_get('^(?P<tid>\d+)/change-user$', 'changeUserForm'), url_post('^(?P<tid>\d+)/change-user$', 'changeUser'), diff --git a/scp/css/scp.css b/scp/css/scp.css index 4500c2dfbeb0a04c7820a08775f148bcef77c5be..50bea7ea4bc26d2cc5634889b0ff35e6c9ec3d34 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -1359,6 +1359,12 @@ time { font-size:0.75em; } +.dialog ul.tabs, .dialog ul.tabs * { + box-sizing: content-box; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; +} + .dialog.draggable h3:hover { cursor: crosshair; } diff --git a/scp/js/scp.js b/scp/js/scp.js index 33839e80c39174391d9d10e9e2c2029a44af9221..2119131553d6c7f48d4601be8f8a529f2d7f3114 100644 --- a/scp/js/scp.js +++ b/scp/js/scp.js @@ -103,6 +103,24 @@ $(document).ready(function(){ return false; }); + $('a.confirm-action').click(function(e) { + $dialog = $('.dialog#confirm-action'); + if ($($(this).attr('href')+'-confirm', $dialog).length) { + e.preventDefault(); + var action = $(this).attr('href').substr(1, $(this).attr('href').length); + + $('input#action', $dialog).val(action); + $('#overlay').show(); + $('.confirm-action', $dialog).hide(); + $('p'+$(this).attr('href')+'-confirm', $dialog) + .show() + .parent('div').show().trigger('click'); + + return false; + } + }); + + if($.browser.msie) { $('.inactive').mouseenter(function() { var elem = $(this); @@ -431,9 +449,12 @@ $(document).ready(function(){ $('#advanced-search').show(); }); - $.dialog = function (url, code, cb, options) { + $.dialog = function (url, codes, cb, options) { options = options||{}; + if (codes && !$.isArray(codes)) + codes = [codes]; + $('.dialog#popup .body').load(url, function () { $('#overlay').show(); $('.dialog#popup').show({ @@ -451,11 +472,12 @@ $(document).ready(function(){ data: $form.serialize(), cache: false, success: function(resp, status, xhr) { - if (xhr && xhr.status == code) { + if (xhr && xhr.status && codes + && $.inArray(xhr.status, codes) != -1) { $('div.body', $dialog).empty(); $dialog.hide(); $('#overlay').hide(); - if(cb) cb(xhr.responseText); + if(cb) cb(xhr); } else { $('div.body', $dialog).html(resp); $('#msg_notice, #msg_error', $dialog).delay(5000).slideUp(); @@ -471,14 +493,23 @@ $(document).ready(function(){ }; $.userLookup = function (url, cb) { - $.dialog(url, 201, function (resp) { - var user = $.parseJSON(resp); - if(cb) cb(user); + $.dialog(url, 201, function (xhr) { + var user = $.parseJSON(xhr.responseText); + if (cb) cb(user); }, { onshow: function() { $('#user-search').focus(); } }); }; + $.orgLookup = function (url, cb) { + $.dialog(url, 201, function (xhr) { + var org = $.parseJSON(xhr.responseText); + if (cb) cb(org); + }, { + onshow: function() { $('#org-search').focus(); } + }); + }; + $('#advanced-search').delegate('#status', 'change', function() { switch($(this).val()) { case 'closed': @@ -558,7 +589,7 @@ $(document).ready(function(){ $(document).on('click.tab', 'ul.tabs li a', function(e) { e.preventDefault(); if ($('.tab_content'+$(this).attr('href')).length) { - $('ul.tabs li a').removeClass('active'); + $('ul.tabs li a', $(this).closest('ul').parent()).removeClass('active'); $(this).addClass('active'); $('.tab_content').hide(); $('.tab_content'+$(this).attr('href')).show(); @@ -569,9 +600,9 @@ $(document).ready(function(){ $(document).on('click', 'a.collaborator, a.collaborators', function(e) { e.preventDefault(); var url = 'ajax.php/'+$(this).attr('href').substr(1); - $.dialog(url, 201, function (resp) { + $.dialog(url, 201, function (xhr) { $('input#emailcollab').show(); - $('#recipients').text(resp); + $('#recipients').text(xhr.responseText); $('.tip_box').remove(); }, { onshow: function() { $('#user-search').focus(); } diff --git a/scp/js/ticket.js b/scp/js/ticket.js index 357031c9e00f4dd506c1d93346f7c0bea3e901df..7199d0829ef136a746413894d3584d3ad66234f1 100644 --- a/scp/js/ticket.js +++ b/scp/js/ticket.js @@ -336,7 +336,7 @@ jQuery(function($) { return false; }); - //ticket status (close & reopen) + //ticket status (close & reopen) xxx: move to backend ticket-action $('a#ticket-close, a#ticket-reopen').click(function(e) { e.preventDefault(); $('#overlay').show(); @@ -344,25 +344,6 @@ jQuery(function($) { return false; }); - //ticket actions confirmation - Delete + more - $('a#ticket-delete, a#ticket-claim, #action-dropdown-more li a:not(.change-user)').click(function(e) { - e.preventDefault(); - if($('.dialog#confirm-action '+$(this).attr('href')+'-confirm').length) { - var action = $(this).attr('href').substr(1, $(this).attr('href').length); - $('.dialog#confirm-action #action').val(action); - $('#overlay').show(); - $('.dialog#confirm-action .confirm-action').hide(); - $('.dialog#confirm-action p'+$(this).attr('href')+'-confirm') - .show() - .parent('div').show().trigger('click'); - - } else { - alert('Unknown action '+$(this).attr('href')+'- get technical help.'); - } - - return false; - }); - $(document).on('change', 'form#reply select#emailreply', function(e) { var $cc = $('form#reply tbody#cc_sec'); if($(this).val() == 0) diff --git a/scp/orgs.php b/scp/orgs.php new file mode 100644 index 0000000000000000000000000000000000000000..0ecb8d1892cbed3f27610139a5a6fb0d4e3a53af --- /dev/null +++ b/scp/orgs.php @@ -0,0 +1,26 @@ +<?php +/********************************************************************* + orgs.php + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2014 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require('staff.inc.php'); +$org = null; +if ($_REQUEST['id']) + $org = Organization::lookup($_REQUEST['id']); + + +$page = $org? 'org-view.inc.php' : 'orgs.inc.php'; +$nav->setTabActive('users'); +require(STAFFINC_DIR.'header.inc.php'); +require(STAFFINC_DIR.$page); +include(STAFFINC_DIR.'footer.inc.php'); +?> diff --git a/scp/users.php b/scp/users.php new file mode 100644 index 0000000000000000000000000000000000000000..3020a91f19fceb60517d817382d28a1cdfb1e2e2 --- /dev/null +++ b/scp/users.php @@ -0,0 +1,81 @@ +<?php +/********************************************************************* + users.php + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2014 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require('staff.inc.php'); +$user = null; +if ($_REQUEST['id'] && !($user=User::lookup($_REQUEST['id']))) + $errors['err'] = 'Unknown or invalid user ID.'; + +if ($_POST) { + switch(strtolower($_POST['do'])) { + case 'update': + if (!$user) { + $errors['err']='Unknown or invalid user.'; + } elseif(($acct = $user->getAccount()) + && !$acct->update($_POST, $errors)) { + $errors['err']='Unable to update user account information'; + } elseif($user->updateInfo($_POST, $errors)) { + $msg='User updated successfully'; + $_REQUEST['a'] = null; + } elseif(!$errors['err']) { + $errors['err']='Unable to update user profile. Correct any error(s) below and try again!'; + } + break; + case 'create': + $form = UserForm::getUserForm()->getForm($_POST); + if (($user = User::fromForm($form))) { + $msg = Format::htmlchars($user->getName()).' added successfully'; + $_REQUEST['a'] = null; + } elseif (!$errors['err']) { + $errors['err'] = 'Unable to add user. Correct any error(s) below and try again.'; + } + break; + case 'confirmlink': + if (!$user || !$user->getAccount()) + $errors['err'] = 'Unknown or invalid user account'; + elseif ($user->getAccount()->isConfirmed()) + $errors['err'] = 'Account is already confirmed'; + elseif ($user->getAccount()->sendConfirmEmail()) + $msg = 'Account activation email sent to '.$user->getEmail(); + else + $errors['err'] = 'Unable to send account activation email - try again!'; + break; + case 'pwreset': + if (!$user || !$user->getAccount()) + $errors['err'] = 'Unknown or invalid user account'; + elseif ($user->getAccount()->sendResetEmail()) + $msg = 'Account password reset email sent to '.$user->getEmail(); + else + $errors['err'] = 'Unable to send account password reset email - try again!'; + break; + case 'mass_process': + if (!$_POST['ids'] || !is_array($_POST['ids']) || !count($_POST['ids'])) { + $errors['err'] = 'You must select at least one user member.'; + } else { + $errors['err'] = "Coming soon!"; + } + break; + default: + $errors['err'] = 'Unknown action/command'; + break; + } +} + +$page = $user? 'user-view.inc.php' : 'users.inc.php'; + +$nav->setTabActive('users'); +require(STAFFINC_DIR.'header.inc.php'); +require(STAFFINC_DIR.$page); +include(STAFFINC_DIR.'footer.inc.php'); +?> diff --git a/setup/cli/modules/org.php b/setup/cli/modules/org.php new file mode 100644 index 0000000000000000000000000000000000000000..c47e5cd7d628d1638d922e1f28a09ac9cc7599a9 --- /dev/null +++ b/setup/cli/modules/org.php @@ -0,0 +1,72 @@ +<?php +require_once dirname(__file__) . "/class.module.php"; +require_once dirname(__file__) . "/../cli.inc.php"; + +class OrganizationManager extends Module { + var $prologue = 'CLI organization manager'; + + var $arguments = array( + 'action' => array( + 'help' => 'Action to be performed', + 'options' => array( + 'import' => 'Import organizations to the system', + 'export' => 'Export organizations from the system', + ), + ), + ); + + + var $options = array( + 'file' => array('-f', '--file', 'metavar'=>'path', + 'help' => 'File or stream to process'), + ); + + var $stream; + + function run($args, $options) { + + Bootstrap::connect(); + + switch ($args['action']) { + case 'import': + if (!$options['file']) + $this->fail('Import CSV file required!'); + elseif (!($this->stream = fopen($options['file'], 'rb'))) + $this->fail("Unable to open input file [{$options['file']}]"); + + //Read the header (if any) + if (($data = fgetcsv($this->stream, 1000, ","))) { + if (strcasecmp($data[0], 'name')) + fseek($this->stream, 0); // We don't have an header! + else; + // TODO: process the header here to figure out the columns + // for now we're assuming one column of Name + } + + while (($data = fgetcsv($this->stream, 1000, ",")) !== FALSE) { + if (!$data[0]) + $this->stderr->write('Invalid data format: Name + required'); + elseif (!Organization::fromVars(array('name' => $data[0], 'email'))) + $this->stderr->write('Unable to import record: '.print_r($data, true)); + } + + break; + case 'export': + $stream = $options['file'] ?: 'php://stdout'; + if (!($this->stream = fopen($stream, 'c'))) + $this->fail("Unable to open output file [{$options['file']}]"); + + fputcsv($this->stream, array('Name')); + foreach (Organization::objects() as $org) + fputcsv($this->stream, + array((string) $org->getName())); + break; + default: + $this->stderr->write('Unknown action!'); + } + @fclose($this->stream); + } +} +Module::register('org', 'OrganizationManager'); +?>