diff --git a/include/ajax.admin.php b/include/ajax.admin.php index ca34433480a46eb6091730a9d9068de46d6bdc1f..0e2f18f67bb430792874aa22a798dab5e7fad7fe 100644 --- a/include/ajax.admin.php +++ b/include/ajax.admin.php @@ -16,12 +16,15 @@ class AdminAjaxAPI extends AjaxController { * * Throws: * 403 - Not logged in + * 403 - Not an administrator */ function addDepartment() { global $ost, $thisstaff; if (!$thisstaff) Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); $form = new DepartmentQuickAddForm($_POST); @@ -60,12 +63,15 @@ class AdminAjaxAPI extends AjaxController { * * Throws: * 403 - Not logged in + * 403 - Not an adminitrator */ function addTeam() { global $ost, $thisstaff; if (!$thisstaff) Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); $form = new TeamQuickAddForm($_POST); diff --git a/include/ajax.staff.php b/include/ajax.staff.php new file mode 100644 index 0000000000000000000000000000000000000000..871fe9db7fd6846fbdc3c0f5748930adda85ae40 --- /dev/null +++ b/include/ajax.staff.php @@ -0,0 +1,61 @@ +<?php + +require_once(INCLUDE_DIR . 'class.staff.php'); + +class StaffAjaxAPI extends AjaxController { + + /** + * Ajax: GET /staff/<id>/set-password + * + * Uses a dialog to add a new department + * + * Returns: + * 200 - HTML form for addition + * 201 - {id: <id>, name: <name>} + * + * Throws: + * 403 - Not logged in + * 403 - Not an administrator + * 404 - No such agent exists + */ + function setPassword($id) { + global $ost, $thisstaff; + + if (!$thisstaff) + Http::response(403, 'Agent login required'); + if (!$thisstaff->isAdmin()) + Http::response(403, 'Access denied'); + if (!$id || !($staff = Staff::lookup($id))) + Http::response(404, 'No such agent'); + + $form = new PasswordResetForm($_POST); + + if ($_POST && $form->isValid()) { + $clean = $form->getClean(); + try { + if ($clean['email']) { + $staff->sendResetEmail(); + } + else { + $staff->setPassword($clean['passwd1'], null); + if ($clean['temporary']) + $staff->change_passwd = 1; + } + if ($staff->save()) + Http::response(201, 'Successfully updated'); + } + catch (BadPassword $ex) { + $passwd1 = $form->getField('passwd1'); + $passwd1->addError($ex->getMessage()); + } + catch (PasswordUpdateFailed $ex) { + // TODO: Add a warning banner or crash the update + } + } + + $title = __("Set Agent Password"); + $path = ltrim($ost->get_path_info(), '/'); + + include STAFFINC_DIR . 'templates/set-password.tmpl.php'; + } +} diff --git a/include/class.forms.php b/include/class.forms.php index 3c898f26a61ef5bb02fba8bc96df1e8638b6f109..c0fbd9040f3a296af2d3c0e3341e2e810f887543 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -99,12 +99,7 @@ class Form { function isValid($include=false) { if (!isset($this->_errors)) { $this->_errors = array(); - $this->getClean(); - // Validate the whole form so that errors can be added to the - // individual fields and collected below. - foreach ($this->validators as $V) { - $V($this); - } + $this->validate($this->getClean()); foreach ($this->getFields() as $field) if ($field->errors() && (!$include || $include($field))) $this->_errors[$field->get('id')] = $field->errors(); @@ -112,6 +107,14 @@ class Form { return !$this->_errors; } + function validate($clean_data) { + // Validate the whole form so that errors can be added to the + // individual fields and collected below. + foreach ($this->validators as $V) { + $V($this); + } + } + function getClean() { if (!$this->_clean) { $this->_clean = array(); @@ -428,11 +431,11 @@ class FormField { $vs, array($this, $this->_clean)); } - if ($this->isVisible()) - $this->validateEntry($this->_clean); - if (!isset($this->_clean) && ($d = $this->get('default'))) $this->_clean = $d; + + if ($this->isVisible()) + $this->validateEntry($this->_clean); } return $this->_clean; } @@ -504,7 +507,6 @@ class FormField { * field is visible and should be considered for validation */ function isVisible() { - $config = $this->getConfiguration(); if ($this->get('visibility') instanceof VisibilityConstraint) { return $this->get('visibility')->isVisible($this); } @@ -3038,7 +3040,8 @@ class CheckboxWidget extends Widget { $classes = 'class="'.$config['classes'].'"'; ?> <div <?php echo implode(' ', array_filter(array($classes))); ?>> - <input id="<?php echo $this->id; ?>" style="vertical-align:top;" + <input id="<?php echo $this->id; ?>" style="vertical-align:top; height:100%" + class="pull-left clearfix" type="checkbox" name="<?php echo $this->name; ?>[]" <?php if ($this->value) echo 'checked="checked"'; ?> value="<?php echo $this->field->get('id'); ?>"/> @@ -3054,9 +3057,7 @@ class CheckboxWidget extends Widget { $data = $this->field->getSource(); if (count($data)) { if (!isset($data[$this->name])) - // Indeterminite. Likely false, but consider current field - // value - return null; + return false; return @in_array($this->field->get('id'), $data[$this->name]); } return parent::getValue(); @@ -3613,10 +3614,6 @@ class AssignmentForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { @@ -3708,10 +3705,6 @@ class TransferForm extends Form { return !$this->errors(); } - function getClean() { - return parent::getClean(); - } - function render($options) { switch(strtolower($options['template'])) { diff --git a/include/class.staff.php b/include/class.staff.php index 5d686dc113914a35f02d45de77728a38c4677450..8b3a087ca60a97b930f2b3995716989412716d27 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -1067,4 +1067,62 @@ class StaffDeptAccess extends VerySimpleModel { $this->setFlag(self::FLAG_ALERTS, $value); } } -?> + +/** + * This form is used to administratively change the password. The + * ChangePasswordForm is used for an agent to change their own password. + */ +class PasswordResetForm +extends AbstractForm { + function buildFields() { + return array( + 'email' => new BooleanField(array( + 'default' => true, + 'configuration' => array( + 'desc' => __('Send the agent a password reset email'), + ), + )), + 'passwd1' => new PasswordField(array( + 'placeholder' => __('New Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'passwd2' => new PasswordField(array( + 'placeholder' => __('Confirm Password'), + 'required' => true, + 'configuration' => array( + 'classes' => 'span12', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + 'temporary' => new BooleanField(array( + 'configuration' => array( + 'desc' => __('Require password change at next login'), + 'classes' => 'form footer', + ), + 'visibility' => new VisibilityConstraint( + new Q(array('email' => false)), + VisibilityConstraint::HIDDEN + ), + )), + ); + } + + function validate($clean) { + if ($clean['passwd1'] != $clean['passwd2']) + $this->getField('passwd1')->addError(__('Passwords do not match')); + } + + function render($staff=true) { + return parent::render($staff, false, array('template' => 'dynamic-form-simple.tmpl.php')); + } +} diff --git a/include/staff/staff.inc.php b/include/staff/staff.inc.php index 659ab6873c2ba23f7d68ac8de189a95143ca58d4..533eb4f935102ee2072b1d999a4f7b1f94b75fac 100644 --- a/include/staff/staff.inc.php +++ b/include/staff/staff.inc.php @@ -95,9 +95,12 @@ $info = Format::htmlchars($info); <input type="text" size="40" style="width:300px" class="staff-username typeahead" name="username" value="<?php echo $info['username']; ?>" /> - <button type="button" class="action-button"> +<?php if (!($bk = $staff->getAuthBackend()) || $bk->supportsPasswordChange()) { ?> + <button type="button" class="action-button" onclick="javascript: + $.dialog('ajax.php/staff/'+<?php echo $info['id']; ?>+'/set-password', 201);"> <i class="icon-refresh"></i> <?php echo __('Set Password'); ?> </button> +<?php } ?> <i class="offset help-tip icon-question-sign" href="#username"></i> <div class="error"><?php echo $errors['username']; ?></div> </td> diff --git a/include/staff/templates/set-password.tmpl.php b/include/staff/templates/set-password.tmpl.php new file mode 100644 index 0000000000000000000000000000000000000000..bc3d66ff50fc8e2d870b11e255c2c38a3a2ad287 --- /dev/null +++ b/include/staff/templates/set-password.tmpl.php @@ -0,0 +1,22 @@ +<h3 class="drag-handle"><?php echo $title ?></h3> +<b><a class="close" href="#"><i class="icon-remove-circle"></i></a></b> +<div class="clear"></div> +<hr/> +<form method="post" action="#<?php echo $path; ?>"> + <div class="inset"> + <?php $form->render(); ?> + </div> + <hr> + <p class="full-width"> + <span class="buttons pull-left"> + <input type="reset" value="<?php echo __('Reset'); ?>" /> + <input type="button" name="cancel" class="close" + value="<?php echo __('Cancel'); ?>" /> + </span> + <span class="buttons pull-right"> + <input type="submit" value="<?php + echo $verb ?: __('Update'); ?>" /> + </span> + </p> + <div class="clear"></div> +</form> diff --git a/scp/ajax.php b/scp/ajax.php index 55dee8d6d19035b46fac75e0cd440c1f27a52877..58e153a6a921e8d74fcbc818b6dc25895e192007 100644 --- a/scp/ajax.php +++ b/scp/ajax.php @@ -230,6 +230,9 @@ $dispatcher = patterns('', url('^/department$', 'addDepartment'), url('^/team', 'addTeam') )) + )), + url('^/staff/(?P<id>\d+)', patterns('ajax.staff.php:StaffAjaxAPI', + url('^/set-password$', 'setPassword') )) ); diff --git a/scp/css/scp.css b/scp/css/scp.css index 42afb11c70fda8aa85eda07d8931f75a0f1955d9..65c4b41f4fc467f5c3e782185f8c4bd4794c39ba 100644 --- a/scp/css/scp.css +++ b/scp/css/scp.css @@ -2524,3 +2524,11 @@ form .inset { .form.footer { margin-top: 50px; } +.clearfix:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; +}