diff --git a/assets/default/css/theme.css b/assets/default/css/theme.css index e7056b813e47b2d0e95548fd77f1e35ac4b352cc..bc9647ede18c9b98973c77c64c22679a82fc97a0 100644 --- a/assets/default/css/theme.css +++ b/assets/default/css/theme.css @@ -870,3 +870,8 @@ a.refresh { color: #777; text-decoration: none; } +table.padded tr > td, +table.padded tr > th { + height: 20px; + padding-bottom: 5px; +} diff --git a/include/class.client.php b/include/class.client.php index 5af39ac61b5f4a2c9b6ecead082fb31ee7f469bd..2fd702d56cab98c8e9d8bfe1bb55b2b856a73114 100644 --- a/include/class.client.php +++ b/include/class.client.php @@ -163,6 +163,7 @@ class TicketOwner extends TicketUser { class EndUser extends AuthenticatedUser { protected $user; + protected $_account = false; function __construct($user) { $this->user = $user; @@ -228,11 +229,12 @@ class EndUser extends AuthenticatedUser { return ($stats=$this->getTicketStats())?$stats['closed']:0; } - function hasAccount() { - } - function getAccount() { - return ClientAccount::lookup(array('user_id'=>$this->getId())); + if ($this->_account === false) + $this->_account = + ClientAccount::lookup(array('user_id'=>$this->getId())); + + return $this->_account; } private function getStats() { @@ -269,10 +271,16 @@ class ClientAccountModel extends VerySimpleModel { class ClientAccount extends ClientAccountModel { var $_options = null; + var $timezone; const LOCKED = 0x0001; const PASSWD_RESET_REQUIRED = 0x0002; + function __onload() { + if ($this->get('timezone_id')) + $this->timezone = Timezone::getOffsetById($this->ht['timezone_id']); + } + function checkPassword($password, $autoupdate=true) { /*bcrypt based password match*/ @@ -293,10 +301,75 @@ class ClientAccount extends ClientAccountModel { return true; } + function hasCurrentPassword($password) { + return $this->checkPassword($password, false); + } + function forcePasswdReset() { $this->set('status', $this->get('status') | self::PASSWD_RESET_REQUIRED); $this->save(); } + + function cancelResetTokens() { + // TODO: Drop password-reset tokens from the config table for + // this user id + $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset" + AND `value`='.db_input($this->getId()); + db_query($sql, false); + unset($_SESSION['_client']['reset-token']); + } + + function getInfo() { + $base = $this->ht; + $base['tz_offset'] = $this->timezone; + return $base; + } + + function update($vars, &$errors) { + if($vars['passwd1'] || $vars['passwd2'] || $vars['cpasswd']) { + + if(!$vars['passwd1']) + $errors['passwd1']='New password required'; + elseif($vars['passwd1'] && strlen($vars['passwd1'])<6) + $errors['passwd1']='Must be at least 6 characters'; + elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) + $errors['passwd2']='Password(s) do not match'; + + if (($rtoken = $_SESSION['_client']['reset-token'])) { + $_config = new Config('pwreset'); + if ($_config->get($rtoken) != $this->getId()) + $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['cpasswd']) + $errors['cpasswd']='Current password 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!'; + } + + if(!$vars['timezone_id']) + $errors['timezone_id']='Time zone required'; + + if($errors) return false; + + $this->set('timezone_id', $vars['timezone_id']); + $this->set('dst',isset($vars['dst'])?1:0); + + if ($vars['passwd1']) { + $this->set('passwd', Passwd::hash($vars['passwd1'])); + $info = array('password' => $vars['passwd1']); + Signal::send('auth.pwchange', $this, $info); + $this->cancelResetTokens(); + } + + return $this->save(); + } } ?> diff --git a/include/class.orm.php b/include/class.orm.php index 951a4ffd425e88109f35c103aeed6e425bd2fb52..289ddaef7d8003640e7f24a35d459781bb72c2e4 100644 --- a/include/class.orm.php +++ b/include/class.orm.php @@ -97,6 +97,8 @@ class VerySimpleModel { } } + function __onload() {} + static function _inspect() { if (!static::$meta['table']) throw new OrmConfigurationError( @@ -366,7 +368,9 @@ class ModelInstanceIterator implements Iterator, ArrayAccess { function buildModel($row) { // TODO: Traverse to foreign keys - return new $this->model($row); # nolint + $model = new $this->model($row); # nolint + $model->__onload(); + return $model; } function fillTo($index) { diff --git a/include/client/profile.inc.php b/include/client/profile.inc.php index a103ba1f7ec5f00ec1ec182b520ccfb2eaa8077b..d5651031cdaac3a0a5975edd862384a18d69bfe6 100644 --- a/include/client/profile.inc.php +++ b/include/client/profile.inc.php @@ -8,12 +8,83 @@ account </p> <form action="profile.php" method="post"> <?php csrf_token(); ?> -<table width="800"> +<table width="800" class="padded"> <?php foreach ($user->getForms() as $f) { $f->render(false); } +if ($acct = $thisclient->getAccount()) { + $info=$acct->getInfo(); + $info=Format::htmlchars(($errors && $_POST)?$_POST:$info); ?> +<tr> + <td colspan="2"> + <div><hr><h3>Preferences</h3> + </div> + </td> +</tr> + <td>Time Zone:</td> + <td> + <select name="timezone_id" id="timezone_id"> + <option value="0">— Select Time Zone —</option> + <?php + $sql='SELECT id, offset,timezone FROM '.TIMEZONE_TABLE.' ORDER BY id'; + if(($res=db_query($sql)) && db_num_rows($res)){ + while(list($id,$offset, $tz)=db_fetch_row($res)){ + $sel=($info['timezone_id']==$id)?'selected="selected"':''; + echo sprintf('<option value="%d" %s>GMT %s - %s</option>',$id,$sel,$offset,$tz); + } + } + ?> + </select> + <span class="error"><?php echo $errors['timezone_id']; ?></span> + </td> +</tr> +<tr> + <td width="180"> + Daylight Saving: + </td> + <td> + <input type="checkbox" name="dst" value="1" <?php echo $info['dst']?'checked="checked"':''; ?>> + Observe daylight saving + <em>(Current Time: <strong><?php echo Format::date($cfg->getDateTimeFormat(),Misc::gmtime(),$info['tz_offset'],$info['dst']); ?></strong>)</em> + </td> +</tr> +<tr> + <td colspan=2"> + <div><hr><h3>Access Credentials</h3></div> + </td> +</tr> +<?php if (!isset($_SESSION['_client']['reset-token'])) { ?> +<tr> + <td width="180"> + Current Password: + </td> + <td> + <input type="password" size="18" name="cpasswd" value="<?php echo $info['cpasswd']; ?>"> + <span class="error"> <?php echo $errors['cpasswd']; ?></span> + </td> +</tr> +<?php } ?> +<tr> + <td width="180"> + New Password: + </td> + <td> + <input type="password" size="18" name="passwd1" value="<?php echo $info['passwd1']; ?>"> + <span class="error"> <?php echo $errors['passwd1']; ?></span> + </td> +</tr> +<tr> + <td width="180"> + Confirm New Password: + </td> + <td> + <input type="password" size="18" name="passwd2" value="<?php echo $info['passwd2']; ?>"> + <span class="error"> <?php echo $errors['passwd2']; ?></span> + </td> +</tr> +<?php } ?> </table> <hr> <p style="text-align: center;"> diff --git a/profile.php b/profile.php index e17bf690eb9241ff2b7bbad74efe8684bed447d2..47c100aef5fe46f44c1072e0248bc47058f9f047 100644 --- a/profile.php +++ b/profile.php @@ -23,7 +23,10 @@ $user = User::lookup($thisclient->getId()); if ($user && $_POST) { $errors = array(); - if ($user->updateInfo($_POST, $errors)) + if ($acct = $thisclient->getAccount()) { + $acct->update($_POST, $errors); + } + if (!$errors && $user->updateInfo($_POST, $errors)) Http::redirect('tickets.php'); }