Open ArchBlood opened 6 months ago
Someone enters their birth year as 2015 then their age is 8 and then they're marked as underage, which then performs the logout action.
When said user tries logging back in, they're force logged out again.
What I've currently not found a way around for is if the birthday field isn't set to required
and at registration
, also currently when creating an account the logout action does not trigger if both of these have a checkmark during finalization of the registration and instead allows for the login action to continue, so this will need fixed before it can be merged.
Hmm, could maybe the
legal
module inject anAgeValidator
for the Birthday field in the profile and make sure that no too young users are created?Wouldn't that also work if the birthday field is activated in ShowOnRegistration?
I could look into this, although I'm not much of a fan of injections, because if another module were to also be created that injects into the field then there would be a conflict between both injections, so if we were to do this then we'd have to tread carefully doing so. :thinking:
Thinking about it, it would be much easier to implement if there were a core FieldValidator
class that we'd be able to extend to do this.
Here's an example for the requested AgeValidator
class;
<?php
namespace humhub\modules\legal\validators;
use DateTime;
use Yii;
use yii\base\Model;
use yii\validators\Validator;
/**
* AgeValidator validates that the given value represents an age greater than or equal to a specified minimum age.
*/
class AgeValidator extends Validator
{
/**
* Initializes the validator.
*/
public function init()
{
parent::init();
if ($this->message === null) {
$this->message = Yii::t('LegalModule.base', 'You must be at least {age} years old.', ['age' => Yii::$app->getModule('legal')->getMinimumAge()]);
}
}
/**
* Validates the age of the user based on the given attribute value.
*
* @param \yii\base\Model $model the data model being validated
* @param string $attribute the name of the attribute to be validated
*/
public function validateAttribute($model, $attribute)
{
// Ensure we are validating the birthday attribute during user registration
if ($model instanceof \humhub\modules\user\models\forms\Registration) {
$birthday = $this->user->profile->birthday;
if ($birthday !== null && $birthday != '' && $birthday->format('Y') != '0000') {
$minimumAge = Yii::$app->getModule('legal')->getMinimumAge();
$minimumAgeDate = new DateTime("-{$minimumAge} years");
$today = new DateTime();
$age = $today->diff($birthday)->y;
if ($age < $minimumAge) {
$this->addError($model, $attribute, $this->message, ['age' => $minimumAge]);
}
}
}
}
/**
* Attaches the validator to the Birthday field of the model.
*
* @param \yii\base\Model $model the model to attach the validator to
*/
public static function attachToBirthday($model)
{
$validator = new self();
$validator->validateAttribute($model, 'birthday');
}
}
Please make any necessary modifications if needed.
Couldn't we use the BEFORE_VALIDATE event of the Profile model and do this additional validation?
Couldn't we use the BEFORE_VALIDATE event of the Profile model and do this additional validation?
Could you provide some documentation for this?
Here is an example: https://docs.humhub.org/docs/develop/modules-event-handler/#model-validation
Here is an example: https://docs.humhub.org/docs/develop/modules-event-handler/#model-validation
So knowing this, maybe something along the lines of this?
/**
* Performs age validation before the registration form is validated.
*
* @param \yii\base\Event $event the event parameter
* @throws \Exception if an error occurs while performing age validation
*/
public static function onBeforeValidate($event)
{
$registrationForm = $event->sender;
$profile = $registrationForm->models['Profile'];
// Check if $profile is loaded and the birthday attribute is set
if ($profile !== null && isset($profile->birthday)) {
// Option 1: Access birthday directly from profile
$birthday = $profile->birthday;
} else {
// Option 2: Access birthday through user->profile
$birthday = Yii::$app->user->profile->birthday;
}
// Perform age validation using $birthday
$minimumAge = Yii::$app->getModule('legal')->getMinimumAge();
// Check if birthday is set and has a year
if ($birthday !== null && $birthday != '' && $birthday->format('Y') != '0000') {
$minimumAgeDate = new DateTime("-{$minimumAge} years");
$today = new DateTime();
$age = $today->diff($birthday)->y;
if ($age < $minimumAge) {
// Add an error if age is less than the minimum age requirement
$registrationForm->addError('profile', Yii::t('LegalModule.base', 'You must be at least {age} years old.', ['age' => $minimumAge]));
}
}
}
<?php /** @noinspection MissedFieldInspection */
use humhub\components\Controller;
use humhub\modules\content\widgets\richtext\ProsemirrorRichText;
use humhub\modules\user\models\forms\Registration;
use humhub\widgets\FooterMenu;
use humhub\widgets\LayoutAddons;
use humhub\modules\user\models\Profile;
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
return [
'id' => 'legal',
'class' => 'humhub\modules\legal\Module',
'namespace' => 'humhub\modules\legal',
'events' => [
['class' => FooterMenu::class, 'event' => FooterMenu::EVENT_INIT, 'callback' => ['humhub\modules\legal\Events', 'onFooterMenuInit']],
['class' => LayoutAddons::class, 'event' => LayoutAddons::EVENT_INIT, 'callback' => ['humhub\modules\legal\Events', 'onLayoutAddonInit']],
['class' => Registration::class, 'event' => Registration::EVENT_BEFORE_RENDER, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationFormRender']],
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_INIT, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationFormInit']],
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_REGISTRATION, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationAfterRegistration']],
['class' => Controller::class, 'event' => Controller::EVENT_BEFORE_ACTION, 'callback' => ['humhub\modules\legal\Events', 'onBeforeControllerAction']],
['class' => ProsemirrorRichText::class, 'event' => ProsemirrorRichText::EVENT_AFTER_RUN, 'callback' => ['humhub\modules\legal\Events', 'onAfterRunRichText']],
['class' => Profile::class, 'event' => Profile::EVENT_BEFORE_VALIDATE, 'callback' => ['humhub\modules\legal\Events', 'onBeforeValidate']],
]
];
Haven't had time for a review yet. But it looks good.
It might be better to put the validation in a separate validator class + an option to activate this validator.
Haven't had time for a review yet. But it looks good.
It might be better to put the validation in a separate validator class + an option to activate this validator.
Just to confirm, you want the EVENT_BEFORE_VALIDTION
and a validator class;
It should use the validator class for the main parts of the code and be called in onBeforeValidate()
instead of using the check within it? I think it would be a good idea and make it more maintainable this way, and I wouldn't have to change much of the provided code, so in theory I can use both https://github.com/humhub-contrib/legal/pull/67#issuecomment-1961141972 & https://github.com/humhub-contrib/legal/pull/67#issuecomment-1961249324 with minor refactoring.
Would you also want an option to enable/disable the validator, similar to the age requirement option from the configuration settings?
I'm not sure if I'm a fan of this method, but it should be better than just logging out the user;
AgeValidator.php
ExampleHere we validate the user's age, if they don't meet the age requirement then the account is disabled instead of doing a forced logout action.
<?php
namespace humhub\modules\legal\validators;
use DateTime;
use Yii;
use yii\validators\Validator;
/**
* AgeValidator validates that the given value represents an age greater than or equal to a specified minimum age.
*/
class AgeValidator extends Validator
{
/**
* Validates the age of the user based on the given attribute value.
*
* @param \yii\base\Model $model the data model being validated
* @param string $attribute the name of the attribute to be validated
*/
public function validateAttribute($model, $attribute)
{
$value = $model->$attribute;
// Ensure the value represents a valid date
if ($value instanceof DateTime) {
// Get minimum age from the module
$minimumAge = Yii::$app->getModule('legal')->getMinimumAge();
// Calculate the age
$today = new DateTime();
$age = $today->diff($value)->y;
// Check if the age meets the minimum requirement
if ($age < $minimumAge) {
// Set error message
$message = Yii::t('LegalModule.base', 'You must be at least {age} years old.', ['age' => $minimumAge]);
// Add error to the model attribute
$model->addError($attribute, $message);
// Check if the user account should be disabled
if ($minimumAge > 0) {
$user = $model->user;
$user->status = User::STATUS_DISABLED;
$user->save();
}
}
}
}
}
Events.php
ExampleHere we make the appropriate calls to the validator within onBeforeValidate()
public static function onBeforeValidate($event)
{
// Get the registration form
$registrationForm = $event->sender;
// Check for minimum
$minimumAge = Yii::$app->getModule('legal')->getMinimumAge();
if ($minimumAge > 0) {
// Validate the user's age
$ageValidator = new AgeValidator();
$ageValidator->validateAttribute($registrationForm, 'birthday');
}
}
config.php
ExampleNothing changed from https://github.com/humhub-contrib/legal/pull/67#issuecomment-1961249324.
<?php /** @noinspection MissedFieldInspection */
use humhub\components\Controller;
use humhub\modules\content\widgets\richtext\ProsemirrorRichText;
use humhub\modules\user\models\forms\Registration;
use humhub\widgets\FooterMenu;
use humhub\widgets\LayoutAddons;
use humhub\modules\user\models\Profile;
/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2018 HumHub GmbH & Co. KG
* @license https://www.humhub.com/licences
*/
return [
'id' => 'legal',
'class' => 'humhub\modules\legal\Module',
'namespace' => 'humhub\modules\legal',
'events' => [
['class' => FooterMenu::class, 'event' => FooterMenu::EVENT_INIT, 'callback' => ['humhub\modules\legal\Events', 'onFooterMenuInit']],
['class' => LayoutAddons::class, 'event' => LayoutAddons::EVENT_INIT, 'callback' => ['humhub\modules\legal\Events', 'onLayoutAddonInit']],
['class' => Registration::class, 'event' => Registration::EVENT_BEFORE_RENDER, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationFormRender']],
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_INIT, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationFormInit']],
['class' => Registration::class, 'event' => Registration::EVENT_AFTER_REGISTRATION, 'callback' => ['humhub\modules\legal\Events', 'onRegistrationAfterRegistration']],
['class' => Controller::class, 'event' => Controller::EVENT_BEFORE_ACTION, 'callback' => ['humhub\modules\legal\Events', 'onBeforeControllerAction']],
['class' => ProsemirrorRichText::class, 'event' => ProsemirrorRichText::EVENT_AFTER_RUN, 'callback' => ['humhub\modules\legal\Events', 'onAfterRunRichText']],
['class' => Profile::class, 'event' => Profile::EVENT_BEFORE_VALIDATE, 'callback' => ['humhub\modules\legal\Events', 'onBeforeValidate']],
]
];
I like this approach.
Question would be, do we need an option to enable this behavior? And maybe unit tests for the AgeValidator would be good.
I like this approach.
Question would be, do we need an option to enable this behavior? And maybe unit tests for the AgeValidator would be good.
Well, to answer the question about an option for enabling the validator, I don't think we'd need to due to the minimum age already having this. As for the unit tests, I don't have much experience with this so I believe this would have to be someone other than me doing it. 🤔
@Semir1212 What do you think regarding the field hint?
@luke- "Show checkbox" is not clear enough. Hint is fine.
Why are you closing the PR. Shouldn't we enforce a minimum age in the profile field?
Why are you closing the PR. Shouldn't we enforce a minimum age in the profile field?
For some reason when renaming a branch it closes and deletes the original branch.
@luke- can a branch be made for this P/R?
Here's what this P/R provides;
Null Check: Before performing any operations, we ensure that the
$user
object and its profile property are notnull
. This prevents errors when trying to access properties of null objects, which could occur if the user is not logged in or if their profile information is missing.Age Calculation: If the user object and its profile are not
null
, we proceed with calculating the user's age. We retrieve the user's birthdate from their profile and calculate their age based on the current year and their birth year.Minimum Age Comparison: We compare the user's age with the minimum age requirement specified by the
$module->getMinimumAge()
method. If the user's age is less than the minimum age requirement, this indicates that they are underage.Logout: If the user's age is less than the minimum age requirement, we log the user out. This action ensures that underage users are not allowed to continue using the system. Logging the user out prevents them from accessing restricted content or performing actions that require them to be of legal age.
Age Confirmation Check: If the user's age meets the minimum age requirement, we proceed with the regular age confirmation check. This involves checking whether the user has accepted the age confirmation, which is typically done through the checkbox mechanism during registration or account setup.
Return Value: The method returns
true
if the user's age meets the minimum age requirement and they have not yet confirmed their age. This indicates that the age confirmation step should be displayed to the user. If the user's age is below the minimum age requirement, the method logs them out and returnsfalse
, preventing further access to the system.In summary, this updated
showAgeCheck()
method ensures that underage users are logged out automatically and prevents them from accessing restricted content or performing actions that require them to be of legal age.