Extending registration form


#1

hi there, i’m trying to add two or three input fields to the registration form, what i’m trying to do is ask the user for two numbers, which would be saved in a database table that is not related to the users database table, each row on the table would have a “nr1” and a “nr2” where “nr1” is repeatable, but “nr2” always has to be unique, as a form of validation, i wanted to make the controller check if the “nr1” submited by the user has a entry in the table that also contains the unique “nr2” submitted by the user, so it looks for all entries with that “nr1” and then sees if there is one that corresponds with the “nr2”.
i tried backtracing the register function in Account/src/controller/accountcontroller , and found that it checks if the username is already used by a staticMethod with findUnique, tried it but couldnt seem to apply it for my purpose, it always validated even when it was supposed to output an error, but still as extra measure i thought about the second number, as this reduces chances of users typing a incorrect number that ,may also be present in the table, how would i go about adding this extra validation?


#2

I would just use two simple where clauses, which by default are ANDed together. Validation should fail when only when there is already a record in the database with the same values for nr1 AND nr2, right? Are you allowed to have the same value for nr2 if the values for nr1 are different?

If so, this is a simple AND:

if (Widget::where('nr1', $data['nr1'])->where('nr2', $data['nr2'])->first()) {
    $ms->addMessageTranslated('danger', 'There is already a widget with nr1={{nr1}} and nr2={{nr2}}.', $data);
    return $response->withStatus(400);
}

#3

i tried before, to replace the find unique with a where clause, but got the same result, just tried the code you suggested, and it also did the same, it just doesnt throw the error, no mather what, just to be clear, i have created a new AccountController in my sprinkle, with just the register funcion in it, to override just that function with the new version of it, in that register function, rigth after the standard check if username and email are already used, is where i add the if statement, i’ve also added the new /account/register route in my sprinkle’s route file


#4

just tested it changing nr1 and nr2 with fields in the users table, just to check if maybe it was my data model the culprit, but it gave the same results, while the other (standard) fields validate just fine.
i was wandering, when adding new fields to the form, is it necessary to adjust something as to the parsing of the data, maybe the data transformer or something like that? because i have correctly added the fields in register.html.twig in my own sprinkle (which i believe should override the register.html.twig in the account sprinkle), also i aded the desired validation rules in my own register.yaml (which i also believe overrides the original one), and that all works, so it validates if the user input is of the desired requirements, and outputs a error message if it isn’t, the only thing that refuses to work is the validation comparing those two fields to the database, i have two hypothesis, in my mind, with how i seem to understand it works, either i have to add the two fields somewhere in the parsing and/or datatransformer, or it has to do with the fact that the database fields for these numbers are actually set as integers and the parsed/transformed data being presented in maybe varchar or something, thus it wont ever find the number in the database table…
i hope i’m making enough sense :joy: i’m not very experienced but getting a hold of things


#5

At this point I’m having trouble reading/understanding your question - maybe join us in chat instead?


#6

i need add two fields to the registration form, “nr1” and “nr2”, the first number “nr1” if a field in the database that doesnt have to be unique, the second number “nr2” however is a field that has to be unique.
to try and achieve this i created a new AccountController class in my own sprinkle, with its own registrate function in it, to override the original one and add my functionality, i also created a new registrate.yaml for validation and routed everything to the new files, so far so good, the registration form works, i made the templates and everything fine, i already have my own datamodel for the table in which the numbers are stored in the database, so at first i tried verifying them just like in the original AccountController the verification is done to check if usernames are already in use or not, this did not work, it dint output any errors, even if i submitted numbers copied out of the databases “nr1” and “nr2” fields, i looked arround the controller, and my templates, and nothing, couldnt find where it was malfunctioning, then i remembered having trouble in the past when i tried to compare a value to something in the database and it failed to do so because the value was in varchar format and the database contained only integers, “nr1” and “nr2” are specified as integers in the database, so i thought maybe the data is being stored as a varchar value before being compared to anything in the database, could this be? is there a way for me to se how the data and what data is being compared to the database?

this is my code in the controller:

    public function register(Request $request, Response $response, $args
        )
    {
        /** @var \UserFrosting\Sprinkle\Core\Alert\AlertStream $ms */
        $ms = $this->ci->alerts;

        /** @var \UserFrosting\Sprinkle\Core\Util\ClassMapper $classMapper */
        $classMapper = $this->ci->classMapper;

        /** @var \UserFrosting\Support\Repository\Repository $config */
        $config = $this->ci->config;

        // Get POST parameters: user_name, first_name, last_name, email, password, passwordc, captcha, spiderbro, csrf_token
        $params = $request->getParsedBody();

        // Check the honeypot. 'spiderbro' is not a real field, it is hidden on the main page and must be submitted with its default value for this to be processed.
        if (!isset($params['spiderbro']) || $params['spiderbro'] != 'http://') {
            throw new SpammyRequestException('Possible spam received:' . print_r($params, true));
        }

        // Security measure: do not allow registering new users until the master account has been created.
        if (!$classMapper->staticMethod('user', 'find', $config['reserved_user_ids.master'])) {
            $ms->addMessageTranslated('danger', 'ACCOUNT.MASTER_NOT_EXISTS');
            return $response->withStatus(403);
        }

        // Check if registration is currently enabled
        if (!$config['site.registration.enabled']) {
            $ms->addMessageTranslated('danger', 'REGISTRATION.DISABLED');
            return $response->withStatus(403);
        }

        /** @var \UserFrosting\Sprinkle\Account\Authenticate\Authenticator $authenticator */
        $authenticator = $this->ci->authenticator;

        // Prevent the user from registering if he/she is already logged in
        if ($authenticator->check()) {
            $ms->addMessageTranslated('danger', 'REGISTRATION.LOGOUT');
            return $response->withStatus(403);
        }

        // Load the request schema
        $schema = new RequestSchema('schema://requests/register.yaml');

        // Whitelist and set parameter defaults
        $transformer = new RequestDataTransformer($schema);
        $data = $transformer->transform($params);

        $error = false;

        // Validate request data
        $validator = new ServerSideValidator($schema, $this->ci->translator);
        if (!$validator->validate($data)) {
            $ms->addValidationErrors($validator);
            $error = true;
        }

        /** @var \UserFrosting\Sprinkle\Core\Throttle\Throttler $throttler */
        $throttler = $this->ci->throttler;
        $delay = $throttler->getDelay('registration_attempt');

        // Throttle requests
        if ($delay > 0) {
            return $response->withStatus(429);
        }

        // Check if username or email already exists
        if ($classMapper->staticMethod('user', 'findUnique', $data['user_name'], 'user_name')) {
            $ms->addMessageTranslated('danger', 'USERNAME.IN_USE', $data);
            $error = true;
        }

        if ($classMapper->staticMethod('user', 'findUnique', $data['email'], 'email')) {
            $ms->addMessageTranslated('danger', 'EMAIL.IN_USE', $data);
            $error = true;
        }

//from here…

if ($classMapper->staticMethod('myMethod', 'findUnique', $data['nr1'], 'nr1')) {
            $ms->addMessageTranslated('danger', 'NUMBER.IN_USE', $data);
            $error = true;
        }

//to here is my addition

        // Check captcha, if required
        if ($config['site.registration.captcha']) {
            $captcha = new Captcha($this->ci->session, $this->ci->config['session.keys.captcha']);
            if (!$data['captcha'] || !$captcha->verifyCode($data['captcha'])) {
                $ms->addMessageTranslated('danger', 'CAPTCHA.FAIL');
                $error = true;
            }
        }

        if ($error) {
            return $response->withStatus(400);
        }

        // Remove captcha, password confirmation from object data after validation
        unset($data['captcha']);
        unset($data['passwordc']);

        if ($config['site.registration.require_email_verification']) {
            $data['flag_verified'] = false;
        } else {
            $data['flag_verified'] = true;
        }

        // Load default group
        $groupSlug = $config['site.registration.user_defaults.group'];
        $defaultGroup = $classMapper->staticMethod('group', 'where', 'slug', $groupSlug)->first();

        if (!$defaultGroup) {
            $e = new HttpException("Account registration is not working because the default group '$groupSlug' does not exist.");
            $e->addUserMessage('REGISTRATION.BROKEN');
            throw $e;
        }

        // Set default group
        $data['group_id'] = $defaultGroup->id;

        // Set default locale
        $data['locale'] = $config['site.registration.user_defaults.locale'];

        // Hash password
        $data['password'] = Password::hash($data['password']);

        // All checks passed!  log events/activities, create user, and send verification email (if required)
        // Begin transaction - DB will be rolled back if an exception occurs
        Capsule::transaction( function() use ($classMapper, $data, $ms, $config, $throttler) {
            // Log throttleable event
            $throttler->logEvent('registration_attempt');

            // Create the user
            $user = $classMapper->createInstance('user', $data);

            // Store new user to database
            $user->save();

            // Create activity record
            $this->ci->userActivityLogger->info("User {$user->user_name} registered for a new account.", [
                'type' => 'sign_up',
                'user_id' => $user->id
            ]);

            // Load default roles
            $defaultRoleSlugs = $classMapper->staticMethod('role', 'getDefaultSlugs');
            $defaultRoles = $classMapper->staticMethod('role', 'whereIn', 'slug', $defaultRoleSlugs)->get();
            $defaultRoleIds = $defaultRoles->pluck('id')->all();

            // Attach default roles
            $user->roles()->attach($defaultRoleIds);

            // Verification email
            if ($config['site.registration.require_email_verification']) {
                // Try to generate a new verification request
                $verification = $this->ci->repoVerification->create($user, $config['verification.timeout']);

                // Create and send verification email
                $message = new TwigMailMessage($this->ci->view, 'mail/verify-account.html.twig');

                $message->from($config['address_book.admin'])
                        ->addEmailRecipient(new EmailRecipient($user->email, $user->full_name))
                        ->addParams([
                            'user' => $user,
                            'token' => $verification->getToken()
                        ]);

                $this->ci->mailer->send($message);

                $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE2', $user->toArray());
            } else {
                // No verification required
                $ms->addMessageTranslated('success', 'REGISTRATION.COMPLETE_TYPE1');
            }
        });

        return $response->withStatus(200);
    }

as you can see above i only compare to “nr1” because i dont know yet how to do a findUnique with two values, except for the way that @alexweissman already sugested, which is this:

if (\Userfrosting\Sprinkles\Site\Database\Models\myModel::where('nr1', $data['nr1'])->where('nr2', $data['nr2'])->first()) {
$ms->addMessageTranslated('danger', 'There is already a widget with nr1={{nr1}} and nr2={{nr2}}.', $data);
return $response->withStatus(400);
}   

but that doesnt work either, it just seems to ignore that part of the verification…

i’ll have a look there, thanks.