Skip to content

Commit

Permalink
Fix User Date of Birth Validation and Processing (#16620)
Browse files Browse the repository at this point in the history
### What does it do?
Replace usage of `strtotime` with `DateTimeImmutable::createFromFormat`
for better format compatibility.

### Why is it needed?
Certain date formats, such as m-d-Y, are incompatible with `strtotime`
and will cause the value of input fields where it's currently used (such
as in a User's Profile) to fail.

### How to test
Set the system setting `manager_date_format` to `m-d-Y` (or any other
format where the month and day would be programmatically ambiguous),
clear caches, and verify that a User's DOB can be added/updated as
expected. Note that there are two distinct places to check this: under
Manage/Users/[User] and via the logged in User's Profile page (at
`/manager/?a=security/profile`)

### Related issue(s)/PR(s)
n/a
  • Loading branch information
smg6511 authored Sep 26, 2024
1 parent 8f4674c commit a8420a5
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 71 deletions.
20 changes: 13 additions & 7 deletions core/src/Revolution/Processors/Security/Profile/Update.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/*
* This file is part of MODX Revolution.
*
Expand All @@ -10,10 +11,9 @@

namespace MODX\Revolution\Processors\Security\Profile;


use MODX\Revolution\Processors\Processor;
use MODX\Revolution\modUser;
use MODX\Revolution\modUserProfile;
use MODX\Revolution\Processors\Processor;

/**
* Update a user profile
Expand Down Expand Up @@ -49,7 +49,6 @@ public function initialize()
if ($this->profile === null) {
return $this->modx->lexicon('user_profile_err_not_found');
}

return true;
}

Expand Down Expand Up @@ -96,12 +95,19 @@ public function prepare()
/* format and set data */
$dob = $this->getProperty('dob');
if (!empty($dob)) {
$properties['dob'] = strtotime($dob);
$date = \DateTimeImmutable::createFromFormat($this->modx->getOption('manager_date_format', null, 'Y-m-d', true), $dob);
if ($date === false) {
$this->addFieldError('dob', $this->modx->lexicon('user_err_not_specified_dob'));
} else {
$properties['dob'] = $date->getTimestamp();
}
}

$this->profile->fromArray($properties);
}

public function validate() {
public function validate()
{
if ($this->getProperty('newpassword') !== 'false') {
$oldPassword = $this->getProperty('password_old');
$newPassword = $this->getProperty('password_new');
Expand All @@ -113,10 +119,10 @@ public function validate() {
}
if (empty($newPassword) || strlen($newPassword) < $this->modx->getOption('password_min_length', null, 8)) {
$this->addFieldError('password_new', $this->modx->lexicon('user_err_password_too_short'));
} else if (!preg_match('/^[^\'\x3c\x3e\(\);\x22\x7b\x7d\x2f\x5c]+$/', $newPassword)) {
} elseif (!preg_match('/^[^\'\x3c\x3e\(\);\x22\x7b\x7d\x2f\x5c]+$/', $newPassword)) {
$this->addFieldError('password_new', $this->modx->lexicon('user_err_password_invalid'));
}
if (empty($confirmPassword) || strcmp($newPassword,$confirmPassword) != 0) {
if (empty($confirmPassword) || strcmp($newPassword, $confirmPassword) != 0) {
$this->addFieldError('password_confirm', $this->modx->lexicon('user_err_password_no_match'));
}
}
Expand Down
139 changes: 75 additions & 64 deletions core/src/Revolution/Processors/Security/User/Validation.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/*
* This file is part of the MODX Revolution package.
*
Expand All @@ -10,18 +11,18 @@

namespace MODX\Revolution\Processors\Security\User;


use MODX\Revolution\Processors\ModelProcessor;
use MODX\Revolution\modUser;
use MODX\Revolution\modUserProfile;
use MODX\Revolution\modX;
use MODX\Revolution\Processors\ModelProcessor;

/**
* Handles common validation for user processors
*
* @package MODX\Revolution\Processors\Security\User
*/
class Validation {
class Validation
{
/** @var modX $modx */
public $modx;
/** @var Create|Update $processor */
Expand All @@ -31,14 +32,16 @@ class Validation {
/** @var modUserProfile $profile */
public $profile;

function __construct(ModelProcessor &$processor,modUser &$user,modUserProfile &$profile) {
public function __construct(ModelProcessor &$processor, modUser &$user, modUserProfile &$profile)
{
$this->processor =& $processor;
$this->modx =& $processor->modx;
$this->user =& $user;
$this->profile =& $profile;
}

public function validate() {
public function validate()
{
$this->checkUsername();
$this->checkPassword();
$this->checkEmail();
Expand All @@ -50,146 +53,154 @@ public function validate() {
return !$this->processor->hasErrors();
}

public function checkUsername() {
public function checkUsername()
{
$username = $this->processor->getProperty('username');
if (empty($username)) {
$this->processor->addFieldError('username',$this->modx->lexicon('user_err_not_specified_username'));
$this->processor->addFieldError('username', $this->modx->lexicon('user_err_not_specified_username'));
} elseif (!preg_match('/^[^\'\\x3c\\x3e\\(\\);\\x22]+$/', $username)) {
$this->processor->addFieldError('username',$this->modx->lexicon('user_err_username_invalid'));
} else if (!empty($username)) {
$this->processor->addFieldError('username', $this->modx->lexicon('user_err_username_invalid'));
} elseif (!empty($username)) {
if ($this->alreadyExists($username)) {
$this->processor->addFieldError('username',$this->modx->lexicon('user_err_already_exists'));
$this->processor->addFieldError('username', $this->modx->lexicon('user_err_already_exists'));
}
$this->user->set('username',$username);
$this->user->set('username', $username);
}
}

public function alreadyExists($name) {
return $this->modx->getCount(modUser::class,
[
public function alreadyExists($name)
{
return $this->modx->getCount(
modUser::class,
[
'username' => $name,
'id:!=' => $this->user->get('id'),
]
) > 0;
) > 0;
}

public function checkPassword() {
$newPassword = $this->processor->getProperty('newpassword',null);
public function checkPassword()
{
$newPassword = $this->processor->getProperty('newpassword', null);
$id = $this->processor->getProperty('id');

$passwordGenerationMethod = $this->processor->getProperty('passwordgenmethod','g');
$passwordGenerationMethod = $this->processor->getProperty('passwordgenmethod', 'g');
if ($passwordGenerationMethod !== 'user_email_specify' && ($newPassword !== null && $newPassword != 'false' || empty($id))) {
$passwordNotifyMethod = $this->processor->getProperty('passwordnotifymethod',null);
$passwordNotifyMethod = $this->processor->getProperty('passwordnotifymethod', null);
if (empty($passwordNotifyMethod)) {
$this->processor->addFieldError('password_notify_method',$this->modx->lexicon('user_err_not_specified_notification_method'));
$this->processor->addFieldError('password_notify_method', $this->modx->lexicon('user_err_not_specified_notification_method'));
}

if ($passwordGenerationMethod == 'g') {
$autoPassword = $this->user->generatePassword();
$this->user->set('password', $autoPassword);
$this->processor->newPassword= $autoPassword;
$this->processor->newPassword = $autoPassword;
} else {
$specifiedPassword = $this->processor->getProperty('specifiedpassword');
$confirmPassword = $this->processor->getProperty('confirmpassword');
if (empty($specifiedPassword)) {
$this->processor->addFieldError('specifiedpassword',$this->modx->lexicon('user_err_not_specified_password'));
$this->processor->addFieldError('specifiedpassword', $this->modx->lexicon('user_err_not_specified_password'));
} elseif ($specifiedPassword != $confirmPassword) {
$this->processor->addFieldError('confirmpassword',$this->modx->lexicon('user_err_password_no_match'));
$this->processor->addFieldError('confirmpassword', $this->modx->lexicon('user_err_password_no_match'));
} elseif (strlen($specifiedPassword) < $this->modx->getOption('password_min_length', null, 8, true)) {
$this->processor->addFieldError('specifiedpassword',$this->modx->lexicon('user_err_password_too_short'));
$this->processor->addFieldError('specifiedpassword', $this->modx->lexicon('user_err_password_too_short'));
} elseif (!preg_match('/^[^\'\x3c\x3e\(\);\x22\x7b\x7d\x2f\x5c]+$/', $specifiedPassword)) {
$this->processor->addFieldError('specifiedpassword', $this->modx->lexicon('user_err_password_invalid'));
} else {
$this->user->set('password',$specifiedPassword);
$this->user->set('password', $specifiedPassword);
$this->processor->newPassword = $specifiedPassword;
}
}
}
return $this->processor->newPassword;
}

public function checkEmail() {
public function checkEmail()
{
$email = $this->processor->getProperty('email');
if (empty($email)) {
$this->processor->addFieldError('email',$this->modx->lexicon('user_err_not_specified_email'));
$this->processor->addFieldError('email', $this->modx->lexicon('user_err_not_specified_email'));
}

if (!$this->modx->getOption('allow_multiple_emails',null,true)) {
if (!$this->modx->getOption('allow_multiple_emails', null, true)) {
/** @var modUserProfile $emailExists */
$emailExists = $this->modx->getObject(modUserProfile::class, ['email' => $email]);
if ($emailExists) {
if ($emailExists->get('internalKey') != $this->processor->getProperty('id')) {
$this->processor->addFieldError('email',$this->modx->lexicon('user_err_already_exists_email'));
$this->processor->addFieldError('email', $this->modx->lexicon('user_err_already_exists_email'));
}
}
}
return $email;
}

public function checkPhone() {
public function checkPhone()
{
$phone = $this->processor->getProperty('phone');
if (!empty($phone)) {
if ($this->modx->getOption('clean_phone_number',null,false)) {
$phone = str_replace(' ','',$phone);
$phone = str_replace('-','',$phone);
$phone = str_replace('(','',$phone);
$phone = str_replace(')','',$phone);
$phone = str_replace('+','',$phone);
$this->processor->setProperty('phone',$phone);
$this->profile->set('phone',$phone);
if ($this->modx->getOption('clean_phone_number', null, false)) {
$phone = str_replace(' ', '', $phone);
$phone = str_replace('-', '', $phone);
$phone = str_replace('(', '', $phone);
$phone = str_replace(')', '', $phone);
$phone = str_replace('+', '', $phone);
$this->processor->setProperty('phone', $phone);
$this->profile->set('phone', $phone);
}
}
}

public function checkCellPhone() {
public function checkCellPhone()
{
$phone = $this->processor->getProperty('mobilephone');
if (!empty($phone)) {
if ($this->modx->getOption('clean_phone_number',null,false)) {
$phone = str_replace(' ','',$phone);
$phone = str_replace('-','',$phone);
$phone = str_replace('(','',$phone);
$phone = str_replace(')','',$phone);
$phone = str_replace('+','',$phone);
$this->processor->setProperty('mobilephone',$phone);
$this->profile->set('mobilephone',$phone);
if ($this->modx->getOption('clean_phone_number', null, false)) {
$phone = str_replace(' ', '', $phone);
$phone = str_replace('-', '', $phone);
$phone = str_replace('(', '', $phone);
$phone = str_replace(')', '', $phone);
$phone = str_replace('+', '', $phone);
$this->processor->setProperty('mobilephone', $phone);
$this->profile->set('mobilephone', $phone);
}
}
}

public function checkBirthDate() {
public function checkBirthDate()
{
$birthDate = $this->processor->getProperty('dob');
if (!empty($birthDate)) {
$birthDate = strtotime($birthDate);
if (false === $birthDate) {
$this->processor->addFieldError('dob',$this->modx->lexicon('user_err_not_specified_dob'));
$date = \DateTimeImmutable::createFromFormat($this->modx->getOption('manager_date_format', null, 'Y-m-d', true), $birthDate);
if ($date === false) {
$this->processor->addFieldError('dob', $this->modx->lexicon('user_err_not_specified_dob'));
}
$this->processor->setProperty('dob',$birthDate);
$this->profile->set('dob',$birthDate);
$birthDate = $date->getTimestamp();
$this->processor->setProperty('dob', $birthDate);
$this->profile->set('dob', $birthDate);
}
}

public function checkBlocked() {
public function checkBlocked()
{
/* blocked until */
$blockedUntil = $this->processor->getProperty('blockeduntil');
if (!empty($blockedUntil)) {
$blockedUntil = str_replace('-','/',$blockedUntil);
$blockedUntil = str_replace('-', '/', $blockedUntil);
if (!$blockedUntil = strtotime($blockedUntil)) {
$this->processor->addFieldError('blockeduntil',$this->modx->lexicon('user_err_not_specified_blockeduntil'));
$this->processor->addFieldError('blockeduntil', $this->modx->lexicon('user_err_not_specified_blockeduntil'));
}
$this->processor->setProperty('blockeduntil',$blockedUntil);
$this->profile->set('blockeduntil',$blockedUntil);
$this->processor->setProperty('blockeduntil', $blockedUntil);
$this->profile->set('blockeduntil', $blockedUntil);
}

/* blocked after */
$blockedAfter = $this->processor->getProperty('blockedafter');
if (!empty($blockedAfter)) {
$blockedAfter = str_replace('-','/',$blockedAfter);
$blockedAfter = str_replace('-', '/', $blockedAfter);
if (!$blockedAfter = strtotime($blockedAfter)) {
$this->processor->addFieldError('blockedafter',$this->modx->lexicon('user_err_not_specified_blockedafter'));
$this->processor->addFieldError('blockedafter', $this->modx->lexicon('user_err_not_specified_blockedafter'));
}
$this->processor->setProperty('blockedafter',$blockedAfter);
$this->profile->set('blockedafter',$blockedAfter);
$this->processor->setProperty('blockedafter', $blockedAfter);
$this->profile->set('blockedafter', $blockedAfter);
}
}

}

0 comments on commit a8420a5

Please sign in to comment.