Compare commits
82 Commits
develop
...
develop-ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6e3376bdb | ||
|
|
d5ee0df82c | ||
|
|
f525d39557 | ||
|
|
462f8df8b0 | ||
|
|
faf0539118 | ||
|
|
7acfcccdc7 | ||
|
|
38232f2edb | ||
|
|
33f2a47c1d | ||
|
|
e36975f5bb | ||
|
|
b23d2b15d8 | ||
|
|
97c0e727f8 | ||
|
|
e54f6f3e24 | ||
|
|
d6ee1a8a49 | ||
|
|
5b5c22160f | ||
|
|
0065c3b154 | ||
|
|
3dc3ed97e9 | ||
|
|
cf16ca683b | ||
|
|
0895654cfc | ||
|
|
9aabb77563 | ||
|
|
143176ae2f | ||
|
|
f02edd94bc | ||
|
|
52825f0fce | ||
|
|
b1b970b8ed | ||
|
|
cbec8b5c36 | ||
|
|
059e3283f5 | ||
|
|
7527ea7ef6 | ||
|
|
36e614c397 | ||
|
|
40b39e6be9 | ||
|
|
791a45eaf5 | ||
|
|
ca9817c9e7 | ||
|
|
4e86313ce1 | ||
|
|
ca8117610a | ||
|
|
07b2373f2e | ||
|
|
9902ac2270 | ||
|
|
642514692c | ||
|
|
3722fc509b | ||
|
|
484023d099 | ||
|
|
7f0602a3ba | ||
|
|
4489b2fe3c | ||
|
|
6fdc111812 | ||
|
|
f306ce887e | ||
|
|
6eae7e682d | ||
|
|
3a8ea4b25b | ||
|
|
46033d11c0 | ||
|
|
49b2f1b355 | ||
|
|
4ad7e95a2f | ||
|
|
e4346d26e0 | ||
|
|
5fbd762483 | ||
|
|
a2a09d2754 | ||
|
|
709b55fee2 | ||
|
|
003ac86231 | ||
|
|
4c9968bd11 | ||
|
|
ea0937384d | ||
|
|
c359d12e43 | ||
|
|
da2fd378f5 | ||
|
|
2731074729 | ||
|
|
0fd63b4284 | ||
|
|
bfd5da8e20 | ||
|
|
4aef938556 | ||
|
|
0ce2499fbd | ||
|
|
cb13b82a73 | ||
|
|
7f0130bff6 | ||
|
|
c605c7d934 | ||
|
|
ff06cc3558 | ||
|
|
a62288938c | ||
|
|
c1f6b98949 | ||
|
|
eec0f4fb52 | ||
|
|
e9b12f3101 | ||
|
|
8f4fe1892f | ||
|
|
36ef030d34 | ||
|
|
5a0a844de5 | ||
|
|
9d46c84bcb | ||
|
|
3f08e626d8 | ||
|
|
a1b78264d7 | ||
|
|
4b6bc735e8 | ||
|
|
ea8610348a | ||
|
|
cff12f7078 | ||
|
|
4ef752c68e | ||
|
|
5dd0a34f13 | ||
|
|
744676bbca | ||
|
|
e1aba7c2c7 | ||
|
|
4f7d23c855 |
22
WHATSNEW.md
22
WHATSNEW.md
@@ -1,3 +1,25 @@
|
||||
osTicket v1.15
|
||||
==============
|
||||
### Enhancements
|
||||
* Change dept_id and priority_id fron tinyint to int (e54f6f3)
|
||||
* csrf: Add ability to rotate token (36e614c)
|
||||
* Feature: Agent/Department Visibility (5fbd762, e4346d2, 4ad7e95, 49b2f1b, 46033d1, 3a8ea4b, 6eae7e6, f306ce8, 6fdc111, 4489b2f, 7f0602a, 484023d, 3722fc5, 6425146, 9902ac2, 07b2373, ca81176, 4e86313)
|
||||
* db: Latest Indexes (da2fd37, 2731074, c359d12, ea09373, 4c9968b)
|
||||
* SLA Plan Search Field (0fd63b4)
|
||||
* 2FA Backends (5dd0a34, 4ef752c, cff12f7, ea86103, 4b6bc73, a1b7826, 3f08e62, 9d46c84, 8f4fe18)
|
||||
* Password Policy Revisited (e1aba7c, 744676b)
|
||||
|
||||
### Improvements
|
||||
* Issue: Missing Events (38232f2)
|
||||
* Issue: 2FA Upgrade (0065c3b)
|
||||
* Ticket From Email (f02edd9)
|
||||
* Issue: User Custom Dept Field (52825f0)
|
||||
* Staff: Password Change (7527ea7)
|
||||
* oops: Indexes Patch Schema (709b55f)
|
||||
* CsvImporter: Skip Byte Order Mark (BOM) if present (bfd5da8)
|
||||
* Oops: Method Inheritance Compatibility (cb13b82)
|
||||
* issue: Form Instructions Translation (4f7d23c)
|
||||
|
||||
osTicket v1.14.4
|
||||
================
|
||||
### Enhancements
|
||||
|
||||
@@ -66,7 +66,16 @@ elseif ($_POST) {
|
||||
$errors['passwd1'] = __('New password is required');
|
||||
elseif (!$_POST['backend'] && $_POST['passwd2'] != $_POST['passwd1'])
|
||||
$errors['passwd1'] = __('Passwords do not match');
|
||||
else {
|
||||
try {
|
||||
UserAccount::checkPassword($_POST['passwd1']);
|
||||
} catch (BadPassword $ex) {
|
||||
$errors['passwd1'] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors)
|
||||
$errors['err'] = $errors['err'] ?: __('Unable to register account. See messages below');
|
||||
// XXX: The email will always be in use already if a guest is logged in
|
||||
// and is registering for an account. Instead,
|
||||
elseif (($addr = $user_form->getField('email')->getClean())
|
||||
|
||||
@@ -340,7 +340,7 @@ define('CLI_DIR', INCLUDE_DIR.'cli/');
|
||||
|
||||
#Current version && schema signature (Changes from version to version)
|
||||
define('GIT_VERSION','$git');
|
||||
define('MAJOR_VERSION', '1.14');
|
||||
define('MAJOR_VERSION', '1.15');
|
||||
define('THIS_VERSION', MAJOR_VERSION.'-git'); //Shown on admin panel
|
||||
//Path separator
|
||||
if(!defined('PATH_SEPARATOR')){
|
||||
|
||||
@@ -34,7 +34,8 @@ class DynamicFormsAjaxAPI extends AjaxController {
|
||||
ob_start();
|
||||
$form->getForm($_SESSION[':form-data'])->render(array(
|
||||
'staff' => !$client,
|
||||
'mode' => 'create'));
|
||||
'mode' => 'create',
|
||||
'filterVisibility' => true));
|
||||
$html .= ob_get_clean();
|
||||
ob_start();
|
||||
print $form->getMedia();
|
||||
|
||||
@@ -55,7 +55,7 @@ class SearchAjaxAPI extends AjaxController {
|
||||
$this->_tryAgain($search);
|
||||
}
|
||||
|
||||
function addField($name) {
|
||||
function addField($name, $filterVisibility=null) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!$thisstaff)
|
||||
@@ -68,7 +68,9 @@ class SearchAjaxAPI extends AjaxController {
|
||||
if (!($F = $searchable[$name]))
|
||||
Http::response(404, 'No such field: ', print_r($name, true));
|
||||
|
||||
$fields = SavedSearch::getSearchField($F, $name);
|
||||
$filterVisibility = ($filterVisibility == 'true') ? true : false;
|
||||
$options = array('filterVisibility' => $filterVisibility);
|
||||
$fields = SavedSearch::getSearchField($F, $name, $options);
|
||||
$form = new AdvancedSearchForm($fields);
|
||||
// Check the box to search the field by default
|
||||
if ($F = $form->getField("{$name}+search"))
|
||||
@@ -151,8 +153,10 @@ class SearchAjaxAPI extends AjaxController {
|
||||
}
|
||||
|
||||
function _tryAgain($search, $form=null, $errors=array(), $info=array()) {
|
||||
if (!$form)
|
||||
$form = $search->getForm();
|
||||
if (!$form) {
|
||||
$filterVisibility = true;
|
||||
$form = $search->getForm(null, null, $filterVisibility);
|
||||
}
|
||||
include STAFFINC_DIR . 'templates/advanced-search.tmpl.php';
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class StaffAjaxAPI extends AjaxController {
|
||||
try {
|
||||
// Validate password
|
||||
if (!$clean['welcome_email'])
|
||||
PasswordPolicy::checkPassword($clean['passwd1'], null);
|
||||
Staff::checkPassword($clean['passwd1'], null);
|
||||
if ($id == 0) {
|
||||
// Stash in the session later when creating the user
|
||||
$_SESSION['new-agent-passwd'] = $clean;
|
||||
@@ -56,8 +56,8 @@ class StaffAjaxAPI extends AjaxController {
|
||||
Http::response(201, 'Successfully updated');
|
||||
}
|
||||
catch (BadPassword $ex) {
|
||||
$passwd1 = $form->getField('passwd1');
|
||||
$passwd1->addError($ex->getMessage());
|
||||
if ($passwd1 = $form->getField('passwd1'))
|
||||
$passwd1->addError($ex->getMessage());
|
||||
}
|
||||
catch (PasswordUpdateFailed $ex) {
|
||||
$errors['err'] = __('Password update failed:').' '.$ex->getMessage();
|
||||
@@ -108,8 +108,8 @@ class StaffAjaxAPI extends AjaxController {
|
||||
}
|
||||
}
|
||||
catch (BadPassword $ex) {
|
||||
$passwd1 = $form->getField('passwd1');
|
||||
$passwd1->addError($ex->getMessage());
|
||||
if ($passwd1 = $form->getField('passwd1'))
|
||||
$passwd1->addError($ex->getMessage());
|
||||
}
|
||||
catch (PasswordUpdateFailed $ex) {
|
||||
$errors['err'] = __('Password update failed:').' '.$ex->getMessage();
|
||||
@@ -232,4 +232,91 @@ class StaffAjaxAPI extends AjaxController {
|
||||
'code' => $code,
|
||||
));
|
||||
}
|
||||
|
||||
function configure2FA($staffId, $id=0) {
|
||||
global $thisstaff;
|
||||
if (!$thisstaff)
|
||||
Http::response(403, 'Agent login required');
|
||||
if ($staffId != $thisstaff->getId())
|
||||
Http::response(403, 'Access denied');
|
||||
if ($id && !($auth= Staff2FABackend::lookup($id)))
|
||||
Http::response(404, 'Unknown 2FA');
|
||||
|
||||
$staff = $thisstaff;
|
||||
$info = array();
|
||||
if ($auth) {
|
||||
// Simple state machine to manage settings and verification
|
||||
$state = @$_POST['state'] ?: 'validate';
|
||||
switch ($state) {
|
||||
case 'verify':
|
||||
try {
|
||||
$form = $auth->getInputForm($_POST);
|
||||
if ($_POST && $form
|
||||
&& $form->isValid()
|
||||
&& $auth->validate($form, $staff)) {
|
||||
// Mark the settings as verified
|
||||
if (($config = $staff->get2FAConfig($auth->getId()))) {
|
||||
$config['verified'] = time();
|
||||
$staff->updateConfig(array(
|
||||
$auth->getId() => JsonDataEncoder::encode($config)));
|
||||
}
|
||||
// We're done here
|
||||
$auth = null;
|
||||
$info['notice'] = __('Setup completed successfully');
|
||||
} else {
|
||||
$info['error'] = __('Unable to verify the token - try again!');
|
||||
}
|
||||
} catch (ExpiredOTP $ex) {
|
||||
// giving up cleanly
|
||||
$info['error'] = $ex->getMessage();
|
||||
$auth = null;
|
||||
}
|
||||
break;
|
||||
case 'validate':
|
||||
default:
|
||||
$config = $staff->get2FAConfig($auth->getId());
|
||||
$vars = $_POST ?: $config['config'] ?: array('email' => $staff->getEmail());
|
||||
$form = $auth->getSetupForm($vars);
|
||||
if ($_POST && $form && $form->isValid()) {
|
||||
if ($config['config'] && $config['config']['external2fa'])
|
||||
$external2fa = true;
|
||||
|
||||
// Save the setting based on setup form
|
||||
$clean = $form->getClean();
|
||||
if (!$external2fa) {
|
||||
$config = ['config' => $clean, 'verified' => 0];
|
||||
$staff->updateConfig(array(
|
||||
$auth->getId() => JsonDataEncoder::encode($config)));
|
||||
}
|
||||
|
||||
// Send verification token to the user
|
||||
if ($token=$auth->send($staff)) {
|
||||
// Transition to verify state
|
||||
$form = $auth->getInputForm($vars);
|
||||
$state = 'verify';
|
||||
$info['notice'] = __('Token sent to you!');
|
||||
} else {
|
||||
// Generic error TODO: better wording
|
||||
$info['error'] = __('Error sending Token - double check entry');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include STAFFINC_DIR . 'templates/2fas.tmpl.php';
|
||||
}
|
||||
|
||||
function reset2fA($staffId) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!$thisstaff)
|
||||
Http::response(403, 'Agent login required');
|
||||
if (!$thisstaff->isAdmin())
|
||||
Http::response(403, 'Access denied');
|
||||
|
||||
$default_2fa = ConfigItem::getConfigsByNamespace('staff.'.$staffId, 'default_2fa');
|
||||
|
||||
if ($default_2fa)
|
||||
$default_2fa->delete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -668,7 +668,7 @@ class TasksAjaxAPI extends AjaxController {
|
||||
|
||||
if (!$task->checkStaffPerm($thisstaff, Task::PERM_ASSIGN)
|
||||
|| !($form=$task->getAssignmentForm($_POST, array(
|
||||
'target' => $target))))
|
||||
'target' => $target, 'filterVisibility' => true))))
|
||||
Http::response(403, __('Permission denied'));
|
||||
|
||||
$errors = array();
|
||||
|
||||
@@ -1086,13 +1086,7 @@ class TicketsAjaxAPI extends AjaxController {
|
||||
|
||||
$depts = $tickets->values_flat('dept_id');
|
||||
}
|
||||
$members = Staff::objects()
|
||||
->distinct('staff_id')
|
||||
->filter(array(
|
||||
'onvacation' => 0,
|
||||
'isactive' => 1,
|
||||
)
|
||||
);
|
||||
$members = $thisstaff->getDeptAgents(array('available' => true));
|
||||
|
||||
if ($depts) {
|
||||
$all_agent_depts = Dept::objects()->filter(
|
||||
|
||||
297
include/class.2fa.php
Normal file
297
include/class.2fa.php
Normal file
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* TwoFactorAuthentication backend
|
||||
*
|
||||
* Provides the basis of abstracting 2fa backends
|
||||
|
||||
* The authentication backend should define a validate() method which
|
||||
* receives a user and OPT.
|
||||
*/
|
||||
abstract class TwoFactorAuthenticationBackend {
|
||||
// Global registry
|
||||
static protected $registry = array();
|
||||
// Grace period in minutes before OTP is expired and user logged out
|
||||
// It's hardcoded to 6 minutes here but downstream backends can make it
|
||||
// configurable
|
||||
protected $timeout = 6;
|
||||
|
||||
// Maximum number of validation attempts before the user is logged out
|
||||
// It's hardcoded to 3 attempts here but downstream backends can make it
|
||||
// configurable
|
||||
protected $maxstrikes = 3;
|
||||
|
||||
// Base properties
|
||||
static $id;
|
||||
static $name;
|
||||
static $desc;
|
||||
|
||||
// Forms
|
||||
private $_setupform;
|
||||
private $_inputform;
|
||||
|
||||
|
||||
// Send OTP to user specified and stash it
|
||||
abstract function send($user);
|
||||
// validate OTP provided by user
|
||||
abstract function validate($form, $user);
|
||||
|
||||
function getId() {
|
||||
return static::$id;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return __(static::$name);
|
||||
}
|
||||
|
||||
function getDescription() {
|
||||
return __(static::$desc);
|
||||
}
|
||||
|
||||
function getTimeout() {
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
function getMaxStrikes() {
|
||||
return $this->maxstrikes;
|
||||
}
|
||||
|
||||
protected function getSetupOptions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function getInputOptions() {
|
||||
return array();
|
||||
}
|
||||
|
||||
// stash OTP info in the session
|
||||
protected function store($otp) {
|
||||
$store = &$_SESSION['_2fa'][$this->getId()];
|
||||
$store = ['otp' => $otp, 'time' => time(), 'strikes' => 0];
|
||||
return $store;
|
||||
}
|
||||
|
||||
// Validate OPT
|
||||
// On strict mode check strikes and timeout
|
||||
protected function _validate($otp, $strict=true) {
|
||||
$store = &$_SESSION['_2fa'][$this->getId()];
|
||||
// Track and check the attempts
|
||||
$store['strikes'] += 1;
|
||||
if ($strict && $store['strikes'] > $this->getMaxStrikes())
|
||||
throw new ExpiredOTP(__('Too many attempts'));
|
||||
|
||||
// Check timeout - if expired throw an exception.
|
||||
if ($strict
|
||||
&& ($timeout=$this->getTimeout())
|
||||
&& ($store['time']+($timeout*60)) < time())
|
||||
throw new ExpiredOTP(__('Expired OTP'));
|
||||
|
||||
// Check the OTP
|
||||
return (!strcmp($store['otp'], $otp));
|
||||
}
|
||||
|
||||
// Called on a successfull validation for house keeping e.g clear 2fa
|
||||
// flags
|
||||
protected function onValidate($user) {
|
||||
$user->clear2FA();
|
||||
}
|
||||
|
||||
// Get a form the user uses to setup 2fa
|
||||
function getSetupForm($data=null) {
|
||||
if (!$this->_setupForm) {
|
||||
$this->_setupForm = new SimpleForm($this->getSetupOptions(), $data);
|
||||
}
|
||||
return $this->_setupForm;
|
||||
}
|
||||
|
||||
// Get a form the user uses to input OTP
|
||||
function getInputForm($data=null) {
|
||||
if (!$this->_inputForm) {
|
||||
$this->_inputForm = new SimpleForm($this->getInputOptions(), $data);
|
||||
}
|
||||
return $this->_inputForm;
|
||||
}
|
||||
|
||||
static function register($class) {
|
||||
if (is_string($class) && class_exists($class))
|
||||
$class = new $class();
|
||||
|
||||
if (!is_object($class)
|
||||
|| !($class instanceof TwoFactorAuthenticationBackend))
|
||||
return false;
|
||||
|
||||
return static::_register($class);
|
||||
}
|
||||
|
||||
static function _register($class) {
|
||||
if (isset(static::$registry[$class::$id]))
|
||||
return false;
|
||||
|
||||
static::$registry[$class::$id] = $class;
|
||||
}
|
||||
|
||||
static function allRegistered() {
|
||||
return static::$registry;
|
||||
}
|
||||
|
||||
static function getBackend($id) {
|
||||
|
||||
if ($id
|
||||
&& ($backends = static::allRegistered())
|
||||
&& isset($backends[$id]))
|
||||
return $backends[$id];
|
||||
}
|
||||
|
||||
static function lookup($id) {
|
||||
return static::getBackend($id);
|
||||
}
|
||||
}
|
||||
|
||||
class ExpiredOTP extends Exception {}
|
||||
|
||||
/*
|
||||
* user type container classes to aid in registry segmentation
|
||||
*
|
||||
*/
|
||||
abstract class Staff2FABackend extends TwoFactorAuthenticationBackend {
|
||||
static private $_registry = array();
|
||||
|
||||
static function _register($class) {
|
||||
if (isset(static::$_registry[$class::$id]))
|
||||
return false;
|
||||
|
||||
static::$_registry[$class::$id] = $class;
|
||||
}
|
||||
|
||||
static function allRegistered() {
|
||||
return array_merge(self::$_registry, parent::allRegistered());
|
||||
}
|
||||
|
||||
|
||||
abstract function send($user);
|
||||
abstract function validate($form, $user);
|
||||
}
|
||||
|
||||
abstract class User2FABackend extends TwoFactorAuthenticationBackend {
|
||||
static private $_registry = array();
|
||||
|
||||
static function _register($class) {
|
||||
if (isset(static::$_registry[$class::$id]))
|
||||
return false;
|
||||
|
||||
static::$_registry[$class::$id] = $class;
|
||||
}
|
||||
|
||||
static function allRegistered() {
|
||||
return array_merge(self::$_registry, parent::allRegistered());
|
||||
}
|
||||
|
||||
abstract function send($user);
|
||||
abstract function validate($form, $user);
|
||||
}
|
||||
|
||||
/*
|
||||
* Email2FABackend
|
||||
*
|
||||
* Email based two factor authentication.
|
||||
*
|
||||
* This is the default 2FA that works out of the box once users configure
|
||||
* it.
|
||||
*
|
||||
*/
|
||||
|
||||
class Email2FABackend extends TwoFactorAuthenticationBackend {
|
||||
static $id = "2fa-email";
|
||||
static $name = /* @trans */ 'Email';
|
||||
static $desc = /* @trans */ 'Verification codes are sent by email';
|
||||
|
||||
protected function getSetupOptions() {
|
||||
return array(
|
||||
'email' => new TextboxField(array(
|
||||
'id'=>2, 'label'=>__('Email Address'), 'required'=>true, 'default'=>'',
|
||||
'validator'=>'email', 'hint'=>__('Valid email address'),
|
||||
'configuration'=>array('size'=>40, 'length'=>40),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
protected function getInputOptions() {
|
||||
return array(
|
||||
'token' => new TextboxField(array(
|
||||
'id'=>1, 'label'=>__('Verification Code'), 'required'=>true, 'default'=>'',
|
||||
'validator'=>'number',
|
||||
'hint'=>__('Please enter the code you were sent'),
|
||||
'configuration'=>array(
|
||||
'size'=>40, 'length'=>40,
|
||||
'autocomplete' => 'one-time-code',
|
||||
'inputmode' => 'numeric',
|
||||
'pattern' => '[0-9]*',
|
||||
'validator-error' => __('Invalid Code format'),
|
||||
),
|
||||
)),
|
||||
);
|
||||
}
|
||||
|
||||
function validate($form, $user) {
|
||||
// Make sure form is valid and token exists
|
||||
if (!($form->isValid()
|
||||
&& ($clean=$form->getClean())
|
||||
&& $clean['token']))
|
||||
return false;
|
||||
|
||||
// upstream validation might throw an exception due to expired token
|
||||
// or too many attempts (timeout). It's the responsibility of the
|
||||
// caller to catch and handle such exceptions.
|
||||
if (!$this->_validate($clean['token']))
|
||||
return false;
|
||||
|
||||
// Validator doesn't do house cleaning - it's our responsibility
|
||||
$this->onValidate($user);
|
||||
return true;
|
||||
}
|
||||
|
||||
function send($user) {
|
||||
global $ost, $cfg;
|
||||
|
||||
// Get backend configuration for this user
|
||||
if (!$cfg || !($info = $user->get2FAConfig($this->getId())))
|
||||
return false;
|
||||
|
||||
// Email to send the OTP via
|
||||
if (!($email = $cfg->getAlertEmail() ?: $cfg->getDefaultEmail()))
|
||||
return false;
|
||||
|
||||
// Generate OTP
|
||||
$otp = Misc::randNumber(6);
|
||||
// Stash it in the session
|
||||
$this->store($otp);
|
||||
|
||||
$template = 'email2fa-staff';
|
||||
$content = Page::lookupByType($template);
|
||||
|
||||
if (!$content)
|
||||
return new BaseError(/* @trans */ 'Unable to retrieve two factor authentication email template');
|
||||
|
||||
$vars = array(
|
||||
'url' => $ost->getConfig()->getBaseUrl(),
|
||||
'otp' => $otp,
|
||||
'staff' => $user,
|
||||
'recipient' => $user,
|
||||
);
|
||||
|
||||
$lang = $user->lang ?: $user->getExtraAttr('browser_lang');
|
||||
$msg = $ost->replaceTemplateVariables(array(
|
||||
'subj' => $content->getLocalName($lang),
|
||||
'body' => $content->getLocalBody($lang),
|
||||
), $vars);
|
||||
|
||||
$email->send($user->getEmail(), Format::striptags($msg['subj']),
|
||||
$msg['body']);
|
||||
|
||||
// MD5 here is not meant to be secure here - just done to avoid plain leaks
|
||||
return md5($otp);
|
||||
}
|
||||
}
|
||||
// Register email2fa for both agents and users (parent class)
|
||||
TwoFactorAuthenticationBackend::register('Email2FABackend');
|
||||
?>
|
||||
@@ -1,16 +1,23 @@
|
||||
<?php
|
||||
|
||||
require_once(INCLUDE_DIR.'class.2fa.php');
|
||||
|
||||
interface AuthenticatedUser {
|
||||
/* PHP 5.3 < 5.3.8 will crash with some abstract inheritance issue
|
||||
* ------------------------------------------------------------
|
||||
// Get basic information
|
||||
function getId();
|
||||
function getUsername();
|
||||
function getUserType();
|
||||
|
||||
|
||||
// Get password reset timestamp
|
||||
function getPasswdResetTimestamp();
|
||||
|
||||
//Backend used to authenticate the user
|
||||
function getAuthBackend();
|
||||
|
||||
// Get 2FA Backend
|
||||
function get2FABackend();
|
||||
|
||||
//Authentication key
|
||||
function setAuthKey($key);
|
||||
|
||||
@@ -22,7 +29,6 @@ interface AuthenticatedUser {
|
||||
// Signal method to allow performing extra things when a user is logged
|
||||
// into the sysem
|
||||
function onLogin($bk);
|
||||
*/
|
||||
}
|
||||
|
||||
abstract class BaseAuthenticatedUser
|
||||
@@ -35,9 +41,17 @@ implements AuthenticatedUser {
|
||||
abstract function getUsername();
|
||||
abstract function getUserType();
|
||||
|
||||
// Get password reset timestamp
|
||||
function getPasswdResetTimestamp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
//Backend used to authenticate the user
|
||||
abstract function getAuthBackend();
|
||||
|
||||
// Get 2FA Backend
|
||||
abstract function get2FABackend();
|
||||
|
||||
//Authentication key
|
||||
function setAuthKey($key) {
|
||||
$this->authkey = $key;
|
||||
@@ -350,6 +364,15 @@ abstract class AuthenticationBackend {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get supported password policies for the backend.
|
||||
*
|
||||
*/
|
||||
function getPasswordPolicies($user=null) {
|
||||
return PasswordPolicy::allActivePolicies();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the backend to update the password for a user. This method is
|
||||
* the main entry for password updates so that password policies can be
|
||||
@@ -361,7 +384,9 @@ abstract class AuthenticationBackend {
|
||||
* PasswordUpdateFailed — if backend failed to update the password
|
||||
*/
|
||||
function setPassword($user, $password, $current=false) {
|
||||
PasswordPolicy::checkPassword($password, $current);
|
||||
foreach ($this->getPasswordPolicies($user) as $P)
|
||||
$P->onSet($password, $current);
|
||||
|
||||
$rv = $this->syncPassword($user, $password);
|
||||
if ($rv) {
|
||||
$info = array('password' => $password, 'current' => $current);
|
||||
@@ -370,6 +395,17 @@ abstract class AuthenticationBackend {
|
||||
return $rv;
|
||||
}
|
||||
|
||||
/*
|
||||
* Request the backend to check the policies for a just logged
|
||||
* in user.
|
||||
* Throws: BadPassword & ExpiredPassword - for password related failures
|
||||
*/
|
||||
function checkPolicies($user, $password) {
|
||||
// Password policies
|
||||
foreach ($this->getPasswordPolicies($user) as $P)
|
||||
$P->onLogin($user, $password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the backend to update the user's password with the password
|
||||
* given. This method should only be used if the backend advertises
|
||||
@@ -454,6 +490,18 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
|
||||
return in_array($bk::$id, array_map('strtolower', $backends));
|
||||
}
|
||||
|
||||
function getPasswordPolicies($user=null) {
|
||||
global $cfg;
|
||||
$policies = PasswordPolicy::allActivePolicies();
|
||||
if ($cfg && ($policy = $cfg->getStaffPasswordPolicy())) {
|
||||
foreach ($policies as $P)
|
||||
if ($policy == $P::$id)
|
||||
return array($P);
|
||||
}
|
||||
|
||||
return $policies;
|
||||
}
|
||||
|
||||
function getAllowedBackends($userid) {
|
||||
|
||||
$backends =array();
|
||||
@@ -491,24 +539,32 @@ abstract class StaffAuthenticationBackend extends AuthenticationBackend {
|
||||
$agent = Staff::lookup($staff->getId());
|
||||
$type = array('type' => 'login');
|
||||
Signal::send('person.login', $agent, $type);
|
||||
|
||||
// Check if the agent has 2fa enabled
|
||||
$auth2fa = null;
|
||||
if (($_2fa = $staff->get2FABackend())
|
||||
&& ($token=$_2fa->send($staff))) {
|
||||
$auth2fa = sprintf('%s:%s:%s',
|
||||
$_2fa->getId(), md5($token.$staff->getId()), time());
|
||||
}
|
||||
|
||||
// Tag the authkey.
|
||||
$authkey = $bk::$id.':'.$authkey;
|
||||
|
||||
// Now set session crap and lets roll baby!
|
||||
$authsession = &$_SESSION['_auth']['staff'];
|
||||
|
||||
$authsession = array(); //clear.
|
||||
$authsession['id'] = $staff->getId();
|
||||
$authsession['key'] = $authkey;
|
||||
$authsession['2fa'] = $auth2fa;
|
||||
|
||||
$staff->setAuthKey($authkey);
|
||||
$staff->refreshSession(true); //set the hash.
|
||||
|
||||
Signal::send('auth.login.succeeded', $staff);
|
||||
|
||||
if ($bk->supportsInteractiveAuthentication())
|
||||
$staff->cancelResetTokens();
|
||||
|
||||
|
||||
// Update last-used language, login time, etc
|
||||
$staff->onLogin($bk);
|
||||
|
||||
@@ -642,6 +698,19 @@ abstract class UserAuthenticationBackend extends AuthenticationBackend {
|
||||
return array_merge(self::$_registry, parent::allRegistered());
|
||||
}
|
||||
|
||||
|
||||
function getPasswordPolicies($user=null) {
|
||||
global $cfg;
|
||||
$policies = PasswordPolicy::allActivePolicies();
|
||||
if ($cfg && ($policy = $cfg->getClientPasswordPolicy())) {
|
||||
foreach ($policies as $P)
|
||||
if ($policy == $P::$id)
|
||||
return array($P);
|
||||
}
|
||||
|
||||
return $policies;
|
||||
}
|
||||
|
||||
function getAllowedBackends($userid) {
|
||||
$backends = array();
|
||||
$sql = 'SELECT A1.backend FROM '.USER_ACCOUNT_TABLE
|
||||
@@ -1008,21 +1077,18 @@ class UserAuthStrikeBackend extends AuthStrikeBackend {
|
||||
UserAuthenticationBackend::register('UserAuthStrikeBackend');
|
||||
|
||||
|
||||
class osTicketAuthentication extends StaffAuthenticationBackend {
|
||||
class osTicketStaffAuthentication extends StaffAuthenticationBackend {
|
||||
static $name = "Local Authentication";
|
||||
static $id = "local";
|
||||
|
||||
function authenticate($username, $password) {
|
||||
if (($user = StaffSession::lookup($username)) && $user->getId() &&
|
||||
$user->check_passwd($password)) {
|
||||
|
||||
//update last login && password reset stuff.
|
||||
$sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() ';
|
||||
if($user->isPasswdResetDue() && !$user->isAdmin())
|
||||
$sql.=',change_passwd=1';
|
||||
$sql.=' WHERE staff_id='.db_input($user->getId());
|
||||
db_query($sql);
|
||||
|
||||
try {
|
||||
$this->checkPolicies($user, $password);
|
||||
} catch (BadPassword | ExpiredPassword $ex) {
|
||||
$user->change_passwd = 1;
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
@@ -1035,8 +1101,12 @@ class osTicketAuthentication extends StaffAuthenticationBackend {
|
||||
$staff->passwd = Passwd::hash($password);
|
||||
}
|
||||
|
||||
static function checkPassword($new, $current) {
|
||||
PasswordPolicy::checkPassword($new, $current, new self());
|
||||
}
|
||||
|
||||
}
|
||||
StaffAuthenticationBackend::register('osTicketAuthentication');
|
||||
StaffAuthenticationBackend::register('osTicketStaffAuthentication');
|
||||
|
||||
class PasswordResetTokenBackend extends StaffAuthenticationBackend {
|
||||
static $id = "pwreset.staff";
|
||||
@@ -1253,11 +1323,15 @@ class osTicketClientAuthentication extends UserAuthenticationBackend {
|
||||
if (($client = new ClientSession(new EndUser($acct->getUser())))
|
||||
&& !$client->getId())
|
||||
return false;
|
||||
elseif (!$acct->checkPassword($password))
|
||||
elseif (!$acct->check_passwd($password))
|
||||
return false;
|
||||
else
|
||||
return $client;
|
||||
}
|
||||
|
||||
static function checkPassword($new, $current) {
|
||||
PasswordPolicy::checkPassword($new, $current, new self());
|
||||
}
|
||||
}
|
||||
UserAuthenticationBackend::register('osTicketClientAuthentication');
|
||||
|
||||
@@ -1329,26 +1403,49 @@ UserAuthenticationBackend::register('ClientAcctConfirmationTokenBackend');
|
||||
// ----- Password Policy --------------------------------------
|
||||
|
||||
class BadPassword extends Exception {}
|
||||
class ExpiredPassword extends Exception {}
|
||||
class PasswordUpdateFailed extends Exception {}
|
||||
|
||||
abstract class PasswordPolicy {
|
||||
static protected $registry = array();
|
||||
|
||||
static $id;
|
||||
static $name;
|
||||
|
||||
/**
|
||||
* Check a password and throw BadPassword with a meaningful message if
|
||||
* the password cannot be accepted.
|
||||
*/
|
||||
abstract function processPassword($new, $current);
|
||||
abstract function onset($new, $current);
|
||||
|
||||
static function checkPassword($new, $current) {
|
||||
foreach (static::allActivePolicies() as $P) {
|
||||
$P->processPassword($new, $current);
|
||||
}
|
||||
/*
|
||||
* Called on login to enforce policies & check for expired passwords
|
||||
*/
|
||||
abstract function onLogin($user, $password);
|
||||
|
||||
/*
|
||||
* get friendly name of the policy
|
||||
*/
|
||||
function getName() {
|
||||
return static::$name;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check a password aganist all available policies
|
||||
*/
|
||||
static function checkPassword($new, $current, $bk=null) {
|
||||
if ($bk && is_a($bk, 'AuthenticationBackend'))
|
||||
$policies = $bk->getPasswordPolicies();
|
||||
else
|
||||
$policies = self::allActivePolicies();
|
||||
|
||||
foreach ($policies as $P)
|
||||
$P->onSet($new, $current);
|
||||
}
|
||||
|
||||
static function allActivePolicies() {
|
||||
$policies = array();
|
||||
foreach (static::$registry as $P) {
|
||||
foreach (array_reverse(static::$registry) as $P) {
|
||||
if (is_string($P) && class_exists($P))
|
||||
$P = new $P();
|
||||
if ($P instanceof PasswordPolicy)
|
||||
@@ -1390,9 +1487,29 @@ abstract class PasswordPolicy {
|
||||
}
|
||||
Signal::connect('auth.clean', array('PasswordPolicy', 'cleanSessions'));
|
||||
|
||||
/*
|
||||
* Basic default password policy that ships with osTicket.
|
||||
*
|
||||
*/
|
||||
class osTicketPasswordPolicy
|
||||
extends PasswordPolicy {
|
||||
function processPassword($passwd, $current) {
|
||||
static $id = "basic";
|
||||
static $name = /* @trans */ "Default Basic Policy";
|
||||
|
||||
function onLogin($user, $password) {
|
||||
global $cfg;
|
||||
|
||||
// Check for possible password expiration
|
||||
// Check is only here for legacy reasons - password management
|
||||
// policies are now done via plugins.
|
||||
if ($cfg && $user
|
||||
&& ($period=$cfg->getPasswdResetPeriod())
|
||||
&& ($time=$user->getPasswdResetTimestamp())
|
||||
&& $time < time()-($period*2629800))
|
||||
throw new ExpiredPassword(__('Expired password'));
|
||||
}
|
||||
|
||||
function onSet($passwd, $current) {
|
||||
if (strlen($passwd) < 6) {
|
||||
throw new BadPassword(
|
||||
__('Password must be at least 6 characters'));
|
||||
|
||||
@@ -229,11 +229,22 @@ extends VerySimpleModel {
|
||||
}
|
||||
|
||||
static function getCannedResponses($deptId=0, $explicit=false) {
|
||||
global $thisstaff;
|
||||
|
||||
$canned = static::objects()
|
||||
->filter(array('isenabled' => true))
|
||||
->order_by('title')
|
||||
->values_flat('canned_id', 'title');
|
||||
|
||||
if ($thisstaff) {
|
||||
$staffDepts = array();
|
||||
|
||||
$staffDepts = $thisstaff->getDepts();
|
||||
$staffDepts[] = 0;
|
||||
|
||||
$canned->filter(array('dept_id__in' => $staffDepts));
|
||||
}
|
||||
|
||||
if ($deptId) {
|
||||
$depts = array($deptId);
|
||||
if (!$explicit)
|
||||
|
||||
@@ -247,6 +247,11 @@ class EndUser extends BaseAuthenticatedUser {
|
||||
return UserAuthenticationBackend::getBackend($authkey);
|
||||
}
|
||||
|
||||
function get2FABackend() {
|
||||
//TODO: support 2FA on client portal
|
||||
return null;
|
||||
}
|
||||
|
||||
function getTicketStats() {
|
||||
if (!isset($this->_stats))
|
||||
$this->_stats = $this->getStats();
|
||||
@@ -381,7 +386,7 @@ class EndUser extends BaseAuthenticatedUser {
|
||||
|
||||
class ClientAccount extends UserAccount {
|
||||
|
||||
function checkPassword($password, $autoupdate=true) {
|
||||
function check_passwd($password, $autoupdate=true) {
|
||||
|
||||
/*bcrypt based password match*/
|
||||
if(Passwd::cmp($password, $this->get('passwd')))
|
||||
@@ -402,7 +407,7 @@ class ClientAccount extends UserAccount {
|
||||
}
|
||||
|
||||
function hasCurrentPassword($password) {
|
||||
return $this->checkPassword($password, false);
|
||||
return $this->check_passwd($password, false);
|
||||
}
|
||||
|
||||
function cancelResetTokens() {
|
||||
@@ -431,32 +436,37 @@ class ClientAccount extends UserAccount {
|
||||
return parent::update($vars, $errors);
|
||||
|
||||
$rtoken = $_SESSION['_client']['reset-token'];
|
||||
if ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd'] || $rtoken) {
|
||||
|
||||
|
||||
if ($rtoken) {
|
||||
$_config = new Config('pwreset');
|
||||
if ($_config->get($rtoken) != 'c'.$this->getUserId())
|
||||
$errors['err'] =
|
||||
__('Invalid reset token. Logout and try again');
|
||||
elseif (!($ts = $_config->lastModified($rtoken))
|
||||
&& ($cfg->getPwResetWindow() < (time() - strtotime($ts))))
|
||||
$errors['err'] =
|
||||
__('Invalid reset token. Logout and try again');
|
||||
} elseif ($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd']) {
|
||||
|
||||
if (!$vars['passwd1'])
|
||||
$errors['passwd1']=__('New password is required');
|
||||
elseif ($vars['passwd1'] && strlen($vars['passwd1'])<6)
|
||||
$errors['passwd1']=__('Password must be at least 6 characters');
|
||||
elseif ($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2']))
|
||||
$errors['passwd2']=__('Passwords do not match');
|
||||
|
||||
if ($rtoken) {
|
||||
$_config = new Config('pwreset');
|
||||
if ($_config->get($rtoken) != 'c'.$this->getUserId())
|
||||
$errors['err'] =
|
||||
__('Invalid reset token. Logout and try again');
|
||||
elseif (!($ts = $_config->lastModified($rtoken))
|
||||
&& ($cfg->getPwResetWindow() < (time() - strtotime($ts))))
|
||||
$errors['err'] =
|
||||
__('Invalid reset token. Logout and try again');
|
||||
}
|
||||
elseif ($this->get('passwd')) {
|
||||
if (!$vars['cpasswd'])
|
||||
$errors['cpasswd']=__('Current password is required');
|
||||
elseif (!$this->hasCurrentPassword($vars['cpasswd']))
|
||||
$errors['cpasswd']=__('Invalid current password!');
|
||||
elseif (!strcasecmp($vars['passwd1'], $vars['cpasswd']))
|
||||
$errors['passwd1']=__('New password MUST be different from the current password!');
|
||||
}
|
||||
|
||||
// Check password policies
|
||||
if (!$errors) {
|
||||
try {
|
||||
UserAccount::checkPassword($vars['passwd1'], @$vars['cpasswd']);
|
||||
} catch (BadPassword $ex) {
|
||||
$errors['passwd1'] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,23 @@ extends VerySimpleModel {
|
||||
'updated__lt' => SqlFunction::NOW()->minus(SqlInterval::SECOND($period)),
|
||||
))->delete();
|
||||
}
|
||||
|
||||
function getConfigsByNamespace($namespace=false, $key, $value=false) {
|
||||
$filter = array();
|
||||
|
||||
$filter['key'] = $key;
|
||||
|
||||
if ($namespace)
|
||||
$filter['namespace'] = $namespace;
|
||||
|
||||
if ($value)
|
||||
$filter['value'] = $value;
|
||||
|
||||
$token = ConfigItem::objects()
|
||||
->filter($filter);
|
||||
|
||||
return $namespace ? $token[0] : $token;
|
||||
}
|
||||
}
|
||||
|
||||
class OsticketConfig extends Config {
|
||||
@@ -451,10 +468,25 @@ class OsticketConfig extends Config {
|
||||
return $this->get('overdue_grace_period');
|
||||
}
|
||||
|
||||
// This is here for legacy reasons - default osTicket Password Policy
|
||||
// uses it, if previously set.
|
||||
function getPasswdResetPeriod() {
|
||||
return $this->get('passwd_reset_period');
|
||||
}
|
||||
|
||||
|
||||
function getStaffPasswordPolicy() {
|
||||
return $this->get('agent_passwd_policy');
|
||||
}
|
||||
|
||||
function getClientPasswordPolicy() {
|
||||
return $this->get('client_passwd_policy');
|
||||
}
|
||||
|
||||
function require2FAForAgents() {
|
||||
return $this->get('require_agent_2fa');
|
||||
}
|
||||
|
||||
function isRichTextEnabled() {
|
||||
return $this->get('enable_richtext');
|
||||
}
|
||||
@@ -1321,13 +1353,14 @@ class OsticketConfig extends Config {
|
||||
return false;
|
||||
|
||||
return $this->updateAll(array(
|
||||
'passwd_reset_period'=>$vars['passwd_reset_period'],
|
||||
'agent_passwd_policy'=>$vars['agent_passwd_policy'],
|
||||
'staff_max_logins'=>$vars['staff_max_logins'],
|
||||
'staff_login_timeout'=>$vars['staff_login_timeout'],
|
||||
'staff_session_timeout'=>$vars['staff_session_timeout'],
|
||||
'staff_ip_binding'=>isset($vars['staff_ip_binding'])?1:0,
|
||||
'allow_pw_reset'=>isset($vars['allow_pw_reset'])?1:0,
|
||||
'pw_reset_window'=>$vars['pw_reset_window'],
|
||||
'require_agent_2fa'=>$vars['require_agent_2fa'],
|
||||
'agent_name_format'=>$vars['agent_name_format'],
|
||||
'hide_staff_name'=>isset($vars['hide_staff_name']) ? 1 : 0,
|
||||
'agent_avatar'=>$vars['agent_avatar'],
|
||||
@@ -1348,6 +1381,7 @@ class OsticketConfig extends Config {
|
||||
return false;
|
||||
|
||||
return $this->updateAll(array(
|
||||
'client_passwd_policy'=>$vars['client_passwd_policy'],
|
||||
'client_max_logins'=>$vars['client_max_logins'],
|
||||
'client_login_timeout'=>$vars['client_login_timeout'],
|
||||
'client_session_timeout'=>$vars['client_session_timeout'],
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
vim: expandtab sw=4 ts=4 sts=4:
|
||||
**********************************************************************/
|
||||
require_once INCLUDE_DIR . 'class.search.php';
|
||||
require_once INCLUDE_DIR.'class.role.php';
|
||||
|
||||
class Dept extends VerySimpleModel
|
||||
implements TemplateVariable, Searchable {
|
||||
@@ -76,6 +77,16 @@ implements TemplateVariable, Searchable {
|
||||
const FLAG_ASSIGN_PRIMARY_ONLY = 0x0010;
|
||||
const FLAG_DISABLE_REOPEN_AUTO_ASSIGN = 0x0020;
|
||||
|
||||
const PERM_DEPT = 'visibility.departments';
|
||||
|
||||
static protected $perms = array(
|
||||
self::PERM_DEPT => array(
|
||||
'title' => /* @trans */ 'Department',
|
||||
'desc' => /* @trans */ 'Ability to see all Departments',
|
||||
'primary' => true,
|
||||
),
|
||||
);
|
||||
|
||||
function asVar() {
|
||||
return $this->getName();
|
||||
}
|
||||
@@ -632,6 +643,15 @@ implements TemplateVariable, Searchable {
|
||||
return $row ? $row[0] : 0;
|
||||
}
|
||||
|
||||
static function getEmailIdById($id) {
|
||||
$row = static::objects()
|
||||
->filter(array('id' => $id))
|
||||
->values_flat('email_id')
|
||||
->first();
|
||||
|
||||
return $row ? $row[0] : 0;
|
||||
}
|
||||
|
||||
function getNameById($id) {
|
||||
$names = Dept::getDepartments();
|
||||
return $names[$id];
|
||||
@@ -654,7 +674,7 @@ implements TemplateVariable, Searchable {
|
||||
// XXX: This will upset the static $depts array
|
||||
$depts = array();
|
||||
$query = self::objects();
|
||||
if (isset($criteria['publiconly']))
|
||||
if (isset($criteria['publiconly']) && $criteria['publiconly'])
|
||||
$query->filter(array(
|
||||
'flags__hasbit' => Dept::FLAG_ACTIVE));
|
||||
|
||||
@@ -973,7 +993,12 @@ implements TemplateVariable, Searchable {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static function getPermissions() {
|
||||
return self::$perms;
|
||||
}
|
||||
}
|
||||
RolePermission::register(/* @trans */ 'Miscellaneous', Dept::getPermissions());
|
||||
|
||||
class DepartmentQuickAddForm
|
||||
extends Form {
|
||||
|
||||
@@ -1487,7 +1487,7 @@ class SelectionField extends FormField {
|
||||
return $this->_list;
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$config = $this->getConfiguration();
|
||||
if ($config['widget'] == 'typeahead' && $config['multiselect'] == false)
|
||||
$widgetClass = 'TypeaheadSelectionWidget';
|
||||
@@ -1744,7 +1744,7 @@ class SelectionField extends FormField {
|
||||
return $config;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!$this->_choices || $verbose) {
|
||||
$choices = array();
|
||||
foreach ($this->getList()->getItems() as $i)
|
||||
@@ -1925,7 +1925,7 @@ class TypeaheadSelectionWidget extends ChoicesWidget {
|
||||
return array($this->getValue() => $this->getEnteredValue());
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
function getValue($options=array()) {
|
||||
$data = $this->field->getSource();
|
||||
$name = $this->field->get('name');
|
||||
if (isset($data["{$this->name}_id"]) && is_numeric($data["{$this->name}_id"])) {
|
||||
|
||||
@@ -489,6 +489,9 @@ class Email extends VerySimpleModel {
|
||||
if ($options['smtp'])
|
||||
$objects = $objects->filter(array('smtp_active'=>true));
|
||||
|
||||
if ($options['depts'])
|
||||
$objects = $objects->filter(array('dept_id__in'=>$options['depts']));
|
||||
|
||||
if (!$flat)
|
||||
return $objects;
|
||||
|
||||
|
||||
@@ -112,7 +112,7 @@ class Form {
|
||||
}
|
||||
|
||||
function getTitle() { return $this->title; }
|
||||
function getInstructions() { return $this->instructions; }
|
||||
function getInstructions() { return Format::htmldecode($this->instructions); }
|
||||
function getSource() { return $this->_source; }
|
||||
function setSource($source) { $this->_source = $source; }
|
||||
|
||||
@@ -222,11 +222,11 @@ class Form {
|
||||
echo $this->getMedia();
|
||||
}
|
||||
|
||||
function getMedia() {
|
||||
function getMedia($options=array()) {
|
||||
static $dedup = array();
|
||||
|
||||
foreach ($this->getFields() as $f) {
|
||||
if (($M = $f->getMedia()) && is_array($M)) {
|
||||
if (($M = $f->getMedia($options)) && is_array($M)) {
|
||||
foreach ($M as $type=>$files) {
|
||||
foreach ($files as $url) {
|
||||
$key = strtolower($type.$url);
|
||||
@@ -1215,8 +1215,8 @@ class FormField {
|
||||
return;
|
||||
}
|
||||
|
||||
function getMedia() {
|
||||
$widget = $this->getWidget();
|
||||
function getMedia($options=array()) {
|
||||
$widget = $this->getWidget(false,$options);
|
||||
return $widget::$media;
|
||||
}
|
||||
|
||||
@@ -1330,13 +1330,13 @@ class FormField {
|
||||
$this->_config[$prop] = $value;
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
if (!static::$widget)
|
||||
throw new Exception(__('Widget not defined for this field'));
|
||||
if (!isset($this->_widget)) {
|
||||
$wc = $widgetClass ?: $this->get('widget') ?: static::$widget;
|
||||
$this->_widget = new $wc($this);
|
||||
$this->_widget->parseValue();
|
||||
$this->_widget->parseValue($options);
|
||||
}
|
||||
return $this->_widget;
|
||||
}
|
||||
@@ -1535,7 +1535,8 @@ class PasswordField extends TextboxField {
|
||||
|
||||
function __construct($options=array()) {
|
||||
parent::__construct($options);
|
||||
$this->set('validator', 'password');
|
||||
if (!isset($options['validator']))
|
||||
$this->set('validator', 'password');
|
||||
}
|
||||
|
||||
function parse($value) {
|
||||
@@ -1960,7 +1961,7 @@ class ChoiceField extends FormField {
|
||||
return $selection;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if ($this->_choices === null || $verbose) {
|
||||
// Allow choices to be set in this->ht (for configurationOptions)
|
||||
$this->_choices = $this->get('choices');
|
||||
@@ -2005,16 +2006,16 @@ class ChoiceField extends FormField {
|
||||
);
|
||||
}
|
||||
|
||||
function getSearchMethodWidgets() {
|
||||
function getSearchMethodWidgets($options=array()) {
|
||||
return array(
|
||||
'set' => null,
|
||||
'nset' => null,
|
||||
'includes' => array('ChoiceField', array(
|
||||
'choices' => $this->getChoices(),
|
||||
'choices' => $this->getChoices(false, $options),
|
||||
'configuration' => array('multiselect' => true),
|
||||
)),
|
||||
'!includes' => array('ChoiceField', array(
|
||||
'choices' => $this->getChoices(),
|
||||
'choices' => $this->getChoices(false, $options),
|
||||
'configuration' => array('multiselect' => true),
|
||||
)),
|
||||
);
|
||||
@@ -2051,7 +2052,7 @@ class ChoiceField extends FormField {
|
||||
}
|
||||
|
||||
function getQuickFilterChoices() {
|
||||
return $this->getChoices();
|
||||
return $this->getChoices(false, array('filterVisibility' => true));
|
||||
}
|
||||
|
||||
function applyQuickFilter($query, $qf_value, $name=false) {
|
||||
@@ -2713,7 +2714,7 @@ class ThreadEntryField extends FormField {
|
||||
function isPresentationOnly() {
|
||||
return true;
|
||||
}
|
||||
function getMedia() {
|
||||
function getMedia($options=array()) {
|
||||
$config = $this->getConfiguration();
|
||||
$media = parent::getMedia() ?: array();
|
||||
if ($config['attachments'])
|
||||
@@ -2762,7 +2763,7 @@ class ThreadEntryField extends FormField {
|
||||
return $config['attachments'];
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
if ($hint = $this->getLocal('hint'))
|
||||
$this->set('placeholder', Format::striptags($hint));
|
||||
$this->set('hint', null);
|
||||
@@ -2776,8 +2777,10 @@ class TopicField extends ChoiceField {
|
||||
var $_choices;
|
||||
|
||||
function getTopics() {
|
||||
global $thisstaff;
|
||||
|
||||
if (!isset($this->topics))
|
||||
$this->topics = Topic::getHelpTopics(false, false, true);
|
||||
$this->topics = $thisstaff->getTopicNames();
|
||||
|
||||
return $this->topics;
|
||||
}
|
||||
@@ -2788,7 +2791,7 @@ class TopicField extends ChoiceField {
|
||||
return Topic::lookup($id);
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$default = $this->get('default');
|
||||
$widget = parent::getWidget($widgetClass);
|
||||
if ($widget->value instanceof Topic)
|
||||
@@ -2802,7 +2805,7 @@ class TopicField extends ChoiceField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!isset($this->_choices)) {
|
||||
$this->_choices = $this->getTopics();
|
||||
}
|
||||
@@ -2894,7 +2897,7 @@ class SLAField extends ChoiceField {
|
||||
return SLA::lookup($id);
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$default = $this->get('default');
|
||||
$widget = parent::getWidget($widgetClass);
|
||||
if ($widget->value instanceof SLA)
|
||||
@@ -2908,7 +2911,7 @@ class SLAField extends ChoiceField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!isset($this->_choices)) {
|
||||
$choices = array();
|
||||
foreach ($this->getSLAs() as $s)
|
||||
@@ -3005,7 +3008,7 @@ class PriorityField extends ChoiceField {
|
||||
return Priority::lookup($id);
|
||||
}
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$widget = parent::getWidget($widgetClass);
|
||||
if ($widget->value instanceof Priority)
|
||||
$widget->value = $widget->value->getId();
|
||||
@@ -3016,7 +3019,7 @@ class PriorityField extends ChoiceField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!isset($this->_choices)) {
|
||||
$choices = array();
|
||||
foreach ($this->getPriorities() as $p)
|
||||
@@ -3141,7 +3144,7 @@ class TimezoneField extends ChoiceField {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $cfg;
|
||||
|
||||
$choices = array();
|
||||
@@ -3177,7 +3180,7 @@ class TimezoneField extends ChoiceField {
|
||||
|
||||
|
||||
class DepartmentField extends ChoiceField {
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$widget = $this->_widget ?: parent::getWidget($widgetClass);
|
||||
if (is_object($widget->value))
|
||||
$widget->value = $widget->value->getId();
|
||||
@@ -3188,8 +3191,8 @@ class DepartmentField extends ChoiceField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
global $cfg;
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $cfg, $thisstaff;
|
||||
|
||||
$selected = self::getWidget();
|
||||
if($selected && $selected->value) {
|
||||
@@ -3205,30 +3208,42 @@ class DepartmentField extends ChoiceField {
|
||||
}
|
||||
}
|
||||
|
||||
$active_depts = Dept::objects()
|
||||
->filter(array('flags__hasbit' => Dept::FLAG_ACTIVE))
|
||||
->values('id', 'name')
|
||||
->order_by('name');
|
||||
if ($thisstaff) {
|
||||
$active = $thisstaff->getDepartmentNames(true);
|
||||
|
||||
$choices = array();
|
||||
if ($depts = Dept::getDepartments(null, true, Dept::DISPLAY_DISABLED)) {
|
||||
//create array w/queryset
|
||||
$active = array();
|
||||
foreach ($active_depts as $dept)
|
||||
$active[$dept['id']] = $dept['name'];
|
||||
$choices = array();
|
||||
if ($options['filterVisibility'])
|
||||
$depts = $thisstaff->getDepartmentNames();
|
||||
else {
|
||||
$depts = Dept::getDepartments(null, true, Dept::DISPLAY_DISABLED);
|
||||
return $depts;
|
||||
}
|
||||
} else {
|
||||
$active_depts = Dept::objects()
|
||||
->filter(array('flags__hasbit' => Dept::FLAG_ACTIVE))
|
||||
->values('id', 'name')
|
||||
->order_by('name');
|
||||
|
||||
//add selected dept to list
|
||||
if($current_id)
|
||||
$choices = array();
|
||||
if ($depts = Dept::getDepartments(null, true, Dept::DISPLAY_DISABLED)) {
|
||||
//create array w/queryset
|
||||
$active = array();
|
||||
foreach ($active_depts as $dept)
|
||||
$active[$dept['id']] = $dept['name'];
|
||||
}
|
||||
}
|
||||
|
||||
//add selected dept to list
|
||||
if($current_id)
|
||||
$active[$current_id] = $current_name;
|
||||
else
|
||||
else
|
||||
return $active;
|
||||
|
||||
foreach ($depts as $id => $name) {
|
||||
foreach ($depts as $id => $name) {
|
||||
$choices[$id] = $name;
|
||||
if(!array_key_exists($id, $active) && $current_id)
|
||||
unset($choices[$id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $choices;
|
||||
}
|
||||
@@ -3313,7 +3328,7 @@ class AssigneeField extends ChoiceField {
|
||||
var $_choices = null;
|
||||
var $_criteria = null;
|
||||
|
||||
function getWidget($widgetClass=false) {
|
||||
function getWidget($widgetClass=false, $options=array()) {
|
||||
$widget = parent::getWidget($widgetClass);
|
||||
$value = $widget->value;
|
||||
if (is_object($value)) {
|
||||
@@ -3328,7 +3343,7 @@ class AssigneeField extends ChoiceField {
|
||||
|
||||
function getCriteria() {
|
||||
if (!isset($this->_criteria)) {
|
||||
$this->_criteria = array('available' => true);
|
||||
$this->_criteria = array('available' => true, 'namesOnly' => true);
|
||||
if (($c=parent::getCriteria()))
|
||||
$this->_criteria = array_merge($this->_criteria, $c);
|
||||
}
|
||||
@@ -3353,8 +3368,8 @@ class AssigneeField extends ChoiceField {
|
||||
return $value;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
global $cfg;
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $cfg, $thisstaff;
|
||||
|
||||
if (!isset($this->_choices)) {
|
||||
$config = $this->getConfiguration();
|
||||
@@ -3364,14 +3379,18 @@ class AssigneeField extends ChoiceField {
|
||||
$A = current($choices);
|
||||
$criteria = $this->getCriteria();
|
||||
$agents = array();
|
||||
if (($dept=$config['dept']) && $dept->assignMembersOnly()) {
|
||||
if (($members = $dept->getAvailableMembers()))
|
||||
foreach ($members as $member)
|
||||
$agents[$member->getId()] = $member;
|
||||
} else {
|
||||
$agents = Staff::getStaffMembers($criteria);
|
||||
if ($options['filterVisibility'])
|
||||
$agents = $thisstaff->getDeptAgents($criteria);
|
||||
else {
|
||||
if (($dept=$config['dept']) && $dept->assignMembersOnly()) {
|
||||
if (($members = $dept->getAvailableMembers()))
|
||||
foreach ($members as $member)
|
||||
$agents[$member->getId()] = $member;
|
||||
} else
|
||||
$agents = Staff::getStaffMembers($criteria);
|
||||
}
|
||||
|
||||
|
||||
foreach ($agents as $id => $name)
|
||||
$A['s'.$id] = $name;
|
||||
|
||||
@@ -3387,6 +3406,7 @@ class AssigneeField extends ChoiceField {
|
||||
return $this->_choices;
|
||||
}
|
||||
|
||||
|
||||
function getChoice($value) {
|
||||
$choices = $this->getChoices();
|
||||
$selection = array();
|
||||
@@ -3406,6 +3426,17 @@ class AssigneeField extends ChoiceField {
|
||||
return parent::getChoice($value);
|
||||
}
|
||||
|
||||
function getQuickFilterChoices() {
|
||||
$choices = $this->getChoices(false, array('filterVisibility' => true));
|
||||
$names = array();
|
||||
foreach ($choices as $value) {
|
||||
foreach ($value as $key => $value)
|
||||
$names[$key] = is_object($value) ? $value->name : $value;
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
if (($value = parent::getValue()) && ($id=$this->getClean())) {
|
||||
$name = (is_object($value[key($value)]) && get_class($value[key($value)]) == 'AgentsName') ?
|
||||
@@ -3557,7 +3588,7 @@ class TicketStateField extends ChoiceField {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
static $_choices;
|
||||
|
||||
$states = static::$_states;
|
||||
@@ -3645,7 +3676,7 @@ class TicketFlagField extends ChoiceField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
$this->ht['default'] = '';
|
||||
|
||||
if (!$this->_choices) {
|
||||
@@ -4207,8 +4238,8 @@ class Widget {
|
||||
$this->id = '_' . $this->name;
|
||||
}
|
||||
|
||||
function parseValue() {
|
||||
$this->value = $this->getValue();
|
||||
function parseValue($options=array()) {
|
||||
$this->value = $this->getValue($options);
|
||||
if (!isset($this->value) && is_object($this->field->getAnswer()))
|
||||
$this->value = $this->field->getAnswer()->getValue();
|
||||
if (!isset($this->value) && isset($this->field->value))
|
||||
@@ -4265,20 +4296,46 @@ class TextboxWidget extends Widget {
|
||||
if (!isset($config[$k]) || !$config[$k])
|
||||
$config[$k] = $v;
|
||||
}
|
||||
if (isset($config['size']))
|
||||
$size = "size=\"{$config['size']}\"";
|
||||
if (isset($config['length']) && $config['length'])
|
||||
$maxlength = "maxlength=\"{$config['length']}\"";
|
||||
if (isset($config['classes']))
|
||||
$classes = 'class="'.$config['classes'].'"';
|
||||
if (isset($config['autocomplete']))
|
||||
$autocomplete = 'autocomplete="'.$config['autocomplete'].'"';
|
||||
|
||||
// Input attributes
|
||||
$attrs = array();
|
||||
foreach ($config as $k => $v) {
|
||||
switch ($k) {
|
||||
case 'autocomplete':
|
||||
if (is_numeric($v))
|
||||
$v = $v ? 'on' : 'off';
|
||||
$attrs[$k] = '"'.$v.'"';
|
||||
break;
|
||||
case 'disabled';
|
||||
$attrs[$k] = '"disabled"';
|
||||
break;
|
||||
case 'translatable':
|
||||
if ($v)
|
||||
$attrs['data-translate-tag'] = '"'.$v.'"';
|
||||
break;
|
||||
case 'size':
|
||||
case 'maxlength':
|
||||
if ($v && is_numeric($v))
|
||||
$attrs[$k] = '"'.$v.'"';
|
||||
break;
|
||||
case 'class':
|
||||
case 'classes':
|
||||
$attrs['class'] = '"'.$v.'"';
|
||||
break;
|
||||
case 'inputmode':
|
||||
case 'pattern':
|
||||
$attrs[$k] = '"'.$v.'"';
|
||||
break;
|
||||
}
|
||||
}
|
||||
// autofocus
|
||||
$autofocus = '';
|
||||
if (isset($config['autofocus']))
|
||||
$autofocus = 'autofocus';
|
||||
if (isset($config['disabled']))
|
||||
$disabled = 'disabled="disabled"';
|
||||
if (isset($config['translatable']) && $config['translatable'])
|
||||
$translatable = 'data-translate-tag="'.$config['translatable'].'"';
|
||||
// placeholder
|
||||
$attrs['placeholder'] = $this->field->getLocal('placeholder',
|
||||
$config['placeholder']);
|
||||
|
||||
$type = static::$input_type;
|
||||
$types = array(
|
||||
'email' => 'email',
|
||||
@@ -4286,14 +4343,11 @@ class TextboxWidget extends Widget {
|
||||
);
|
||||
if ($type == 'text' && isset($types[$config['validator']]))
|
||||
$type = $types[$config['validator']];
|
||||
$placeholder = sprintf('placeholder="%s"', $this->field->getLocal('placeholder',
|
||||
$config['placeholder']));
|
||||
?>
|
||||
<input type="<?php echo $type; ?>"
|
||||
id="<?php echo $this->id; ?>"
|
||||
<?php echo implode(' ', array_filter(array(
|
||||
$size, $maxlength, $classes, $autocomplete, $disabled,
|
||||
$translatable, $placeholder, $autofocus))); ?>
|
||||
<?php echo $autofocus .' '.Format::array_implode('=', ' ',
|
||||
array_filter($attrs)); ?>
|
||||
name="<?php echo $this->name; ?>"
|
||||
value="<?php echo Format::htmlchars($this->value); ?>"/>
|
||||
<?php
|
||||
@@ -4332,7 +4386,7 @@ class PasswordWidget extends TextboxWidget {
|
||||
return parent::render($mode, $extra);
|
||||
}
|
||||
|
||||
function parseValue() {
|
||||
function parseValue($options=array()) {
|
||||
parent::parseValue();
|
||||
// Show empty box unless failed POST
|
||||
if ($_SERVER['REQUEST_METHOD'] != 'POST'
|
||||
@@ -4344,27 +4398,41 @@ class PasswordWidget extends TextboxWidget {
|
||||
class TextareaWidget extends Widget {
|
||||
function render($options=array()) {
|
||||
$config = $this->field->getConfiguration();
|
||||
$class = $cols = $rows = $maxlength = "";
|
||||
// process textarea attributes
|
||||
$attrs = array();
|
||||
if (isset($config['rows']))
|
||||
$rows = "rows=\"{$config['rows']}\"";
|
||||
if (isset($config['cols']))
|
||||
$cols = "cols=\"{$config['cols']}\"";
|
||||
if (isset($config['length']) && $config['length'])
|
||||
$maxlength = "maxlength=\"{$config['length']}\"";
|
||||
if (isset($config['html']) && $config['html']) {
|
||||
$class = array('richtext', 'no-bar');
|
||||
$class[] = @$config['size'] ?: 'small';
|
||||
$class = sprintf('class="%s"', implode(' ', $class));
|
||||
$this->value = Format::viewableImages($this->value);
|
||||
foreach ($config as $k => $v) {
|
||||
switch ($k) {
|
||||
case 'rows':
|
||||
case 'cols':
|
||||
case 'length':
|
||||
case 'maxlength':
|
||||
if ($v && is_numeric($v))
|
||||
$attrs[$k] = '"'.$v.'"';;
|
||||
break;
|
||||
case 'context':
|
||||
$attrs['data-root-context'] = '"'.$v.'"';
|
||||
break;
|
||||
case 'class':
|
||||
// This might conflict with html attr below
|
||||
$attrs[$k] = '"'.$v.'"';
|
||||
break;
|
||||
case 'html':
|
||||
if ($v) {
|
||||
$class = array('richtext', 'no-bar');
|
||||
$class[] = @$config['size'] ?: 'small';
|
||||
$attrs['class'] = '"'.implode(' ', $class).'"';
|
||||
$this->value = Format::viewableImages($this->value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (isset($config['context']))
|
||||
$attrs['data-root-context'] = '"'.$config['context'].'"';
|
||||
// placeholder
|
||||
$attrs['placeholder'] = $this->field->getLocal('placeholder',
|
||||
$config['placeholder']);
|
||||
?>
|
||||
<span style="display:inline-block;width:100%">
|
||||
<textarea <?php echo $rows." ".$cols." ".$maxlength." ".$class
|
||||
.' '.Format::array_implode('=', ' ', $attrs)
|
||||
.' placeholder="'.$this->field->getLocal('placeholder', $config['placeholder']).'"'; ?>
|
||||
<textarea <?php echo Format::array_implode('=', ' ',
|
||||
array_filter($attrs)); ?>
|
||||
id="<?php echo $this->id; ?>"
|
||||
name="<?php echo $this->name; ?>"><?php
|
||||
echo Format::htmlchars($this->value);
|
||||
@@ -4373,7 +4441,7 @@ class TextareaWidget extends Widget {
|
||||
<?php
|
||||
}
|
||||
|
||||
function parseValue() {
|
||||
function parseValue($options=array()) {
|
||||
parent::parseValue();
|
||||
if (isset($this->value)) {
|
||||
$value = $this->value;
|
||||
@@ -4440,7 +4508,7 @@ class ChoicesWidget extends Widget {
|
||||
|
||||
// Determine the value for the default (the one listed if nothing is
|
||||
// selected)
|
||||
$choices = $this->field->getChoices(true);
|
||||
$choices = $this->field->getChoices(true, $options);
|
||||
$prompt = ($config['prompt'])
|
||||
? $this->field->getLocal('prompt', $config['prompt'])
|
||||
: __('Select'
|
||||
@@ -4535,7 +4603,7 @@ class ChoicesWidget extends Widget {
|
||||
}
|
||||
}
|
||||
|
||||
function getValue() {
|
||||
function getValue($options=array()) {
|
||||
|
||||
if (!($value = parent::getValue()))
|
||||
return null;
|
||||
@@ -4545,7 +4613,7 @@ class ChoicesWidget extends Widget {
|
||||
|
||||
// Assume multiselect
|
||||
$values = array();
|
||||
$choices = $this->field->getChoices();
|
||||
$choices = $this->field->getChoices(false, $options);
|
||||
|
||||
if ($choices && is_array($value)) {
|
||||
// Complex choices
|
||||
@@ -5020,7 +5088,7 @@ class ThreadEntryWidget extends Widget {
|
||||
return $field;
|
||||
}
|
||||
|
||||
function parseValue() {
|
||||
function parseValue($options=array()) {
|
||||
parent::parseValue();
|
||||
if (isset($this->value)) {
|
||||
$value = $this->value;
|
||||
@@ -5533,7 +5601,6 @@ class AssignmentForm extends Form {
|
||||
if (isset($this->_assignees))
|
||||
$fields['assignee']->setChoices($this->_assignees);
|
||||
|
||||
|
||||
$this->setFields($fields);
|
||||
|
||||
return $this->fields;
|
||||
@@ -5760,7 +5827,7 @@ class ReferralForm extends Form {
|
||||
),
|
||||
)
|
||||
),
|
||||
'agent' => new ChoiceField(array(
|
||||
'agent' => new AgentSelectionField(array(
|
||||
'id'=>2,
|
||||
'label' => '',
|
||||
'flags' => hexdec(0X450F3),
|
||||
|
||||
@@ -126,7 +126,7 @@ class Internationalization {
|
||||
'registration-staff', 'pwreset-staff', 'banner-staff',
|
||||
'registration-client', 'pwreset-client', 'banner-client',
|
||||
'registration-confirm', 'registration-thanks',
|
||||
'access-link') as $type) {
|
||||
'access-link', 'email2fa-staff') as $type) {
|
||||
$tpl = $this->getTemplate("templates/page/{$type}.yaml");
|
||||
if (!($page = $tpl->getData()))
|
||||
continue;
|
||||
|
||||
@@ -112,16 +112,16 @@ class osTicket {
|
||||
return ($token && $this->getCSRF()->validateToken($token));
|
||||
}
|
||||
|
||||
function checkCSRFToken($name=false) {
|
||||
function checkCSRFToken($name=false, $rotate=false) {
|
||||
$name = $name ?: $this->getCSRF()->getTokenName();
|
||||
if(isset($_POST[$name]) && $this->validateCSRFToken($_POST[$name]))
|
||||
return true;
|
||||
|
||||
if(isset($_SERVER['HTTP_X_CSRFTOKEN']) && $this->validateCSRFToken($_SERVER['HTTP_X_CSRFTOKEN']))
|
||||
$token = $_POST[$name] ?: $_SERVER['HTTP_X_CSRFTOKEN'];
|
||||
if ($token && $this->validateCSRFToken($token)) {
|
||||
if ($rotate) $this->getCSRF()->rotate();
|
||||
return true;
|
||||
}
|
||||
|
||||
$msg=sprintf(__('Invalid CSRF token [%1$s] on %2$s'),
|
||||
(Format::htmlchars($_POST[$name]).''.$_SERVER['HTTP_X_CSRFTOKEN']), THISPAGE);
|
||||
Format::htmlchars($token), THISPAGE);
|
||||
$this->logWarning(__('Invalid CSRF Token').' '.$name, $msg, false);
|
||||
|
||||
return false;
|
||||
|
||||
@@ -172,7 +172,7 @@ class CustomQueue extends VerySimpleModel {
|
||||
* search beyond the current configuration of the search criteria
|
||||
* $searchables - search fields - default to current if not provided
|
||||
*/
|
||||
function getForm($source=null, $searchable=null) {
|
||||
function getForm($source=null, $searchable=null, $filterVisibility=null) {
|
||||
$fields = array();
|
||||
if (!isset($searchable)) {
|
||||
$fields = array(
|
||||
@@ -196,7 +196,7 @@ class CustomQueue extends VerySimpleModel {
|
||||
}
|
||||
|
||||
foreach ($searchable ?: array() as $path => $field)
|
||||
$fields = array_merge($fields, static::getSearchField($field, $path));
|
||||
$fields = array_merge($fields, static::getSearchField($field, $path, array('filterVisibility' => $filterVisibility)));
|
||||
|
||||
$form = new AdvancedSearchForm($fields, $source);
|
||||
|
||||
@@ -435,7 +435,7 @@ class CustomQueue extends VerySimpleModel {
|
||||
* representing the configurable search
|
||||
* $name - <string> ORM path for the search
|
||||
*/
|
||||
static function getSearchField($F, $name) {
|
||||
static function getSearchField($F, $name, $options=array()) {
|
||||
list($label, $field) = $F;
|
||||
|
||||
$pieces = array();
|
||||
@@ -460,7 +460,7 @@ class CustomQueue extends VerySimpleModel {
|
||||
)), VisibilityConstraint::HIDDEN),
|
||||
));
|
||||
$offs = 0;
|
||||
foreach ($field->getSearchMethodWidgets() as $m=>$w) {
|
||||
foreach ($field->getSearchMethodWidgets($options) as $m=>$w) {
|
||||
if (!$w)
|
||||
continue;
|
||||
list($class, $args) = $w;
|
||||
@@ -598,6 +598,7 @@ class CustomQueue extends VerySimpleModel {
|
||||
'status__id' =>__('Current Status'),
|
||||
'lastupdate' => __('Last Updated'),
|
||||
'est_duedate' => __('SLA Due Date'),
|
||||
'sla_id' => __('SLA Plan'),
|
||||
'duedate' => __('Due Date'),
|
||||
'closed' => __('Closed Date'),
|
||||
'isoverdue' => __('Overdue'),
|
||||
@@ -1943,7 +1944,7 @@ extends QueueColumnAnnotation {
|
||||
|
||||
class DataSourceField
|
||||
extends ChoiceField {
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
$config = $this->getConfiguration();
|
||||
$root = $config['root'];
|
||||
$fields = array();
|
||||
@@ -2154,7 +2155,7 @@ extends ChoiceField {
|
||||
return new $choices(array('name' => $prop));
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (isset($this->property))
|
||||
return static::$properties[$this->property];
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ class OverviewReport {
|
||||
$headers = array(__('Help Topic'));
|
||||
$header = function($row) { return Topic::getLocalNameById($row['topic_id'], $row['topic__topic']); };
|
||||
$pk = 'topic_id';
|
||||
$topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
|
||||
$topics = $thisstaff->getTopicNames(false, Topic::DISPLAY_DISABLED);
|
||||
if (empty($topics))
|
||||
return array("columns" => array_merge($headers, $dash_headers),
|
||||
"data" => array());
|
||||
|
||||
@@ -745,14 +745,14 @@ class SavedQueue extends CustomQueue {
|
||||
* configuring this search in the user interface.
|
||||
*
|
||||
*/
|
||||
function getForm($source=null, $searchable=array()) {
|
||||
function getForm($source=null, $searchable=array(), $filterVisibility=NULL) {
|
||||
$searchable = null;
|
||||
if ($this->isAQueue())
|
||||
// Only allow supplemental matches.
|
||||
$searchable = array_intersect_key($this->getCurrentSearchFields($source),
|
||||
$this->getSupplementalMatches());
|
||||
|
||||
return parent::getForm($source, $searchable);
|
||||
return parent::getForm($source, $searchable, $filterVisibility);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1181,23 +1181,48 @@ class HelpTopicChoiceField extends AdvancedSearchSelectionField {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
if (!isset($this->_topics))
|
||||
$this->_topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $thisstaff;
|
||||
if (!isset($this->_topics)) {
|
||||
if ($options['filterVisibility'])
|
||||
$this->_topics = $thisstaff->getTopicNames(false, Topic::DISPLAY_DISABLED);
|
||||
else
|
||||
$this->_topics = Topic::getHelpTopics(false, Topic::DISPLAY_DISABLED);
|
||||
}
|
||||
|
||||
return $this->_topics;
|
||||
}
|
||||
}
|
||||
|
||||
class SLAChoiceField extends AdvancedSearchSelectionField {
|
||||
static $_slas;
|
||||
|
||||
function hasIdValue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!isset($this->_slas))
|
||||
$this->_slas = SLA::getSLAs(array('nameOnly' => true));
|
||||
|
||||
return $this->_slas;
|
||||
}
|
||||
}
|
||||
|
||||
require_once INCLUDE_DIR . 'class.dept.php';
|
||||
class DepartmentChoiceField extends AdvancedSearchSelectionField {
|
||||
static $_depts;
|
||||
var $_choices;
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
if (!isset($this->_depts))
|
||||
$this->_depts = Dept::getDepartments();
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!isset($this->_depts)) {
|
||||
if ($options['filterVisibility'])
|
||||
$this->_depts = $thisstaff->getDepartmentNames();
|
||||
else
|
||||
$this->_depts = Dept::getDepartments();
|
||||
}
|
||||
return $this->_depts;
|
||||
}
|
||||
|
||||
@@ -1239,7 +1264,7 @@ class AssigneeChoiceField extends ChoiceField {
|
||||
protected $_items;
|
||||
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!isset($this->_items)) {
|
||||
@@ -1247,7 +1272,12 @@ class AssigneeChoiceField extends ChoiceField {
|
||||
'M' => __('Me'),
|
||||
'T' => __('One of my teams'),
|
||||
);
|
||||
foreach (Staff::getStaffMembers() as $id=>$name) {
|
||||
if ($options['filterVisibility'])
|
||||
$assignees = $thisstaff->getDeptAgents(array('available' => true, 'namesOnly' => true));
|
||||
else
|
||||
$assignees = Staff::getStaffMembers();
|
||||
|
||||
foreach ($assignees as $id=>$name) {
|
||||
// Don't include $thisstaff (since that's 'Me')
|
||||
if ($thisstaff && $thisstaff->getId() == $id)
|
||||
continue;
|
||||
@@ -1277,16 +1307,16 @@ class AssigneeChoiceField extends ChoiceField {
|
||||
);
|
||||
}
|
||||
|
||||
function getSearchMethodWidgets() {
|
||||
function getSearchMethodWidgets($options=array()) {
|
||||
return array(
|
||||
'assigned' => null,
|
||||
'!assigned' => null,
|
||||
'includes' => array('ChoiceField', array(
|
||||
'choices' => $this->getChoices(),
|
||||
'choices' => $this->getChoices(false, $options),
|
||||
'configuration' => array('multiselect' => true),
|
||||
)),
|
||||
'!includes' => array('ChoiceField', array(
|
||||
'choices' => $this->getChoices(),
|
||||
'choices' => $this->getChoices(false, $options),
|
||||
'configuration' => array('multiselect' => true),
|
||||
)),
|
||||
);
|
||||
@@ -1384,7 +1414,7 @@ class AssigneeChoiceField extends ChoiceField {
|
||||
|
||||
class AssignedField extends AssigneeChoiceField {
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
return array(
|
||||
'assigned' => __('Assigned'),
|
||||
'!assigned' => __('Unassigned'),
|
||||
@@ -1527,10 +1557,25 @@ class AgentSelectionField extends AdvancedSearchSelectionField {
|
||||
|
||||
static $_agents;
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!isset($this->_agents)) {
|
||||
$this->_agents = array('M' => __('Me')) +
|
||||
Staff::getStaffMembers();
|
||||
//determine if we should filter results based on panel being visited
|
||||
if ($options['filterVisibility']) {
|
||||
$this->_agents = array('M' => __('Me'));
|
||||
$agents = $thisstaff->getDeptAgents(array('available' => true, 'namesOnly' => true));
|
||||
foreach (Staff::getStaffMembers() as $id => $name) {
|
||||
if (!$agents || array_key_exists($id, $agents)) {
|
||||
// Don't include $thisstaff (since that's 'Me')
|
||||
if ($thisstaff && $thisstaff->getId() == $id)
|
||||
continue;
|
||||
|
||||
$this->_agents[$id] = $name;
|
||||
}
|
||||
}
|
||||
} else
|
||||
$this->_agents = array('M' => __('Me')) + Staff::getStaffMembers();
|
||||
}
|
||||
return $this->_agents;
|
||||
}
|
||||
@@ -1574,12 +1619,17 @@ class AgentSelectionField extends AdvancedSearchSelectionField {
|
||||
class DepartmentManagerSelectionField extends AgentSelectionField {
|
||||
static $_members;
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
if (!isset($this->_members)) {
|
||||
$managers = array();
|
||||
$staff = Staff::objects()->filter(array('dept__manager_id__gt' => 0));
|
||||
foreach ($staff as $s) {
|
||||
$managers['s'.$s->getId()] = $s->getName()->name;
|
||||
$mgr = Dept::objects()->filter(array('manager_id__gt' => 0))->values_flat('manager_id');
|
||||
$staff = $thisstaff->getDeptAgents(array('available' => true, 'namesOnly' => true));
|
||||
foreach ($mgr as $mid) {
|
||||
$mid = $mid[0];
|
||||
if (array_key_exists($mid, $staff))
|
||||
$managers['s'.$mid] = $staff[$mid]->getName()->name;
|
||||
}
|
||||
$this->_members = $managers;
|
||||
}
|
||||
@@ -1595,7 +1645,7 @@ class DepartmentManagerSelectionField extends AgentSelectionField {
|
||||
class TeamSelectionField extends AdvancedSearchSelectionField {
|
||||
static $_teams;
|
||||
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
if (!isset($this->_teams) && $teams = Team::getTeams())
|
||||
$this->_teams = array('T' => __('One of my teams')) +
|
||||
$teams;
|
||||
@@ -1641,7 +1691,7 @@ class TeamSelectionField extends AdvancedSearchSelectionField {
|
||||
}
|
||||
|
||||
class TicketStateChoiceField extends AdvancedSearchSelectionField {
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
return array(
|
||||
'open' => __('Open'),
|
||||
'closed' => __('Closed'),
|
||||
@@ -1663,7 +1713,7 @@ class TicketStateChoiceField extends AdvancedSearchSelectionField {
|
||||
}
|
||||
|
||||
class TicketFlagChoiceField extends ChoiceField {
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
return array(
|
||||
'isanswered' => __('Answered'),
|
||||
'isoverdue' => __('Overdue'),
|
||||
@@ -1691,7 +1741,7 @@ class TicketFlagChoiceField extends ChoiceField {
|
||||
}
|
||||
|
||||
class TicketSourceChoiceField extends ChoiceField {
|
||||
function getChoices($verbose=false) {
|
||||
function getChoices($verbose=false, $options=array()) {
|
||||
return Ticket::getSources();
|
||||
}
|
||||
|
||||
|
||||
@@ -249,12 +249,16 @@ implements TemplateVariable {
|
||||
|
||||
$entries = array();
|
||||
foreach ($slas as $row) {
|
||||
$row[2] = $row[2] & self::FLAG_ACTIVE;
|
||||
$entries[$row[0]] = sprintf(__('%s (%d hours - %s)'
|
||||
/* Tokens are <name> (<#> hours - <Active|Disabled>) */),
|
||||
self::getLocalById($row[0], 'name', $row[1]),
|
||||
$row[3],
|
||||
$row[2] ? __('Active') : __('Disabled'));
|
||||
if ($criteria['nameOnly'])
|
||||
$entries[$row[0]] = __(self::getLocalById($row[0], 'name', $row[1]));
|
||||
else {
|
||||
$row[2] = $row[2] & self::FLAG_ACTIVE;
|
||||
$entries[$row[0]] = sprintf(__('%s (%d hours - %s)'
|
||||
/* Tokens are <name> (<#> hours - <Active|Disabled>) */),
|
||||
self::getLocalById($row[0], 'name', $row[1]),
|
||||
$row[3],
|
||||
$row[2] ? __('Active') : __('Disabled'));
|
||||
}
|
||||
}
|
||||
|
||||
return $entries;
|
||||
|
||||
@@ -54,17 +54,17 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
var $_config = null;
|
||||
var $_perm;
|
||||
|
||||
const PERM_STAFF = 'visibility.agents';
|
||||
|
||||
static protected $perms = array(
|
||||
self::PERM_STAFF => array(
|
||||
'title' => /* @trans */ 'Agent',
|
||||
'desc' => /* @trans */ 'Ability to see Agents in all Departments',
|
||||
'primary' => true,
|
||||
),
|
||||
);
|
||||
|
||||
function __onload() {
|
||||
|
||||
// WE have to patch info here to support upgrading from old versions.
|
||||
$time = null;
|
||||
if (isset($this->passwdreset) && $this->passwdreset)
|
||||
$time=strtotime($this->passwdreset);
|
||||
elseif (isset($this->added) && $this->added)
|
||||
$time=strtotime($this->added);
|
||||
|
||||
if ($time)
|
||||
$this->passwd_change = time()-$time; //XXX: check timezone issues.
|
||||
}
|
||||
|
||||
function get($field, $default=false) {
|
||||
@@ -82,6 +82,10 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return $this->_config[$field];
|
||||
}
|
||||
|
||||
function getConfigObj() {
|
||||
return new Config('staff.'.$this->getId());
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
|
||||
if (!isset($this->_config) && $this->getId()) {
|
||||
@@ -102,6 +106,12 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return $this->_config;
|
||||
}
|
||||
|
||||
function updateConfig($vars) {
|
||||
$config = $this->getConfigObj();
|
||||
$config->updateAll($vars);
|
||||
$this->_config = null;
|
||||
}
|
||||
|
||||
function __toString() {
|
||||
return (string) $this->getName();
|
||||
}
|
||||
@@ -178,6 +188,21 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return StaffAuthenticationBackend::getBackend($bk);
|
||||
}
|
||||
|
||||
function get2FABackendId() {
|
||||
return $this->default_2fa;
|
||||
}
|
||||
|
||||
function get2FABackend() {
|
||||
return Staff2FABackend::getBackend($this->get2FABackendId());
|
||||
}
|
||||
|
||||
// gets configured backends
|
||||
function get2FAConfig($id) {
|
||||
$config = $this->getConfig();
|
||||
return isset($config[$id]) ?
|
||||
JsonDataParser::decode($config[$id]) : array();
|
||||
}
|
||||
|
||||
function setAuthKey($key) {
|
||||
$this->authkey = $key;
|
||||
}
|
||||
@@ -228,11 +253,22 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return $this->save();
|
||||
}
|
||||
|
||||
/* check if passwd reset is due. */
|
||||
function isPasswdResetDue() {
|
||||
global $cfg;
|
||||
return ($cfg && $cfg->getPasswdResetPeriod()
|
||||
&& $this->passwd_change>($cfg->getPasswdResetPeriod()*30*24*60*60));
|
||||
function getPasswdResetTimestamp() {
|
||||
if (!isset($this->passwd_change)) {
|
||||
// WE have to patch info here to support upgrading from old versions.
|
||||
if (isset($this->passwdreset) && $this->passwdreset)
|
||||
$this->passwd_change = strtotime($this->passwdreset);
|
||||
elseif (isset($this->added) && $this->added)
|
||||
$this->passwd_change = strtotime($this->added);
|
||||
elseif (isset($this->created) && $this->created)
|
||||
$this->passwd_change = strtotime($this->created);
|
||||
}
|
||||
|
||||
return $this->passwd_change;
|
||||
}
|
||||
|
||||
static function checkPassword($new, $current=null) {
|
||||
osTicketStaffAuthentication::checkPassword($new, $current);
|
||||
}
|
||||
|
||||
function setPassword($new, $current=false) {
|
||||
@@ -246,7 +282,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
|| !$bk instanceof AuthBackend
|
||||
) {
|
||||
// Fallback to osTicket authentication token udpates
|
||||
$bk = new osTicketAuthentication();
|
||||
$bk = new osTicketStaffAuthentication();
|
||||
}
|
||||
|
||||
// And now for the magic
|
||||
@@ -275,10 +311,6 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isPasswdChangeDue() {
|
||||
return $this->isPasswdResetDue();
|
||||
}
|
||||
|
||||
function getRefreshRate() {
|
||||
return $this->auto_refresh_rate;
|
||||
}
|
||||
@@ -367,6 +399,18 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return $this->change_passwd;
|
||||
}
|
||||
|
||||
function force2faConfig() {
|
||||
global $cfg;
|
||||
|
||||
$id = $this->get2FABackendId();
|
||||
$config = $this->get2FAConfig($id);
|
||||
|
||||
//2fa is required and
|
||||
//1. agent doesn't have default_2fa or
|
||||
//2. agent has default_2fa, but that default_2fa is not configured
|
||||
return ($cfg->require2FAForAgents() && !$id || ($id && empty($config)));
|
||||
}
|
||||
|
||||
function getDepartments() {
|
||||
// TODO: Cache this in the agent's session as it is unlikely to
|
||||
// change while logged in
|
||||
@@ -417,6 +461,53 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return $this->getDepartments();
|
||||
}
|
||||
|
||||
function getDepartmentNames($publiconly=false) {
|
||||
$depts = Dept::getDepartments(array('publiconly' => $publiconly));
|
||||
|
||||
//filter out departments the agent does not have access to
|
||||
if (!$this->hasPerm(Dept::PERM_DEPT) && $staffDepts = $this->getDepts()) {
|
||||
foreach ($depts as $id => $name) {
|
||||
if (!in_array($id, $staffDepts))
|
||||
unset($depts[$id]);
|
||||
}
|
||||
}
|
||||
|
||||
return $depts;
|
||||
}
|
||||
|
||||
function getTopicNames($publicOnly=false, $disabled=false) {
|
||||
$allInfo = !$this->hasPerm(Dept::PERM_DEPT) ? true : false;
|
||||
$topics = Topic::getHelpTopics($publicOnly, $disabled, true, array(), $allInfo);
|
||||
$topicsClean = array();
|
||||
|
||||
if (!$this->hasPerm(Dept::PERM_DEPT) && $staffDepts = $this->getDepts()) {
|
||||
foreach ($topics as $id => $info) {
|
||||
if ($info['pid']) {
|
||||
$childDeptId = $info['dept_id'];
|
||||
//show child if public, has access to topic dept_id, or no dept at all
|
||||
if ($info['public'] || !$childDeptId || ($childDeptId && in_array($childDeptId, $staffDepts)))
|
||||
$topicsClean[$id] = $info['topic'];
|
||||
$parent = Topic::lookup($info['pid']);
|
||||
if (!$parent->isPublic() && $parent->getDeptId() && !in_array($parent->getDeptId(), $staffDepts)) {
|
||||
//hide child if parent topic is private and no access to parent topic dept_id
|
||||
unset($topicsClean[$id]);
|
||||
}
|
||||
} elseif ($info['public']) {
|
||||
//show all public topics
|
||||
$topicsClean[$id] = $info['topic'];
|
||||
} else {
|
||||
//show private topics if access to topic dept_id or no dept at all
|
||||
if ($info['dept_id'] && in_array($info['dept_id'], $staffDepts) || !$info['dept_id'])
|
||||
$topicsClean[$id] = $info['topic'];
|
||||
}
|
||||
}
|
||||
|
||||
return $topicsClean;
|
||||
}
|
||||
|
||||
return $topics;
|
||||
}
|
||||
|
||||
function getManagedDepartments() {
|
||||
|
||||
return ($depts=Dept::getDepartments(
|
||||
@@ -779,6 +870,7 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
$_config->updateAll(array(
|
||||
'datetime_format' => $vars['datetime_format'],
|
||||
'default_from_name' => $vars['default_from_name'],
|
||||
'default_2fa' => $vars['default_2fa'],
|
||||
'thread_view_order' => $vars['thread_view_order'],
|
||||
'default_ticket_queue_id' => $vars['default_ticket_queue_id'],
|
||||
'reply_redirect' => ($vars['reply_redirect'] == 'Queue') ? 'Queue' : 'Ticket',
|
||||
@@ -897,6 +989,40 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
return self::getStaffMembers(array('available'=>true));
|
||||
}
|
||||
|
||||
//returns agents in departments this agent has access to
|
||||
function getDeptAgents($criteria=array()) {
|
||||
$agents = static::objects()
|
||||
->distinct('staff_id')
|
||||
->select_related('dept')
|
||||
->select_related('dept_access');
|
||||
|
||||
if (!$this->hasPerm(Staff::PERM_STAFF)) {
|
||||
$agents = Staff::objects()
|
||||
->distinct('staff_id')
|
||||
->filter(Q::any(array(
|
||||
'dept_access__dept_id__in' => $this->getDepts(),
|
||||
'dept_id__in' => $this->getDepts(),
|
||||
)));
|
||||
}
|
||||
|
||||
if (isset($criteria['available'])) {
|
||||
$agents = $agents->filter(array(
|
||||
'onvacation' => 0,
|
||||
'isactive' => 1,
|
||||
));
|
||||
}
|
||||
|
||||
if (isset($criteria['namesOnly'])) {
|
||||
$clean = array();
|
||||
foreach ($agents as $a) {
|
||||
$clean[$a->getId()] = $a->getName();
|
||||
}
|
||||
return $clean;
|
||||
}
|
||||
|
||||
return $agents;
|
||||
}
|
||||
|
||||
static function getsortby($path='', $format=null) {
|
||||
global $cfg;
|
||||
|
||||
@@ -1289,7 +1415,12 @@ implements AuthenticatedUser, EmailContact, TemplateVariable, Searchable {
|
||||
Export::agents($agents, $filename);
|
||||
}
|
||||
|
||||
static function getPermissions() {
|
||||
return self::$perms;
|
||||
}
|
||||
|
||||
}
|
||||
RolePermission::register(/* @trans */ 'Miscellaneous', Staff::getPermissions());
|
||||
|
||||
interface RestrictedAccess {
|
||||
function checkStaffPerm($staff);
|
||||
@@ -1355,6 +1486,14 @@ extends AbstractForm {
|
||||
new Q(array('welcome_email' => false)),
|
||||
VisibilityConstraint::HIDDEN
|
||||
),
|
||||
'validator' => '',
|
||||
'validators' => function($self, $v) {
|
||||
try {
|
||||
Staff::checkPassword($v, null);
|
||||
} catch (BadPassword $ex) {
|
||||
$self->addError($ex->getMessage());
|
||||
}
|
||||
},
|
||||
)),
|
||||
'passwd2' => new PasswordField(array(
|
||||
'placeholder' => __('Confirm Password'),
|
||||
@@ -1366,6 +1505,14 @@ extends AbstractForm {
|
||||
new Q(array('welcome_email' => false)),
|
||||
VisibilityConstraint::HIDDEN
|
||||
),
|
||||
'validator' => '',
|
||||
'validators' => function($self, $v) {
|
||||
try {
|
||||
Staff::checkPassword($v, null);
|
||||
} catch (BadPassword $ex) {
|
||||
$self->addError($ex->getMessage());
|
||||
}
|
||||
},
|
||||
)),
|
||||
'change_passwd' => new BooleanField(array(
|
||||
'default' => true,
|
||||
@@ -1397,15 +1544,32 @@ extends AbstractForm {
|
||||
'configuration' => array(
|
||||
'autofocus' => true,
|
||||
),
|
||||
'validator' => 'noop',
|
||||
)),
|
||||
'passwd1' => new PasswordField(array(
|
||||
'label' => __('Enter a new password'),
|
||||
'placeholder' => __('New Password'),
|
||||
'required' => true,
|
||||
'validator' => '',
|
||||
'validators' => function($self, $v) {
|
||||
try {
|
||||
Staff::checkPassword($v, null);
|
||||
} catch (BadPassword $ex) {
|
||||
$self->addError($ex->getMessage());
|
||||
}
|
||||
},
|
||||
)),
|
||||
'passwd2' => new PasswordField(array(
|
||||
'placeholder' => __('Confirm Password'),
|
||||
'required' => true,
|
||||
'validator' => '',
|
||||
'validators' => function($self, $v) {
|
||||
try {
|
||||
Staff::checkPassword($v, null);
|
||||
} catch (BadPassword $ex) {
|
||||
$self->addError($ex->getMessage());
|
||||
}
|
||||
},
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -1594,6 +1758,14 @@ extends AbstractForm {
|
||||
'placeholder' => __("Temporary Password"),
|
||||
'autocomplete' => 'new-password',
|
||||
),
|
||||
'validator' => '',
|
||||
'validators' => function($self, $v) {
|
||||
try {
|
||||
Staff::checkPassword($v, null);
|
||||
} catch (BadPassword $ex) {
|
||||
$self->addError($ex->getMessage());
|
||||
}
|
||||
},
|
||||
'visibility' => new VisibilityConstraint(
|
||||
new Q(array('welcome_email' => false))
|
||||
),
|
||||
@@ -1635,6 +1807,8 @@ extends AbstractForm {
|
||||
Organization::PERM_EDIT,
|
||||
Organization::PERM_DELETE,
|
||||
FAQ::PERM_MANAGE,
|
||||
Dept::PERM_DEPT,
|
||||
Staff::PERM_STAFF,
|
||||
);
|
||||
return $clean;
|
||||
}
|
||||
|
||||
@@ -483,14 +483,21 @@ class Task extends TaskModel implements RestrictedAccess, Threadable {
|
||||
}
|
||||
|
||||
function getAssignmentForm($source=null, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
$prompt = $assignee = '';
|
||||
// Possible assignees
|
||||
$assignees = array();
|
||||
switch (strtolower($options['target'])) {
|
||||
case 'agents':
|
||||
$dept = $this->getDept();
|
||||
foreach ($dept->getAssignees() as $member)
|
||||
$assignees['s'.$member->getId()] = $member;
|
||||
if ($options['filterVisibility']) {
|
||||
foreach ($thisstaff->getDeptAgents(array('available' => true)) as $member)
|
||||
$assignees['s'.$member->getId()] = $member;
|
||||
} else {
|
||||
foreach ($dept->getAssignees() as $member)
|
||||
$assignees['s'.$member->getId()] = $member;
|
||||
}
|
||||
|
||||
if (!$source && $this->isOpen() && $this->staff)
|
||||
$assignee = sprintf('s%d', $this->staff->getId());
|
||||
|
||||
@@ -1319,12 +1319,14 @@ implements TemplateVariable {
|
||||
|
||||
/* static */
|
||||
function logEmailHeaders($id, $mid, $header=false) {
|
||||
$headerInfo = Mail_Parse::splitHeaders($header);
|
||||
|
||||
if (!$id || !$mid)
|
||||
return false;
|
||||
|
||||
$this->email_info = new ThreadEntryEmailInfo(array(
|
||||
'thread_entry_id' => $id,
|
||||
'email_id' => Email::getIdByEmail($headerInfo['Delivered-To']),
|
||||
'mid' => $mid,
|
||||
));
|
||||
|
||||
@@ -1999,6 +2001,12 @@ class ThreadEvent extends VerySimpleModel {
|
||||
),
|
||||
'null' => true,
|
||||
),
|
||||
'event' => array(
|
||||
'constraint' => array(
|
||||
'event_id' => 'Event.id',
|
||||
),
|
||||
'null' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -985,6 +985,8 @@ implements RestrictedAccess, Threadable, Searchable {
|
||||
}
|
||||
|
||||
function getAssignmentForm($source=null, $options=array()) {
|
||||
global $thisstaff;
|
||||
|
||||
$prompt = $assignee = '';
|
||||
// Possible assignees
|
||||
$assignees = null;
|
||||
@@ -992,7 +994,7 @@ implements RestrictedAccess, Threadable, Searchable {
|
||||
switch (strtolower($options['target'])) {
|
||||
case 'agents':
|
||||
$assignees = array();
|
||||
foreach ($dept->getAssignees() as $member)
|
||||
foreach ($thisstaff->getDeptAgents(array('available' => true)) as $member)
|
||||
$assignees['s'.$member->getId()] = $member;
|
||||
|
||||
if (!$source && $this->isOpen() && $this->staff)
|
||||
@@ -2317,6 +2319,9 @@ implements RestrictedAccess, Threadable, Searchable {
|
||||
'dept_id' => new DepartmentChoiceField(array(
|
||||
'label' => __('Department'),
|
||||
)),
|
||||
'sla_id' => new SLAChoiceField(array(
|
||||
'label' => __('SLA Plan'),
|
||||
)),
|
||||
'topic_id' => new HelpTopicChoiceField(array(
|
||||
'label' => __('Help Topic'),
|
||||
)),
|
||||
@@ -2985,12 +2990,27 @@ implements RestrictedAccess, Threadable, Searchable {
|
||||
}
|
||||
|
||||
function systemReferral($emails) {
|
||||
global $cfg;
|
||||
|
||||
if (!$this->getThread())
|
||||
if (!$thread = $this->getThread())
|
||||
return;
|
||||
|
||||
$eventEmails = array();
|
||||
$events = ThreadEvent::objects()
|
||||
->filter(array('thread_id' => $thread->getId(),
|
||||
'event__name' => 'transferred'));
|
||||
if ($events) {
|
||||
foreach ($events as $e) {
|
||||
$emailId = Dept::getEmailIdById($e->dept_id) ?: $cfg->getDefaultEmailId();
|
||||
if (!in_array($emailId, $eventEmails))
|
||||
$eventEmails[] = $emailId;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($emails as $id) {
|
||||
$refer = $eventEmails ? !in_array($id, $eventEmails) : true;
|
||||
if ($id != $this->email_id
|
||||
&& $refer
|
||||
&& ($email=Email::lookup($id))
|
||||
&& $this->getDeptId() != $email->getDeptId()
|
||||
&& ($dept=Dept::lookup($email->getDeptId()))
|
||||
|
||||
@@ -319,25 +319,25 @@ implements TemplateVariable, Searchable {
|
||||
$this->flags &= ~$flag;
|
||||
}
|
||||
|
||||
static function getHelpTopics($publicOnly=false, $disabled=false, $localize=true, $whitelist=array()) {
|
||||
static function getHelpTopics($publicOnly=false, $disabled=false, $localize=true, $whitelist=array(), $allData=false) {
|
||||
global $cfg;
|
||||
static $topics, $names = array();
|
||||
|
||||
// If localization is specifically requested, then rebuild the list.
|
||||
if (!$names || $localize) {
|
||||
$objects = self::objects()->values_flat(
|
||||
'topic_id', 'topic_pid', 'ispublic', 'flags', 'topic'
|
||||
'topic_id', 'topic_pid', 'ispublic', 'flags', 'topic', 'dept_id'
|
||||
)
|
||||
->order_by('sort');
|
||||
|
||||
// Fetch information for all topics, in declared sort order
|
||||
$topics = array();
|
||||
foreach ($objects as $T) {
|
||||
list($id, $pid, $pub, $flags, $topic) = $T;
|
||||
list($id, $pid, $pub, $flags, $topic, $deptId) = $T;
|
||||
|
||||
$display = ($flags & self::FLAG_ACTIVE);
|
||||
$topics[$id] = array('pid'=>$pid, 'public'=>$pub,
|
||||
'disabled'=>!$display, 'topic'=>$topic);
|
||||
'disabled'=>!$display, 'topic'=>$topic, 'dept_id'=>$deptId);
|
||||
}
|
||||
|
||||
$localize_this = function($id, $default) use ($localize) {
|
||||
@@ -371,6 +371,7 @@ implements TemplateVariable, Searchable {
|
||||
|
||||
// Apply requested filters
|
||||
$requested_names = array();
|
||||
$topicsClean = array();
|
||||
foreach ($names as $id=>$n) {
|
||||
$info = $topics[$id];
|
||||
if ($publicOnly && !$info['public'])
|
||||
@@ -381,8 +382,12 @@ implements TemplateVariable, Searchable {
|
||||
if ($disabled === self::DISPLAY_DISABLED && $info['disabled'])
|
||||
$n .= " - ".__("(disabled)");
|
||||
$requested_names[$id] = $n;
|
||||
$topicsClean[$id] = $info;
|
||||
}
|
||||
|
||||
if ($allData)
|
||||
return $topicsClean;
|
||||
|
||||
// If localization requested and the current locale is not the
|
||||
// primary, the list may need to be sorted. Caching is ok here,
|
||||
// because the locale is not going to be changed within a single
|
||||
|
||||
@@ -1273,10 +1273,13 @@ class UserAccount extends VerySimpleModel {
|
||||
if ($vars['passwd1'] || $vars['passwd2']) {
|
||||
if (!$vars['passwd1'])
|
||||
$errors['passwd1'] = __('New password is 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'] = __('Passwords do not match');
|
||||
else {
|
||||
try {
|
||||
self::checkPassword($vars['passwd1']);
|
||||
} catch (BadPassword $ex) {
|
||||
$errors['passwd1'] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the username is not an email.
|
||||
@@ -1359,10 +1362,15 @@ class UserAccount extends VerySimpleModel {
|
||||
&& !isset($vars['sendemail'])) {
|
||||
if (!$vars['passwd1'])
|
||||
$errors['passwd1'] = 'Temporary 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'] = 'Passwords do not match';
|
||||
else {
|
||||
try {
|
||||
self::checkPassword($vars['passwd1']);
|
||||
} catch (BadPassword $ex) {
|
||||
$errors['passwd1'] = $ex->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) return false;
|
||||
@@ -1397,6 +1405,10 @@ class UserAccount extends VerySimpleModel {
|
||||
return $account;
|
||||
}
|
||||
|
||||
static function checkPassword($new, $current=null) {
|
||||
osTicketClientAuthentication::checkPassword($new, $current);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class UserAccountStatus {
|
||||
|
||||
@@ -177,12 +177,28 @@ class StaffSession extends Staff {
|
||||
return $staff;
|
||||
}
|
||||
|
||||
function clear2FA() {
|
||||
$_SESSION['_auth']['staff']['2fa'] = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
// If 2fa is set then it means it's pending
|
||||
function is2FAPending() {
|
||||
if (!isset($_SESSION['_auth']['staff']['2fa']))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isValid(){
|
||||
global $_SESSION, $cfg;
|
||||
global $cfg;
|
||||
|
||||
if(!$this->getId() || $this->session->getSessionId()!=session_id())
|
||||
return false;
|
||||
|
||||
if ($this->is2FAPending())
|
||||
return false;
|
||||
|
||||
return $this->session->isvalidSession($this->token,$cfg->getStaffTimeout(),$cfg->enableStaffIPBinding())?true:false;
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,14 @@ username:
|
||||
- title: Change a username as an Administrator
|
||||
href: /scp/staff.php
|
||||
|
||||
config2fa:
|
||||
title: Two Factor Authentication
|
||||
content: >
|
||||
Two Factor Authentication adds an extra layer of security
|
||||
when logging into the helpdesk. Once you correctly submit
|
||||
your username and password, you will need to enter a token
|
||||
to finish logging into the helpdesk.
|
||||
|
||||
time_zone:
|
||||
title: Time Zone
|
||||
content: >
|
||||
@@ -83,11 +91,11 @@ confirm_new_password:
|
||||
signature:
|
||||
title: Signature
|
||||
content: >
|
||||
Create an optional <span class="doc-desc-title">Signature</span>
|
||||
that perhaps appears at the end of your Ticket Responses. Whether this
|
||||
<span class="doc-desc-title">Signature</span> appears, or not, depends
|
||||
on the <span class="doc-desc-title">Email Template</span> that will be
|
||||
used in a Ticket Response.
|
||||
Create an optional <span class="doc-desc-title">Signature</span>
|
||||
that perhaps appears at the end of your Ticket Responses. Whether this
|
||||
<span class="doc-desc-title">Signature</span> appears, or not, depends
|
||||
on the <span class="doc-desc-title">Email Template</span> that will be
|
||||
used in a Ticket Response.
|
||||
links:
|
||||
- title: Create Emails Templates in the Admin Panel
|
||||
href: /scp/templates.php
|
||||
|
||||
@@ -39,19 +39,12 @@ disable_agent_collabs:
|
||||
This setting is global for all User created Tickets via API, Piping, and Fetching.
|
||||
|
||||
# Authentication settings
|
||||
password_reset:
|
||||
title: Password Expiration Policy
|
||||
agent_password_policy:
|
||||
title: Password Management Policy
|
||||
content: >
|
||||
Sets how often (in months) Agents will be required to change
|
||||
their password. If disabled (set to "No expiration"), passwords will
|
||||
not expire.
|
||||
|
||||
password_expiration_policy:
|
||||
title: Password Expiration Policy
|
||||
content: >
|
||||
Choose how often Agents will be required to change their password. If
|
||||
disabled (i.e., <span class="doc-desc-opt">No Expiration</span>), passwords
|
||||
will not expire.
|
||||
Chose a <span class="doc-desc-title">Password Policy</span> for <span class="doc-desc-title">Agents</span>.
|
||||
<br><br>
|
||||
Additional policies can be added by installing <span class="doc-desc-title">Password Policy</span> plugins.
|
||||
|
||||
allow_password_resets:
|
||||
title: Allow Password Resets
|
||||
@@ -85,3 +78,11 @@ bind_staff_session_to_ip:
|
||||
upon Log In.
|
||||
<br><br>
|
||||
This setting is not recommened for users assigned IP addresses dynamically.
|
||||
|
||||
require_agent_2fa:
|
||||
title: Require Two Factor Authentication
|
||||
content: >
|
||||
Enable this feature if you would like to have an extra layer of authentication
|
||||
set up for Agents when they log into the helpdesk. Once they correctly submit
|
||||
their username and password, they will be required to submit a token to finish
|
||||
logging into the helpdesk.
|
||||
|
||||
@@ -21,6 +21,13 @@ client_name_format:
|
||||
will use it for names if no other format is specified.
|
||||
|
||||
# Authentication settings
|
||||
client_password_policy:
|
||||
title: Password Management Policy
|
||||
content: >
|
||||
Chose a <span class="doc-desc-title">Password Policy</span> for <span class="doc-desc-title">Users</span>.
|
||||
<br><br>
|
||||
Additional policies can be added by installing <span class="doc-desc-title">Password Policy</span> plugins.
|
||||
|
||||
client_session_timeout:
|
||||
title: User Session Timeout
|
||||
content: >
|
||||
|
||||
@@ -20,6 +20,14 @@ username:
|
||||
that is unique to your <span class="doc-desc-title">Help
|
||||
Desk</span>.
|
||||
|
||||
reset2fa:
|
||||
title: Reset Two Factor Authentication
|
||||
content: >
|
||||
In the event that an Agent loses the ability to log into the helpdesk
|
||||
using their current 2FA configuration, an Admin can reset the Agent's
|
||||
2FA configuration so that they can reconfigure it upon their next
|
||||
successful login.
|
||||
|
||||
email_address:
|
||||
title: Email Address
|
||||
content: >
|
||||
|
||||
24
include/i18n/en_US/templates/page/email2fa-staff.yaml
Normal file
24
include/i18n/en_US/templates/page/email2fa-staff.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
#
|
||||
# email2fa-staff.yaml
|
||||
#
|
||||
# Template of the email sent to staff members when using the Email
|
||||
# Two Factor Authentication
|
||||
---
|
||||
notes: >
|
||||
This template defines the email sent to Staff who use Email for
|
||||
Two Factor Authentication
|
||||
name: "osTicket Two Factor Authentication"
|
||||
body: >
|
||||
<h3><strong>Hi %{staff.name.first},</strong></h3>
|
||||
<div>
|
||||
You have just logged into for the helpdesk at %{url}.<br />
|
||||
<br />
|
||||
Use the verification code below to finish logging into the helpdesk.<br />
|
||||
<br />
|
||||
%{otp}<br />
|
||||
<br />
|
||||
<em style="font-size: small">Your friendly Customer Support System</em>
|
||||
<br />
|
||||
<img src="cid:b56944cb4722cc5cda9d1e23a3ea7fbc" alt="Powered by
|
||||
osTicket" width="126" height="19" style="width: 126px" />
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@ $info=Format::htmlchars(($errors && $_POST)?$_POST:$info);
|
||||
<select name="dept_id">
|
||||
<option value="0">— <?php echo __('All Departments');?> —</option>
|
||||
<?php
|
||||
if (($depts=Dept::getDepartments(array('publiconly' => true)))) {
|
||||
if (($depts=$thisstaff->getDepartmentNames())) {
|
||||
foreach($depts as $id => $name) {
|
||||
$selected=($info['dept_id'] && $id==$info['dept_id'])?'selected="selected"':'';
|
||||
echo sprintf('<option value="%d" %s>%s</option>',$id,$selected,$name);
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
<?php
|
||||
if(!defined('OSTSCPINC') || !$thisstaff) die('Access Denied');
|
||||
|
||||
//get canned responses for depts with a role that can manage canned responses
|
||||
$departments = array();
|
||||
if ($thisstaff->getRole()->hasPerm(Canned::PERM_MANAGE, false))
|
||||
$departments[] = $thisstaff->getDeptId();
|
||||
|
||||
foreach($thisstaff->dept_access as $da) {
|
||||
if ($da->role->hasPerm(Canned::PERM_MANAGE, false))
|
||||
$departments[] = $da->dept->getId();
|
||||
}
|
||||
$departments = implode(',', $departments);
|
||||
|
||||
$qs = array();
|
||||
$sql='SELECT canned.*, count(attach.file_id) as files, dept.name as department '.
|
||||
' FROM '.CANNED_TABLE.' canned '.
|
||||
' LEFT JOIN '.DEPT_TABLE.' dept ON (dept.id=canned.dept_id) '.
|
||||
' LEFT JOIN '.ATTACHMENT_TABLE.' attach
|
||||
ON (attach.object_id=canned.canned_id AND attach.`type`=\'C\' AND NOT attach.inline)';
|
||||
$sql.=' WHERE 1';
|
||||
$sql.= sprintf(' WHERE canned.dept_id = 0 OR dept.id IN (%s)', $departments);
|
||||
|
||||
$sortOptions=array('title'=>'canned.title','status'=>'canned.isenabled','dept'=>'department','updated'=>'canned.updated');
|
||||
$orderWays=array('DESC'=>'DESC','ASC'=>'ASC');
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
<?php
|
||||
if(!defined('OSTSTAFFINC') || !$thisstaff || !$thisstaff->isStaff()) die('Access Denied');
|
||||
$qs = array();
|
||||
|
||||
$agents = Staff::objects()
|
||||
->select_related('dept');
|
||||
$agents = $thisstaff->getDeptAgents();
|
||||
|
||||
// Sanitize 'order' param To Escape XSS
|
||||
if ($_REQUEST['order'])
|
||||
@@ -86,7 +84,7 @@ $qstr.='&order='.($order=='DESC' ? 'ASC' : 'DESC');
|
||||
<select name="did" id="did">
|
||||
<option value="0">— <?php echo __('All Departments');?> —</option>
|
||||
<?php
|
||||
foreach (Dept::getDepartments(array('nonempty'=>1)) as $id=>$name) {
|
||||
foreach ($thisstaff->getDepartmentNames() as $id=>$name) {
|
||||
$sel=($_REQUEST['did'] && $_REQUEST['did']==$id)?'selected="selected"':'';
|
||||
echo sprintf('<option value="%d" %s>%s</option>',$id,$sel,$name);
|
||||
}
|
||||
|
||||
@@ -77,16 +77,21 @@ usort($topics, function($a, $b) {
|
||||
return strcmp($a->getFullName(), $b->getFullName());
|
||||
});
|
||||
array_unshift($topics, new Topic(array('id' => 0, 'topic' => __('All Topics'), 'faq_count' => $total)));
|
||||
if (!$thisstaff->hasPerm(Dept::PERM_DEPT))
|
||||
$staffTopics = $thisstaff->getTopicNames(false);
|
||||
|
||||
foreach ($topics as $T) {
|
||||
$active = $_REQUEST['topicId'] == $T->getId(); ?>
|
||||
<li <?php if ($active) echo 'class="active"'; ?>>
|
||||
<a href="#" data-topic-id="<?php echo $T->getId(); ?>">
|
||||
<i class="icon-fixed-width <?php
|
||||
if ($active) echo 'icon-hand-right'; ?>"></i>
|
||||
<?php echo sprintf('%s (%d)',
|
||||
Format::htmlchars($T->getFullName()),
|
||||
$T->faq_count); ?></a>
|
||||
</li> <?php
|
||||
$active = $_REQUEST['topicId'] == $T->getId();
|
||||
if (!$staffTopics || is_null($T->getId()) || ($staffTopics && array_key_exists($T->getId(), $staffTopics))) { ?>
|
||||
<li <?php if ($active) echo 'class="active"'; ?>>
|
||||
<a href="#" data-topic-id="<?php echo $T->getId(); ?>">
|
||||
<i class="icon-fixed-width <?php
|
||||
if ($active) echo 'icon-hand-right'; ?>"></i>
|
||||
<?php echo sprintf('%s (%d)',
|
||||
Format::htmlchars($T->getFullName()),
|
||||
$T->faq_count); ?></a>
|
||||
</li> <?php
|
||||
}
|
||||
} ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -58,7 +58,10 @@ echo sprintf('<div class="pull-right flush-right">
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
if (!$thisstaff->hasPerm(Dept::PERM_DEPT)) {
|
||||
$staffTopics = $thisstaff->getTopicNames(false);
|
||||
$filter = true;
|
||||
}
|
||||
|
||||
$faqs = $category->faqs
|
||||
->constrain(array('attachments__inline' => 0))
|
||||
@@ -67,11 +70,23 @@ if ($faqs->exists(true)) {
|
||||
echo '<div id="faq">
|
||||
<ol>';
|
||||
foreach ($faqs as $faq) {
|
||||
echo sprintf('
|
||||
<li><strong><a href="faq.php?id=%d" class="previewfaq">%s <span>- %s</span></a> %s</strong></li>',
|
||||
$faq->getId(),$faq->getQuestion(),$faq->isPublished() ? __('Published'):__('Internal'),
|
||||
$faq->attachments ? '<i class="icon-paperclip"></i>' : ''
|
||||
);
|
||||
if ($filter) {
|
||||
if ($faqTopics = $faq->getHelpTopicsIds()) {
|
||||
foreach ($faqTopics as $key => $value) {
|
||||
if (array_key_exists($value, $staffTopics))
|
||||
$show = true;
|
||||
}
|
||||
} else
|
||||
$show = true;
|
||||
} else
|
||||
$show = true;
|
||||
|
||||
if ($show)
|
||||
echo sprintf('
|
||||
<li><strong><a href="faq.php?id=%d" class="previewfaq">%s <span>- %s</span></a> %s</strong></li>',
|
||||
$faq->getId(),$faq->getQuestion(),$faq->isPublished() ? __('Published'):__('Internal'),
|
||||
$faq->attachments ? '<i class="icon-paperclip"></i>' : ''
|
||||
);
|
||||
}
|
||||
echo ' </ol>
|
||||
</div>';
|
||||
|
||||
@@ -71,7 +71,7 @@ $qstr = Http::build_query($qs);
|
||||
<div class="error"><?php echo $errors['category_id']; ?></div>
|
||||
|
||||
<?php
|
||||
if ($topics = Topic::getAllHelpTopics()) {
|
||||
if ($topics = $thisstaff->getTopicNames()) {
|
||||
if (!is_array(@$info['topics']))
|
||||
$info['topics'] = array();
|
||||
?>
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<?php
|
||||
include_once(INCLUDE_DIR.'staff/login.header.php');
|
||||
$info = ($_POST && $errors)?Format::htmlchars($_POST):array();
|
||||
|
||||
|
||||
if ($thisstaff && $thisstaff->is2FAPending())
|
||||
$msg = "2FA Pending";
|
||||
|
||||
?>
|
||||
<div id="brickwall"></div>
|
||||
<div id="loginBox">
|
||||
@@ -13,22 +18,44 @@ $info = ($_POST && $errors)?Format::htmlchars($_POST):array();
|
||||
</a></h1>
|
||||
<h3 id="login-message"><?php echo Format::htmlchars($msg); ?></h3>
|
||||
<div class="banner"><small><?php echo ($content) ? Format::display($content->getLocalBody()) : ''; ?></small></div>
|
||||
<div id="loading" style="display:none;" class="dialog">
|
||||
<h1><i class="icon-spinner icon-spin icon-large"></i>
|
||||
<?php echo __('Verifying');?></h1>
|
||||
</div>
|
||||
<form action="login.php" method="post" id="login" onsubmit="attemptLoginAjax(event)">
|
||||
<?php csrf_token(); ?>
|
||||
<input type="hidden" name="do" value="scplogin">
|
||||
<fieldset>
|
||||
<input type="text" name="userid" id="name" value="<?php
|
||||
echo $info['userid']; ?>" placeholder="<?php echo __('Email or Username'); ?>"
|
||||
autofocus autocorrect="off" autocapitalize="off">
|
||||
<input type="password" name="passwd" id="pass" placeholder="<?php echo __('Password'); ?>" autocorrect="off" autocapitalize="off">
|
||||
<h3 style="display:inline"><a id="reset-link" class="<?php
|
||||
if (!$show_reset || !$cfg->allowPasswordReset()) echo 'hidden';
|
||||
?>" href="pwreset.php"><?php echo __('Forgot My Password'); ?></a></h3>
|
||||
<button class="submit button pull-right" type="submit"
|
||||
<?php csrf_token();
|
||||
if ($thisstaff
|
||||
&& $thisstaff->is2FAPending()
|
||||
&& ($bk=$thisstaff->get2FABackend())
|
||||
&& ($form=$bk->getInputForm($_POST))) {
|
||||
// Render 2FA input form
|
||||
include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php';
|
||||
?>
|
||||
<fieldset style="padding-top:10px;">
|
||||
<input type="hidden" name="do" value="2fa">
|
||||
<button class="submit button pull-center" type="submit"
|
||||
name="submit"><i class="icon-signin"></i>
|
||||
<?php echo __('Log In'); ?>
|
||||
<?php echo __('Verify'); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<?php
|
||||
} else { ?>
|
||||
<input type="hidden" name="do" value="scplogin">
|
||||
<fieldset>
|
||||
<input type="text" name="userid" id="name" value="<?php
|
||||
echo $info['userid']; ?>" placeholder="<?php echo __('Email or Username'); ?>"
|
||||
autofocus autocorrect="off" autocapitalize="off">
|
||||
<input type="password" name="passwd" id="pass" placeholder="<?php echo __('Password'); ?>" autocorrect="off" autocapitalize="off">
|
||||
<h3 style="display:inline"><a id="reset-link" class="<?php
|
||||
if (!$show_reset || !$cfg->allowPasswordReset()) echo 'hidden';
|
||||
?>" href="pwreset.php"><?php echo __('Forgot My Password'); ?></a></h3>
|
||||
<button class="submit button pull-right" type="submit"
|
||||
name="submit"><i class="icon-signin"></i>
|
||||
<?php echo __('Log In'); ?>
|
||||
</button>
|
||||
</fieldset>
|
||||
<?php
|
||||
} ?>
|
||||
</form>
|
||||
<?php
|
||||
$ext_bks = array();
|
||||
@@ -64,6 +91,7 @@ if (count($ext_bks)) { ?>
|
||||
});
|
||||
|
||||
function attemptLoginAjax(e) {
|
||||
$('#loading').show();
|
||||
var objectifyForm = function(formArray) { //serialize data function
|
||||
var returnArray = {};
|
||||
for (var i = 0; i < formArray.length; i++) {
|
||||
@@ -78,6 +106,7 @@ if (count($ext_bks)) { ?>
|
||||
var oldEffect = $.fn.effect;
|
||||
$.fn.effect = function (effectName) {
|
||||
if (effectName === "shake") {
|
||||
$('#loading').hide();
|
||||
var old = $.effects.createWrapper;
|
||||
$.effects.createWrapper = function (element) {
|
||||
var result;
|
||||
@@ -101,12 +130,14 @@ if (count($ext_bks)) { ?>
|
||||
var form = $(e.target),
|
||||
data = objectifyForm(form.serializeArray())
|
||||
data.ajax = 1;
|
||||
$('button[type=submit]', form).attr('disabled', 'disabled');
|
||||
$.ajax({
|
||||
url: form.attr('action'),
|
||||
method: 'POST',
|
||||
data: data,
|
||||
cache: false,
|
||||
success: function(json) {
|
||||
$('button[type=submit]', form).removeAttr('disabled');
|
||||
if (!typeof(json) === 'object' || !json.status)
|
||||
return;
|
||||
switch (json.status) {
|
||||
|
||||
@@ -121,6 +121,50 @@ if ($avatar->isChangeable()) { ?>
|
||||
<div class="error"><?php echo $errors['username']; ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<?php
|
||||
if (($bks=Staff2FABackend::allRegistered())) {
|
||||
$current = $staff->get2FABackendId();
|
||||
$required2fa = $cfg->require2FAForAgents();
|
||||
$_config = $staff->getConfig();
|
||||
?>
|
||||
<tr>
|
||||
<td <?php if ($required2fa) echo 'class="required"'; ?>><?php echo __('Default 2FA'); ?>:</td>
|
||||
<td>
|
||||
<select name="default_2fa" id="default2fa-selection"
|
||||
style="width:300px">
|
||||
<?php
|
||||
if (!$required2fa) { ?>
|
||||
<option value="">— <?php echo __('Disable'); ?> —</option>
|
||||
<?php
|
||||
}
|
||||
foreach ($bks as $bk) {
|
||||
$configuration = $staff->get2FAConfig($bk->getId());
|
||||
$configured = $configuration['verified'];
|
||||
?>
|
||||
<option id="<?php echo $bk->getId(); ?>"
|
||||
value="<?php echo $bk->getId(); ?>" <?php
|
||||
if ($current == $bk->getId() && $configured)
|
||||
echo ' selected="selected" '; ?>
|
||||
<?php
|
||||
if (!$configured)
|
||||
echo ' disabled="disabled" '; ?>
|
||||
><?php
|
||||
echo $bk->getName(); ?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
|
||||
<button type="button" id="config2fa-button" class="action-button" onclick="javascript:
|
||||
$.dialog('ajax.php/staff/'+<?php echo $staff->getId();
|
||||
?>+'/2fa/configure', 201);">
|
||||
<i class="icon-gear"></i> <?php echo __('Configure Options'); ?>
|
||||
</button>
|
||||
<i class="offset help-tip icon-question-sign" href="#config2fa"></i>
|
||||
<div class="error"><?php echo $errors['default_2fa']; ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
} ?>
|
||||
</tbody>
|
||||
<!-- ================================================ -->
|
||||
<tbody>
|
||||
|
||||
@@ -94,7 +94,8 @@ else {
|
||||
<div class="error"><?php echo $errors['criteria']; ?></div>
|
||||
<div class="advanced-search">
|
||||
<?php
|
||||
$form = $queue->getForm();
|
||||
$filterVisibility = false;
|
||||
$form = $queue->getForm(null, null, $filterVisibility);
|
||||
$search = $queue;
|
||||
$matches = $queue->getSupportedMatches();
|
||||
include STAFFINC_DIR . 'templates/advanced-search-criteria.tmpl.php';
|
||||
|
||||
@@ -83,20 +83,23 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo __('Password Expiration Policy'); ?>:</td>
|
||||
<td><?php echo __('Password Policy'); ?>:</td>
|
||||
<td>
|
||||
<select name="passwd_reset_period">
|
||||
<option value="0"> — <?php echo __('No expiration'); ?> —</option>
|
||||
<select name="agent_passwd_policy">
|
||||
<option value=" "> — <?php echo __('All Active Policies'); ?> —</option>
|
||||
<?php
|
||||
for ($i = 1; $i <= 12; $i++) {
|
||||
echo sprintf('<option value="%d" %s>%s</option>',
|
||||
$i,(($config['passwd_reset_period']==$i)?'selected="selected"':''),
|
||||
sprintf(_N('Monthly', 'Every %d months', $i), $i));
|
||||
foreach (PasswordPolicy::allActivePolicies()
|
||||
as $P) {
|
||||
echo sprintf('<option value="%s" %s>%s</option>',
|
||||
$P::$id,
|
||||
(($config['agent_passwd_policy'] == $P::$id) ? 'selected="selected"' : ''),
|
||||
$P->getName());
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<font class="error"><?php echo $errors['passwd_reset_period']; ?></font>
|
||||
<i class="help-tip icon-question-sign" href="#password_expiration_policy"></i>
|
||||
<font class="error"><?php echo
|
||||
$errors['agent_passwd_policy']; ?></font>
|
||||
<i class="help-tip icon-question-sign" href="#agent_password_policy"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -116,6 +119,19 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config
|
||||
<font class="error"><?php echo $errors['pw_reset_window']; ?></font>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo __('Multifactor Authentication'); ?>:</td>
|
||||
<td>
|
||||
<input type="checkbox" name="require_agent_2fa" <?php
|
||||
echo $config['require_agent_2fa'] ? 'checked="checked"' : ''; ?>>
|
||||
|
||||
<?php
|
||||
echo __('Require agents to turn on 2FA');
|
||||
?>
|
||||
<i class="help-tip icon-question-sign"
|
||||
href="#require_agent_2fa"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo __('Agent Excessive Logins'); ?>:</td>
|
||||
<td>
|
||||
@@ -207,6 +223,7 @@ if (!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config
|
||||
<?php $manage_content(__('Agent Welcome Email'), 'registration-staff'); ?>
|
||||
<?php $manage_content(__('Sign-in Login Banner'), 'banner-staff'); ?>
|
||||
<?php $manage_content(__('Password Reset Email'), 'pwreset-staff'); ?>
|
||||
<?php $manage_content(__('Two Factor Authentication Email'), 'email2fa-staff'); ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -89,6 +89,26 @@ if(!defined('OSTADMININC') || !$thisstaff || !$thisstaff->isAdmin() || !$config)
|
||||
<i class="help-tip icon-question-sign" href="#registration_method"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><?php echo __('Password Policy'); ?>:</td>
|
||||
<td>
|
||||
<select name="client_passwd_policy">
|
||||
<option value=" "> — <?php echo __('All Active Policies'); ?> —</option>
|
||||
<?php
|
||||
foreach (PasswordPolicy::allActivePolicies()
|
||||
as $P) {
|
||||
echo sprintf('<option value="%s" %s>%s</option>',
|
||||
$P::$id,
|
||||
(($config['client_passwd_policy'] == $P::$id) ? 'selected="selected"' : ''),
|
||||
$P->getName());
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<font class="error"><?php echo
|
||||
$errors['client_passwd_policy']; ?></font>
|
||||
<i class="help-tip icon-question-sign" href="#client_password_policy"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td><?php echo __('User Excessive Logins'); ?>:</td>
|
||||
<td>
|
||||
<select name="client_max_logins">
|
||||
|
||||
@@ -19,6 +19,8 @@ if ($_REQUEST['a']=='add'){
|
||||
Organization::PERM_EDIT,
|
||||
Organization::PERM_DELETE,
|
||||
FAQ::PERM_MANAGE,
|
||||
Dept::PERM_DEPT,
|
||||
Staff::PERM_STAFF,
|
||||
));
|
||||
}
|
||||
$title=__('Add New Agent');
|
||||
@@ -162,6 +164,35 @@ if (count($bks) > 1) {
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
} ?>
|
||||
<?php
|
||||
if ($bks=Staff2FABackend::allRegistered() && $current = $staff->get2FABackend()) {
|
||||
$_config = $staff->getConfig();
|
||||
?>
|
||||
<tr>
|
||||
<td><?php echo __('Default 2FA'); ?>:</td>
|
||||
<td>
|
||||
<input type="text" size="40" style="width:300px"
|
||||
name="default_2fa" disabled value="<?php echo $current->getName(); ?>" />
|
||||
|
||||
<button type="button" id="reset-2fa" class="action-button" onclick="javascript:
|
||||
if (confirm('<?php echo __('You sure?'); ?>')) {
|
||||
$.ajax({
|
||||
url: 'ajax.php/staff/'+<?php echo $staff->getId(); ?>+'/reset-2fa',
|
||||
type: 'POST',
|
||||
data: {'staffId':<?php echo $staff->getId(); ?>},
|
||||
success: function(data) {
|
||||
location.reload();
|
||||
}
|
||||
});
|
||||
}
|
||||
return false;">
|
||||
<i class="icon-gear"></i> <?php echo __('Reset 2FA'); ?>
|
||||
</button>
|
||||
<i class="offset help-tip icon-question-sign" href="#reset2fa"></i>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
} ?>
|
||||
</tbody>
|
||||
<!-- ================================================ -->
|
||||
|
||||
113
include/staff/templates/2fas.tmpl.php
Normal file
113
include/staff/templates/2fas.tmpl.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
$title = __('Manage 2FA Options');
|
||||
if ($auth)
|
||||
$title = sprintf('%s %s %s', $auth->getName(), '2FA', __('Setup'));
|
||||
?>
|
||||
<h3 class="drag-handle"><?php echo $title; ?></h3>
|
||||
<a class="close" href=""><i class="icon-remove-circle"></i></a>
|
||||
<hr/>
|
||||
<?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['notice']) {
|
||||
echo sprintf('<p id="msg_notice">%s</p>', $info['notice']);
|
||||
} ?>
|
||||
<div id="backends" <?php if ($auth) echo 'class="hidden"'; ?>>
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<?php
|
||||
foreach (Staff2FABackend::allRegistered() ?: array() as $bk) {
|
||||
$configuration = $staff->get2FAConfig($bk->getId());
|
||||
$isVerified = $configuration['verified'];
|
||||
$vclass = $isVerified ? 'verified' : 'unverified'; ?>
|
||||
<tr id="<?php echo $bk->getId(); ?>" class="2fa-type <?php echo $vclass; ?>">
|
||||
<td nowrap width="10px">
|
||||
<i class="faded-more <?php echo sprintf('icon-check-%s',
|
||||
$isVerified ? 'sign' : 'empty'); ?>"></i>
|
||||
<span data-name="label"></span>
|
||||
</td>
|
||||
<td width="300px">
|
||||
<a class="config2fa"
|
||||
href="<?php echo sprintf('#staff/%d/2fa/configure/%s',
|
||||
$staff->getId(), urlencode($bk->getId())); ?>"> <?php echo $bk->getName(); ?>
|
||||
</a>
|
||||
<div align="left" class="faded"><?php
|
||||
echo Format::htmlchars($bk->getDescription()); ?></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
} ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<hr>
|
||||
</div>
|
||||
<div id="backend" <?php if (!$auth) echo 'class="hidden"'; ?>>
|
||||
<?php
|
||||
if ($auth && $form) {
|
||||
if ($state == 'verify')
|
||||
$instruction = __('Enter the token sent to you and click Verify');
|
||||
else
|
||||
$instruction = __('Complete the form below and then click Next to verify the setup');
|
||||
?>
|
||||
<div><?php echo Format::htmlchars($instruction); ?></div>
|
||||
<br>
|
||||
<form class="bk" method="post" action="<?php echo sprintf('#staff/%d/2fa/configure/%s',
|
||||
$staff->getId(), $auth->getId()); ?>">
|
||||
<input type="hidden" name="state" value="<?php
|
||||
echo $state ?: 'validate'; ?>" />
|
||||
<?php
|
||||
echo csrf_token();
|
||||
include STAFFINC_DIR . 'templates/dynamic-form-simple.tmpl.php';
|
||||
?>
|
||||
<br>
|
||||
<hr>
|
||||
<p class="full-width">
|
||||
<span class="buttons pull-left">
|
||||
<input type="reset" value="<?php echo __('Reset'); ?>">
|
||||
<input type="button" name="close" value="<?php echo __('Cancel'); ?>" class="close">
|
||||
</span>
|
||||
<span class="buttons pull-right">
|
||||
<input type="submit" value="<?php echo ($state == 'verify') ?
|
||||
__('Verify') : __('Next'); ?>">
|
||||
</span>
|
||||
</p>
|
||||
</form>
|
||||
<?php
|
||||
} ?>
|
||||
</div>
|
||||
<div class="clear"></div>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
var ids = $('.verified').map(function() {
|
||||
id2fa = $(this).attr('id');
|
||||
document.getElementById(id2fa).disabled=false;
|
||||
});
|
||||
|
||||
$('a.config2fa').click( function(e) {
|
||||
e.preventDefault();
|
||||
if ($(this).attr('href').length > 1) {
|
||||
var url = 'ajax.php/'+$(this).attr('href').substr(1);
|
||||
$.dialog(url, [201, 204], function (xhr) {
|
||||
window.location.href = window.location.href;
|
||||
}, {
|
||||
onshow: function() { $('#user-search').focus(); }
|
||||
});
|
||||
} else {
|
||||
$('div#backends').hide();
|
||||
$('div#backend').fadeIn();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(document).on('click', 'input.close', function (e) {
|
||||
e.preventDefault();
|
||||
alert('Alert');
|
||||
$('div#backend').hide();
|
||||
$('div#backends').fadeIn();
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@@ -111,7 +111,7 @@ $(function() {
|
||||
$('#search-add-new-field').on('change', function() {
|
||||
var that=this;
|
||||
$.ajax({
|
||||
url: 'ajax.php/tickets/search/field/'+$(this).val(),
|
||||
url: 'ajax.php/tickets/search/field/'+$(this).val()+'/<?php echo $filterVisibility ? 'true' : 'false' ?>',
|
||||
type: 'get',
|
||||
dataType: 'json',
|
||||
success: function(json) {
|
||||
|
||||
@@ -44,7 +44,7 @@ $action = $info[':action'] ?: ('#');
|
||||
<tbody>
|
||||
<tr><td colspan=2>
|
||||
<?php
|
||||
$options = array('template' => 'simple', 'form_id' => 'assign');
|
||||
$options = array('template' => 'simple', 'form_id' => 'assign', 'filterVisibility' => true);
|
||||
$form->render($options);
|
||||
?>
|
||||
</td> </tr>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div class="form-simple">
|
||||
<?php
|
||||
echo $form->getMedia();
|
||||
echo $form->getMedia($options);
|
||||
foreach ($form->getFields() as $name=>$f) { ?>
|
||||
<div class="flush-left custom-field" id="field<?php echo $f->getWidget()->id;
|
||||
?>" <?php if (!$f->isVisible()) echo 'style="display:none;"'; ?>>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
global $cfg;
|
||||
|
||||
$options = array('template' => 'simple', 'form_id' => $form->getId());
|
||||
$options = array('template' => 'simple', 'form_id' => $form->getId(), 'filterVisibility' => true);
|
||||
|
||||
?>
|
||||
<h3 class="drag-handle"><?php echo $info[':title']; ?></h3>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
if(($tabs=$nav->getTabs()) && is_array($tabs)){
|
||||
if($nav && ($tabs=$nav->getTabs()) && is_array($tabs)){
|
||||
foreach($tabs as $name =>$tab) {
|
||||
if ($tab['href'][0] != '/')
|
||||
$tab['href'] = ROOT_PATH . 'scp/' . $tab['href'];
|
||||
|
||||
@@ -53,7 +53,7 @@ $manage = (!$target);
|
||||
<tbody>
|
||||
<tr><td colspan=2>
|
||||
<?php
|
||||
$options = array('template' => 'simple', 'form_id' => 'refer');
|
||||
$options = array('template' => 'simple', 'form_id' => 'refer', 'filterVisibility' => true);
|
||||
$form->render($options);
|
||||
?>
|
||||
</td> </tr>
|
||||
|
||||
@@ -34,7 +34,7 @@ if ($info['error']) {
|
||||
echo $form->getForm(false, array('mode' => 'edit'))->asTable(
|
||||
__('Task Information'),
|
||||
array(
|
||||
'draft-namespace' => $namespace,
|
||||
'draft-namespace' => $namespace, 'filterVisibility' => true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ if ($info['error']) {
|
||||
<?php
|
||||
$form = $form ?: TaskForm::getInstance();
|
||||
echo $form->getForm($vars)->asTable(' ',
|
||||
array('draft-namespace' => $namespace)
|
||||
array('draft-namespace' => $namespace, 'filterVisibility' => true)
|
||||
);
|
||||
|
||||
$iform = $iform ?: TaskForm::getInternalForm();
|
||||
echo $iform->asTable(__("Task Visibility & Assignment"));
|
||||
echo $iform->asTable(__("Task Visibility & Assignment"), array('filterVisibility' => true));
|
||||
?>
|
||||
<hr>
|
||||
<p class="full-width">
|
||||
|
||||
@@ -39,7 +39,7 @@ $action = $info[':action'] ?: ('#');
|
||||
<tbody>
|
||||
<tr><td colspan=2>
|
||||
<?php
|
||||
$options = array('template' => 'simple', 'form_id' => 'transfer');
|
||||
$options = array('template' => 'simple', 'form_id' => 'transfer', 'filterVisibility' => true);
|
||||
$form->render($options);
|
||||
?>
|
||||
</td> </tr>
|
||||
|
||||
@@ -93,7 +93,7 @@ if ($_POST)
|
||||
<select name="topicId">
|
||||
<option value="" selected >— <?php echo __('Select Help Topic');?> —</option>
|
||||
<?php
|
||||
if($topics=Topic::getHelpTopics()) {
|
||||
if ($topics=$thisstaff->getTopicNames()) {
|
||||
if($ticket->topic_id && !array_key_exists($ticket->topic_id, $topics)) {
|
||||
$topics[$ticket->topic_id] = $ticket->topic;
|
||||
$errors['topicId'] = sprintf(__('%s selected must be active'), __('Help Topic'));
|
||||
@@ -154,7 +154,7 @@ if ($_POST)
|
||||
<table class="form_table dynamic-forms" width="940" border="0" cellspacing="0" cellpadding="2">
|
||||
<?php if ($forms)
|
||||
foreach ($forms as $form) {
|
||||
$form->render(array('staff'=>true,'mode'=>'edit','width'=>160,'entry'=>$form));
|
||||
$form->render(array('staff'=>true,'mode'=>'edit','width'=>160,'entry'=>$form, 'filterVisibility' => true));
|
||||
} ?>
|
||||
</table>
|
||||
<table class="form_table" width="940" border="0" cellspacing="0" cellpadding="2">
|
||||
|
||||
@@ -228,7 +228,7 @@ if ($_POST)
|
||||
}
|
||||
});">
|
||||
<?php
|
||||
if ($topics=Topic::getHelpTopics(false, false, true)) {
|
||||
if ($topics=$thisstaff->getTopicNames(false, false)) {
|
||||
if (count($topics) == 1)
|
||||
$selected = 'selected="selected"';
|
||||
else { ?>
|
||||
@@ -257,7 +257,7 @@ if ($_POST)
|
||||
<select name="deptId">
|
||||
<option value="" selected >— <?php echo __('Select Department'); ?>—</option>
|
||||
<?php
|
||||
if($depts=Dept::getPublicDepartments()) {
|
||||
if($depts=$thisstaff->getDepartmentNames(true)) {
|
||||
foreach($depts as $id =>$name) {
|
||||
if (!($role = $thisstaff->getRole($id))
|
||||
|| !$role->hasPerm(Ticket::PERM_CREATE)
|
||||
@@ -318,7 +318,7 @@ if ($_POST)
|
||||
<select id="assignId" name="assignId">
|
||||
<option value="0" selected="selected">— <?php echo __('Select an Agent OR a Team');?> —</option>
|
||||
<?php
|
||||
if(($users=Staff::getAvailableStaffMembers())) {
|
||||
if(($users=$assignees = $thisstaff->getDeptAgents(array('available' => true, 'namesOnly' => true)))) {
|
||||
echo '<OPTGROUP label="'.sprintf(__('Agents (%d)'), count($users)).'">';
|
||||
foreach($users as $id => $name) {
|
||||
$k="s$id";
|
||||
|
||||
@@ -836,9 +836,10 @@ if ($errors['err'] && isset($_POST['a'])) {
|
||||
$e->getId(),
|
||||
Format::htmlchars($e->getAddress()));
|
||||
}
|
||||
$staffDepts = $thisstaff->getDepts();
|
||||
// Optional SMTP addreses user can send email via
|
||||
if (($emails = Email::getAddresses(array('smtp' =>
|
||||
true), false)) && count($emails)) {
|
||||
if (($emails = Email::getAddresses(array('smtp' => true,
|
||||
'depts' => $staffDepts), false)) && count($emails)) {
|
||||
echo '<option value=""
|
||||
disabled="disabled"> </option>';
|
||||
$emailId = $_POST['from_email_id'] ?: 0;
|
||||
|
||||
@@ -1 +1 @@
|
||||
4bd47d94b10bd8a6bab35c119dadf41f
|
||||
add628927ee030469f5d3272ebda1e16
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Rename old thread_event table
|
||||
RENAME TABLE `%TABLE_PREFIX%thread_entry_email` TO `%TABLE_PREFIX%thread_entry_email_old`;
|
||||
|
||||
-- Change tmp_table to thread_event
|
||||
RENAME TABLE `%TABLE_PREFIX%thread_entry_email_new` TO `%TABLE_PREFIX%thread_entry_email`;
|
||||
|
||||
-- Drop old thread_event table
|
||||
DROP TABLE `%TABLE_PREFIX%thread_entry_email_old`;
|
||||
24
include/upgrader/streams/core/4bd47d94-e7038ce9.patch.sql
Normal file
24
include/upgrader/streams/core/4bd47d94-e7038ce9.patch.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* @signature e7038ce9762843b6cfbe9fa2d98feaf3
|
||||
* @version v1.15
|
||||
* @title Add email_id to thread_entry_email
|
||||
*
|
||||
* This patch adds an email_id column to the thread_entry_email table
|
||||
*/
|
||||
-- Create a blank temporary table with the new email_id column
|
||||
CREATE TABLE `%TABLE_PREFIX%thread_entry_email_new` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`thread_entry_id` int(11) unsigned NOT NULL,
|
||||
`email_id` int(11) unsigned DEFAULT NULL,
|
||||
`mid` varchar(255) NOT NULL,
|
||||
`headers` text,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `thread_entry_id` (`thread_entry_id`),
|
||||
KEY `mid` (`mid`),
|
||||
KEY `email_id` (`email_Id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- Finished with patch
|
||||
UPDATE `%TABLE_PREFIX%config`
|
||||
SET `value` = 'e7038ce9762843b6cfbe9fa2d98feaf3'
|
||||
WHERE `key` = 'schema_signature' AND `namespace` = 'core';
|
||||
154
include/upgrader/streams/core/4bd47d94-e7038ce9.task.php
Normal file
154
include/upgrader/streams/core/4bd47d94-e7038ce9.task.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Populate the temporary thread_entry_email table in batches
|
||||
* to avoid an alter table statement to add the email_id column.
|
||||
*/
|
||||
require_once INCLUDE_DIR.'class.migrater.php';
|
||||
define('THREAD_ENTRY_EMAIL_NEW_TABLE', TABLE_PREFIX.'thread_entry_email_new');
|
||||
|
||||
class ThreadEntryEmailNew extends VerySimpleModel {
|
||||
static $meta = array(
|
||||
'table' => THREAD_ENTRY_EMAIL_NEW_TABLE,
|
||||
'pk' => array('id'),
|
||||
);
|
||||
}
|
||||
|
||||
class ThreadEntryEmailMigration extends MigrationTask {
|
||||
var $description = "Add an email_id column to the thread_entry_email table";
|
||||
var $queue;
|
||||
var $skipList;
|
||||
var $errorList = array();
|
||||
var $limit = 20000;
|
||||
|
||||
function sleep() {
|
||||
return array('queue'=>$this->queue, 'skipList'=>$this->skipList);
|
||||
}
|
||||
function wakeup($stuff) {
|
||||
$this->queue = $stuff['queue'];
|
||||
$this->skipList = $stuff['skipList'];
|
||||
while (!$this->isFinished())
|
||||
$this->do_batch(30, $this->limit);
|
||||
}
|
||||
|
||||
function run($max_time) {
|
||||
$this->do_batch($max_time * 0.9, $this->limit);
|
||||
}
|
||||
|
||||
function isFinished() {
|
||||
return $this->getQueueLength() == 0;
|
||||
}
|
||||
|
||||
function do_batch($time=30, $max=0) {
|
||||
if(!$this->queueEvents($max) || !$this->getQueueLength())
|
||||
return 0;
|
||||
|
||||
$this->setStatus("{$this->getQueueLength()} records remaining");
|
||||
|
||||
$count = 0;
|
||||
$start = Misc::micro_time();
|
||||
while ($this->getQueueLength() && (Misc::micro_time()-$start) < $time) {
|
||||
if($this->next() && $max && ++$count>=$max) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->queueEvents($max);
|
||||
}
|
||||
|
||||
function queueEvents($limit=0){
|
||||
global $cfg, $ost;
|
||||
|
||||
# Since the queue is persistent - we want to make sure we get to empty
|
||||
# before we find more events.
|
||||
if(($qc=$this->getQueueLength()))
|
||||
return $qc;
|
||||
|
||||
// Count how many rows are left
|
||||
$sql = "SELECT COUNT(old.id)
|
||||
FROM ". THREAD_ENTRY_EMAIL_TABLE." old
|
||||
WHERE old.id NOT IN (SELECT new.id FROM ". THREAD_ENTRY_EMAIL_NEW_TABLE ." new)";
|
||||
|
||||
//XXX: Do a hard fail or error querying the database?
|
||||
if(!($res=db_query($sql)))
|
||||
return $this->error('Unable to query DB for Thread Entry Email migration!');
|
||||
|
||||
$count = db_result($res);
|
||||
|
||||
// Force the log message to the database
|
||||
$ost->logDebug("Thread Entry Email Migration", 'Found '.$count
|
||||
.' records to migrate', true);
|
||||
|
||||
if($count == 0)
|
||||
return 0; //Nothing else to do!!
|
||||
|
||||
// Get id of the next record to be inserted into the new table
|
||||
$start = db_result(db_query("SELECT id
|
||||
FROM ". THREAD_ENTRY_EMAIL_TABLE ."
|
||||
WHERE id > (SELECT MAX(id) FROM ". THREAD_ENTRY_EMAIL_NEW_TABLE .")
|
||||
LIMIT 1"));
|
||||
$start = intval($start);
|
||||
|
||||
$this->queue = array();
|
||||
$info=array(
|
||||
'count' => $count,
|
||||
'start' => $start,
|
||||
'end' => $start + $limit
|
||||
);
|
||||
$this->enqueue($info);
|
||||
|
||||
return $this->getQueueLength();
|
||||
}
|
||||
|
||||
function skip($id, $error) {
|
||||
$this->skipList[] = $id;
|
||||
|
||||
return $this->error($error." (ID #$id)");
|
||||
}
|
||||
|
||||
function error($what) {
|
||||
global $ost;
|
||||
|
||||
$this->errors++;
|
||||
$this->errorList[] = $what;
|
||||
// Log the error but don't send the alert email
|
||||
$ost->logError('Upgrader: Thread Entry Email Migrater', $what, false);
|
||||
# Assist in returning FALSE for inline returns with this method
|
||||
return false;
|
||||
}
|
||||
|
||||
function getErrors() {
|
||||
return $this->errorList;
|
||||
}
|
||||
|
||||
function getSkipList() {
|
||||
return $this->skipList;
|
||||
}
|
||||
|
||||
function enqueue($info) {
|
||||
$this->queue[] = $info;
|
||||
}
|
||||
|
||||
function getQueueLength() {
|
||||
return $this->queue[0]['count'];
|
||||
}
|
||||
|
||||
function next() {
|
||||
# Fetch next item -- use the last item so the array indices don't
|
||||
# need to be recalculated for every shift() operation.
|
||||
$info = array_pop($this->queue);
|
||||
|
||||
// Insert rows into the new table starting at the start and stopping at the end id
|
||||
$sql = "INSERT INTO ". THREAD_ENTRY_EMAIL_NEW_TABLE ."
|
||||
SELECT B.id, B.thread_entry_id, NULL, B.mid, B.headers
|
||||
FROM ". THREAD_ENTRY_EMAIL_TABLE . " B
|
||||
WHERE B.id >= ".$info['start']." AND B.id < ". $info['end'];
|
||||
|
||||
db_query($sql);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return 'ThreadEntryEmailMigration';
|
||||
|
||||
?>
|
||||
17
include/upgrader/streams/core/ddbe2e76-add62892.patch.sql
Normal file
17
include/upgrader/streams/core/ddbe2e76-add62892.patch.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @signature add628927ee030469f5d3272ebda1e16
|
||||
* @version v1.15
|
||||
* @title Alter dept_id and priority_id in email table
|
||||
*
|
||||
* This patch changes dept_id and priority_id fron tinyint to int
|
||||
*/
|
||||
|
||||
-- change dept_id
|
||||
ALTER TABLE `%TABLE_PREFIX%email` CHANGE `dept_id` `dept_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT '0';
|
||||
-- change priority_id
|
||||
ALTER TABLE `%TABLE_PREFIX%email` CHANGE `priority_id` `priority_id` INT( 11 ) UNSIGNED NOT NULL DEFAULT '2';
|
||||
|
||||
-- Finished with patch
|
||||
UPDATE `%TABLE_PREFIX%config`
|
||||
SET `value` = 'add628927ee030469f5d3272ebda1e16'
|
||||
WHERE `key` = 'schema_signature' AND `namespace` = 'core';
|
||||
47
include/upgrader/streams/core/e7038ce9-ddbe2e76.patch.sql
Normal file
47
include/upgrader/streams/core/e7038ce9-ddbe2e76.patch.sql
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @signature ddbe2e76ec38a2e58bdbff9109c07930
|
||||
* @version v1.15
|
||||
* @title Add indexes accumulated over time
|
||||
*
|
||||
* This patch adds all indexes we've implemented over time and adds the
|
||||
* `version` column to the plugin table (if not exists)
|
||||
*/
|
||||
|
||||
-- Add FLAG_AGENT_REQUIRED For Task Title
|
||||
UPDATE `%TABLE_PREFIX%form_field`
|
||||
SET `flags` = IF((`flags` & 16384) != 16384, `flags` | 16384, `flags`)
|
||||
WHERE `form_id` = (SELECT `id` FROM `%TABLE_PREFIX%form` WHERE `type` = 'A') AND `name` = 'title';
|
||||
|
||||
-- Add FLAG_AGENT_VIEW for Task Title/Description
|
||||
UPDATE `%TABLE_PREFIX%form_field`
|
||||
SET `flags` = IF((`flags` & 4096) != 4096, `flags` | 4096, `flags`)
|
||||
WHERE `form_id` = (SELECT `id` FROM `%TABLE_PREFIX%form` WHERE `type` = 'A') AND `name` IN ('title', 'description');
|
||||
|
||||
-- Add FLAG_MASK_REQUIRE for Task Title
|
||||
UPDATE `%TABLE_PREFIX%form_field`
|
||||
SET `flags` = IF((`flags` & 65536) != 65536, `flags` | 65536, `flags`)
|
||||
WHERE `form_id` = (SELECT `id` FROM `%TABLE_PREFIX%form` WHERE `type` = 'A') AND `name` = 'title';
|
||||
|
||||
-- Add FLAG_MASK_VIEW for Task Title/Description
|
||||
UPDATE `%TABLE_PREFIX%form_field`
|
||||
SET `flags` = IF((`flags` & 131072) != 131072, `flags` | 131072, `flags`)
|
||||
WHERE `form_id` = (SELECT `id` FROM `%TABLE_PREFIX%form` WHERE `type` = 'A') AND `name` IN ('title', 'description');
|
||||
|
||||
-- Add `version` column to the plugin table (if not exists)
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE table_name = '%TABLE_PREFIX%plugin'
|
||||
AND table_schema = DATABASE()
|
||||
AND column_name = 'version'
|
||||
) > 0,
|
||||
"SELECT 1",
|
||||
"ALTER TABLE `%TABLE_PREFIX%plugin` ADD `version` VARCHAR(64) DEFAULT NULL AFTER `isactive`"
|
||||
));
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
-- Finished with patch
|
||||
UPDATE `%TABLE_PREFIX%config`
|
||||
SET `value` = 'ddbe2e76ec38a2e58bdbff9109c07930'
|
||||
WHERE `key` = 'schema_signature' AND `namespace` = 'core';
|
||||
120
include/upgrader/streams/core/e7038ce9-ddbe2e76.task.php
Normal file
120
include/upgrader/streams/core/e7038ce9-ddbe2e76.task.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* Add all indexes we've implemented over time. Check if exists
|
||||
* to avoid errors.
|
||||
*/
|
||||
class AddIndexMigration extends MigrationTask {
|
||||
var $description = "Add indexes accumulated over time";
|
||||
var $indexes = array(
|
||||
'department:flags',
|
||||
'draft:staff_id',
|
||||
'draft:namespace',
|
||||
'file:type',
|
||||
'file:created',
|
||||
'file:size',
|
||||
'form:type',
|
||||
'form_field:form_id',
|
||||
'form_field:sort',
|
||||
'queue:staff_id',
|
||||
'queue:parent_id',
|
||||
'staff:isactive',
|
||||
'staff:onvacation',
|
||||
'task:flags',
|
||||
'ticket:ticket_pid',
|
||||
'team_member:staff_id',
|
||||
'user:default_email_id',
|
||||
'user:name'
|
||||
);
|
||||
|
||||
function run($max_time) {
|
||||
global $ost;
|
||||
|
||||
foreach ($this->indexes as $index) {
|
||||
list($t, $i) = explode(':', $index);
|
||||
|
||||
// Check if INDEX already exists via SHOW INDEX
|
||||
$sql = sprintf("SHOW INDEX FROM `%s` WHERE `Column_name` = '%s' AND `Key_name` != 'PRIMARY'",
|
||||
TABLE_PREFIX.$t, $i);
|
||||
|
||||
// Hardfail if we cannot check if exists
|
||||
if(!($res=db_query($sql)))
|
||||
return $this->error('Unable to query DB for Add Index migration!');
|
||||
|
||||
$count = db_num_rows($res);
|
||||
|
||||
if (!$count || ($count && ($count == 0))) {
|
||||
// CREATE INDEX if not exists
|
||||
$create = sprintf('CREATE INDEX `%s` ON `%s`.`%s` (`%s`)',
|
||||
$i, DBNAME, TABLE_PREFIX.$t, $i);
|
||||
|
||||
if(!($res=db_query($create))) {
|
||||
$message = "Unable to create index `$i` on `".TABLE_PREFIX.$t."`.";
|
||||
// Log the error but don't send the alert email
|
||||
$ost->logError('Upgrader: Add Index Migrater', $message, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//add permissions to staff
|
||||
foreach (Staff::objects() as $staff) {
|
||||
$perms = array();
|
||||
foreach ($staff->getPermissionInfo() as $value => $setting)
|
||||
$perms[] = $value;
|
||||
|
||||
array_push($perms, 'visibility.departments', 'visibility.agents');
|
||||
$staff->updatePerms($perms);
|
||||
$staff->save();
|
||||
}
|
||||
|
||||
//add 2fa template
|
||||
foreach (array('email2fa-staff') as $type) {
|
||||
$i18n = new Internationalization();
|
||||
$tpl = $i18n->getTemplate("templates/page/{$type}.yaml");
|
||||
if (!($page = $tpl->getData()))
|
||||
// No such template on disk
|
||||
continue;
|
||||
|
||||
if ($id = db_result(db_query('select id from '.PAGE_TABLE
|
||||
.' where `type`='.db_input($type))))
|
||||
// Already have a template for the content type
|
||||
continue;
|
||||
|
||||
$sql = 'INSERT INTO '.PAGE_TABLE.' SET type='.db_input($type)
|
||||
.', name='.db_input($page['name'])
|
||||
.', body='.db_input($page['body'])
|
||||
.', notes='.db_input($page['notes'])
|
||||
.', created=NOW(), updated=NOW(), isactive=1';
|
||||
db_query($sql);
|
||||
}
|
||||
|
||||
// See if there are missing events that should be added to the database
|
||||
$event_type = array('login', 'logout', 'message', 'note');
|
||||
foreach($event_type as $eType) {
|
||||
$sql = sprintf("SELECT * FROM `%s` WHERE name = '%s'",
|
||||
TABLE_PREFIX.'event', $eType);
|
||||
|
||||
$res=db_query($sql);
|
||||
$count = db_num_rows($res);
|
||||
|
||||
if($count > 0) {
|
||||
$message = "Event '$eType' already exists.";
|
||||
$ost->logError('Upgrader: Add Events', $message, false);
|
||||
} else {
|
||||
// Add event
|
||||
$sql = sprintf("INSERT INTO `%s` (`id`, `name`, `description`)
|
||||
VALUES
|
||||
('','%s',NULL)",
|
||||
TABLE_PREFIX.'event', $eType);
|
||||
|
||||
if(!($res=db_query($sql))) {
|
||||
$message = "Unable to add $eType event to `".TABLE_PREFIX.'event'."`.";
|
||||
$ost->logError('Upgrader: Add Events', $message, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 'AddIndexMigration';
|
||||
|
||||
?>
|
||||
@@ -203,7 +203,7 @@ $dispatcher = patterns('',
|
||||
url_post('^/(?P<id>\d+)/save$', 'saveSearch'),
|
||||
url_post('^/save$', 'saveSearch'),
|
||||
url_delete('^/(?P<id>\d+)$', 'deleteSearch'),
|
||||
url_get('^/field/(?P<id>[\w_!:]+)$', 'addField'),
|
||||
url_get('^/field/(?P<id>[\w_!:]+)/(?P<type>\w+)$', 'addField'),
|
||||
url('^/column/edit/(?P<id>\d+)$', 'editColumn'),
|
||||
url('^/sort/edit/(?P<id>\d+)$', 'editSort'),
|
||||
url_post('^(?P<id>\d+)/delete$', 'deleteQueues'),
|
||||
@@ -295,7 +295,9 @@ $dispatcher = patterns('',
|
||||
url_get('^/(?P<id>\d+)/perms', 'getAgentPerms'),
|
||||
url('^/reset-permissions', 'resetPermissions'),
|
||||
url('^/change-department', 'changeDepartment'),
|
||||
url('^/(?P<id>\d+)/avatar/change', 'setAvatar')
|
||||
url('^/(?P<id>\d+)/avatar/change', 'setAvatar'),
|
||||
url('^/(?P<id>\d+)/2fa/configure(?:/(?P<mfid>.+))?$', 'configure2FA'),
|
||||
url('^/(?P<id>\d+)/reset-2fa', 'reset2fA')
|
||||
)),
|
||||
url('^/queue/', patterns('ajax.search.php:SearchAjaxAPI',
|
||||
url('^(?P<id>\d+/)?preview$', 'previewQueue'),
|
||||
|
||||
@@ -16,9 +16,17 @@
|
||||
require('staff.inc.php');
|
||||
include_once(INCLUDE_DIR.'class.canned.php');
|
||||
|
||||
if ($thisstaff && $roles = $thisstaff->getRoles()) {
|
||||
$cannedManage = array();
|
||||
foreach ($roles as $r) {
|
||||
if ($r->hasPerm(Canned::PERM_MANAGE, false))
|
||||
$cannedManage[] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* check permission */
|
||||
if(!$thisstaff
|
||||
|| !$thisstaff->getRole()->hasPerm(Canned::PERM_MANAGE, false)
|
||||
|| !in_array(1, $cannedManage)
|
||||
|| !$cfg->isCannedResponseEnabled()) {
|
||||
header('Location: kb.php');
|
||||
exit;
|
||||
|
||||
@@ -24,7 +24,7 @@ require_once(INCLUDE_DIR.'class.staff.php');
|
||||
require_once(INCLUDE_DIR.'class.csrf.php');
|
||||
|
||||
$content = Page::lookupByType('banner-staff');
|
||||
|
||||
$thisstaff = StaffAuthenticationBackend::getUser();
|
||||
$dest = $_SESSION['_staff']['auth']['dest'];
|
||||
$msg = $_SESSION['_staff']['auth']['msg'];
|
||||
$msg = $msg ?: ($content ? $content->getLocalName() : __('Authentication Required'));
|
||||
@@ -70,7 +70,7 @@ if ($_POST && isset($_POST['userid'])) {
|
||||
$username = trim($_POST['userid']);
|
||||
if ($user = StaffAuthenticationBackend::process($username,
|
||||
$_POST['passwd'], $errors)) {
|
||||
$redirect($dest);
|
||||
$redirect($user->isValid() ? $dest : 'login.php');
|
||||
}
|
||||
|
||||
$msg = $errors['err'] ?: __('Invalid login');
|
||||
@@ -84,6 +84,31 @@ if ($_POST && isset($_POST['userid'])) {
|
||||
$ost->getCSRF()->rotate();
|
||||
}
|
||||
}
|
||||
elseif ($_POST
|
||||
&& !strcmp($_POST['do'], '2fa')
|
||||
&& $thisstaff
|
||||
&& $thisstaff->is2FAPending()
|
||||
&& ($auth=$thisstaff->get2FABackend())) {
|
||||
|
||||
try {
|
||||
$form = $auth->getInputForm($_POST);
|
||||
if ($form->isValid() && $auth->validate($form, $thisstaff))
|
||||
$redirect($dest);
|
||||
} catch (ExpiredOTP $ex) {
|
||||
// Expired or too many attempts
|
||||
$thisstaff->logOut();
|
||||
$redirect('login.php');
|
||||
}
|
||||
|
||||
$msg = __('Invalid Code');
|
||||
if ($json) {
|
||||
$respond(401, ['message' => $msg]);
|
||||
}
|
||||
else {
|
||||
// Rotate the CSRF token (original cannot be reused)
|
||||
$ost->getCSRF()->rotate();
|
||||
}
|
||||
}
|
||||
elseif ($_GET['do']) {
|
||||
switch ($_GET['do']) {
|
||||
case 'ext':
|
||||
|
||||
@@ -44,6 +44,15 @@ if($thisstaff->forcePasswdChange() && !$errors['err'])
|
||||
$thisstaff->getFirstName()
|
||||
)
|
||||
);
|
||||
elseif($thisstaff->force2faConfig() && !$errors['err'])
|
||||
$errors['err'] = str_replace(
|
||||
'<a>',
|
||||
sprintf('<a data-dialog="ajax.php/staff/%d/2fa/configure" href="#">', $thisstaff->getId()),
|
||||
sprintf(
|
||||
__('<b>Hi %s</b> - You must <a>configure and save Two Factor Authentication </a>!'),
|
||||
$thisstaff->getFirstName()
|
||||
)
|
||||
);
|
||||
elseif($thisstaff->onVacation() && !$warn)
|
||||
$warn=sprintf(__("<b>Welcome back %s</b>! You are listed as 'on vacation' Please let your manager know that you are back."),$thisstaff->getFirstName());
|
||||
|
||||
|
||||
@@ -136,6 +136,10 @@ if($thisstaff->forcePasswdChange() && !$exempt) {
|
||||
$sysnotice = __('Password change required to continue');
|
||||
require('profile.php'); //profile.php must request this file as require_once to avoid problems.
|
||||
exit;
|
||||
} elseif ($thisstaff->force2faConfig() && !$exempt) {
|
||||
$sysnotice = __('Two Factor Authentication configuration required to continue');
|
||||
require('profile.php');
|
||||
exit;
|
||||
}
|
||||
$ost->setWarning($sysnotice);
|
||||
$ost->setPageTitle(__('osTicket :: Staff Control Panel'));
|
||||
|
||||
@@ -206,6 +206,8 @@ class Installer extends SetupWizard {
|
||||
Organization::PERM_DELETE,
|
||||
FAQ::PERM_MANAGE,
|
||||
Email::PERM_BANLIST,
|
||||
Dept::PERM_DEPT,
|
||||
Staff::PERM_STAFF,
|
||||
));
|
||||
$staff->setPassword($vars['passwd']);
|
||||
if (!$staff->save()) {
|
||||
|
||||
@@ -124,7 +124,8 @@ CREATE TABLE `%TABLE_PREFIX%form` (
|
||||
`notes` text,
|
||||
`created` datetime NOT NULL,
|
||||
`updated` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `type` (`type`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%form_field`;
|
||||
@@ -140,7 +141,9 @@ CREATE TABLE `%TABLE_PREFIX%form_field` (
|
||||
`hint` varchar(512),
|
||||
`created` datetime NOT NULL,
|
||||
`updated` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `form_id` (`form_id`),
|
||||
KEY `sort` (`sort`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%form_entry`;
|
||||
@@ -222,7 +225,8 @@ CREATE TABLE `%TABLE_PREFIX%department` (
|
||||
UNIQUE KEY `name` (`name`, `pid`),
|
||||
KEY `manager_id` (`manager_id`),
|
||||
KEY `autoresp_email_id` (`autoresp_email_id`),
|
||||
KEY `tpl_id` (`tpl_id`)
|
||||
KEY `tpl_id` (`tpl_id`),
|
||||
KEY `flags` (`flags`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%draft`;
|
||||
@@ -234,15 +238,17 @@ CREATE TABLE `%TABLE_PREFIX%draft` (
|
||||
`extra` text,
|
||||
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated` timestamp NULL DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `staff_id` (`staff_id`),
|
||||
KEY `namespace` (`namespace`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%email`;
|
||||
CREATE TABLE `%TABLE_PREFIX%email` (
|
||||
`email_id` int(11) unsigned NOT NULL auto_increment,
|
||||
`noautoresp` tinyint(1) unsigned NOT NULL default '0',
|
||||
`priority_id` tinyint(3) unsigned NOT NULL default '2',
|
||||
`dept_id` tinyint(3) unsigned NOT NULL default '0',
|
||||
`priority_id` int(11) unsigned NOT NULL default '2',
|
||||
`dept_id` int(11) unsigned NOT NULL default '0',
|
||||
`topic_id` int(11) unsigned NOT NULL default '0',
|
||||
`email` varchar(255) NOT NULL default '',
|
||||
`name` varchar(255) NOT NULL default '',
|
||||
@@ -388,7 +394,10 @@ CREATE TABLE `%TABLE_PREFIX%file` (
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `ft` (`ft`),
|
||||
KEY `key` (`key`),
|
||||
KEY `signature` (`signature`)
|
||||
KEY `signature` (`signature`),
|
||||
KEY `type` (`type`),
|
||||
KEY `created` (`created`),
|
||||
KEY `size` (`size`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%file_chunk`;
|
||||
@@ -600,7 +609,9 @@ CREATE TABLE `%TABLE_PREFIX%staff` (
|
||||
PRIMARY KEY (`staff_id`),
|
||||
UNIQUE KEY `username` (`username`),
|
||||
KEY `dept_id` (`dept_id`),
|
||||
KEY `issuperuser` (`isadmin`)
|
||||
KEY `issuperuser` (`isadmin`),
|
||||
KEY `isactive` (`isactive`),
|
||||
KEY `onvacation` (`onvacation`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%staff_dept_access`;
|
||||
@@ -646,7 +657,8 @@ CREATE TABLE `%TABLE_PREFIX%team_member` (
|
||||
`team_id` int(10) unsigned NOT NULL default '0',
|
||||
`staff_id` int(10) unsigned NOT NULL,
|
||||
`flags` int(10) unsigned NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`team_id`,`staff_id`)
|
||||
PRIMARY KEY (`team_id`,`staff_id`),
|
||||
KEY `staff_id` (`staff_id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%thread`;
|
||||
@@ -695,11 +707,13 @@ DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_entry_email`;
|
||||
CREATE TABLE `%TABLE_PREFIX%thread_entry_email` (
|
||||
`id` int(11) unsigned NOT NULL auto_increment,
|
||||
`thread_entry_id` int(11) unsigned NOT NULL,
|
||||
`email_id` int(11) unsigned DEFAULT NULL,
|
||||
`mid` varchar(255) NOT NULL,
|
||||
`headers` text,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `thread_entry_id` (`thread_entry_id`),
|
||||
KEY `mid` (`mid`)
|
||||
KEY `mid` (`mid`),
|
||||
KEY `email_id` (`email_id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%thread_entry_merge`;
|
||||
@@ -750,7 +764,8 @@ CREATE TABLE `%TABLE_PREFIX%ticket` (
|
||||
KEY `closed` (`closed`),
|
||||
KEY `duedate` (`duedate`),
|
||||
KEY `topic_id` (`topic_id`),
|
||||
KEY `sla_id` (`sla_id`)
|
||||
KEY `sla_id` (`sla_id`),
|
||||
KEY `ticket_pid` (`ticket_pid`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%lock`;
|
||||
@@ -872,7 +887,8 @@ CREATE TABLE `%TABLE_PREFIX%task` (
|
||||
KEY `staff_id` (`staff_id`),
|
||||
KEY `team_id` (`team_id`),
|
||||
KEY `created` (`created`),
|
||||
KEY `object` (`object_id`,`object_type`)
|
||||
KEY `object` (`object_id`,`object_type`),
|
||||
KEY `flags` (`flags`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
-- pages
|
||||
@@ -918,7 +934,9 @@ CREATE TABLE `%TABLE_PREFIX%queue` (
|
||||
`path` varchar(80) NOT NULL DEFAULT '/',
|
||||
`created` datetime not null,
|
||||
`updated` datetime not null,
|
||||
primary key (`id`)
|
||||
primary key (`id`),
|
||||
KEY `staff_id` (`staff_id`),
|
||||
KEY `parent_id` (`parent_id`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_column`;
|
||||
@@ -1014,7 +1032,9 @@ CREATE TABLE `%TABLE_PREFIX%user` (
|
||||
`created` datetime NOT NULL,
|
||||
`updated` datetime NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `org_id` (`org_id`)
|
||||
KEY `org_id` (`org_id`),
|
||||
KEY `default_email_id` (`default_email_id`),
|
||||
KEY `name` (`name`)
|
||||
) DEFAULT CHARSET=utf8;
|
||||
|
||||
DROP TABLE IF EXISTS `%TABLE_PREFIX%user_email`;
|
||||
|
||||
Reference in New Issue
Block a user