358 lines
14 KiB
PHP
Executable File
358 lines
14 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Models\Configuration;
|
|
use App\Models\ParentModel;
|
|
use App\Models\Role;
|
|
use App\Models\User;
|
|
use App\Models\UserRole;
|
|
use App\Services\EmailService;
|
|
use App\Services\PhoneFormatterService;
|
|
use App\Services\SchoolIdService;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\Rule;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
|
|
class RegisterController extends BaseApiController
|
|
{
|
|
protected Configuration $config;
|
|
protected User $user;
|
|
protected UserRole $userRole;
|
|
protected Role $role;
|
|
protected Parent $parent;
|
|
protected SchoolIdService $schoolIdService;
|
|
protected EmailService $emailService;
|
|
protected PhoneFormatterService $phoneFormatter;
|
|
protected string $semester;
|
|
protected string $schoolYear;
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
|
|
$this->config = model(Configuration::class);
|
|
$this->user = model(User::class);
|
|
$this->userRole = model(UserRole::class);
|
|
$this->role = model(Role::class);
|
|
$this->parent = model(ParentModel::class);
|
|
$this->schoolIdService = app(SchoolIdService::class);
|
|
$this->emailService = app(EmailService::class);
|
|
$this->phoneFormatter = app(PhoneFormatterService::class);
|
|
|
|
$this->semester = (string) ($this->config->getConfig('semester') ?? '');
|
|
$this->schoolYear = (string) ($this->config->getConfig('school_year') ?? '');
|
|
}
|
|
|
|
public function index(): JsonResponse
|
|
{
|
|
$session = session();
|
|
log_message('debug', 'CAPTCHA session value: ' . ($session->get('captcha_answer') ?? ''));
|
|
|
|
if (!$session->has('captcha_answer')) {
|
|
$length = random_int(4, 8);
|
|
$session->put('captcha_answer', $this->generateCaptchaText($length));
|
|
}
|
|
|
|
return $this->respondSuccess([
|
|
'captcha_question' => $session->get('captcha_answer'),
|
|
], 'Captcha generated');
|
|
}
|
|
|
|
public function registrationSuccess(): JsonResponse
|
|
{
|
|
$session = session();
|
|
if (!$session->has('user_email')) {
|
|
return $this->respondError('No registration session found.', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
return $this->respondSuccess([
|
|
'email' => $session->get('user_email'),
|
|
], 'Registration success data');
|
|
}
|
|
|
|
public function register(): JsonResponse
|
|
{
|
|
$payload = $this->payloadData();
|
|
log_message('debug', 'CAPTCHA session value: ' . (session()->get('captcha_answer') ?? ''));
|
|
|
|
$formatted = $this->formatUserInput($payload);
|
|
$validationPayload = array_merge($payload, $formatted);
|
|
$captcha = (string) ($payload['captcha'] ?? '');
|
|
$isParent = !empty($payload['is_parent']) && (string) $payload['is_parent'] === '1';
|
|
$noSecondParentInfo = !empty($payload['no_second_parent_info']) && (string) $payload['no_second_parent_info'] === '1';
|
|
|
|
$rules = [
|
|
'firstname' => ['required', 'regex:/^[a-zA-Z\s-]+$/', 'min:2', 'max:30'],
|
|
'lastname' => ['required', 'regex:/^[a-zA-Z\s-]+$/', 'min:2', 'max:30'],
|
|
'gender' => ['required', Rule::in(['Male', 'Female'])],
|
|
'email' => ['required', 'email', 'max:50'],
|
|
'confirm_email' => ['required', 'same:email'],
|
|
'cellphone' => ['required', 'regex:/^[\d\s\-\(\)\.]+$/', 'min:10', 'max:20'],
|
|
'address_street' => ['nullable', 'regex:/^[a-zA-Z0-9\s\-]+$/', 'max:150'],
|
|
'apt' => ['nullable', 'regex:/^[a-zA-Z0-9\s-]+$/', 'max:10'],
|
|
'city' => ['required', 'regex:/^[a-zA-Z\s]+$/', 'min:2', 'max:30'],
|
|
'state' => ['required', Rule::in(['CT', 'ME', 'MA', 'NH', 'NY', 'RI', 'VT'])],
|
|
'zip' => ['required', 'regex:/^\d{5}$/'],
|
|
'accept_school_policy' => ['required', 'accepted'],
|
|
'captcha' => ['required', 'alpha_num', 'min:4', 'max:10'],
|
|
];
|
|
|
|
if ($isParent && !$noSecondParentInfo) {
|
|
$rules = array_merge($rules, [
|
|
'second_firstname' => ['required', 'regex:/^[a-zA-Z\s-]+$/', 'min:2', 'max:30'],
|
|
'second_lastname' => ['required', 'regex:/^[a-zA-Z\s-]+$/', 'min:2', 'max:30'],
|
|
'second_gender' => ['required', Rule::in(['Male', 'Female'])],
|
|
'second_email' => ['required', 'email', 'max:50'],
|
|
'second_cellphone' => ['required', 'regex:/^[\d\s\-\(\)\.]+$/', 'min:10', 'max:20'],
|
|
]);
|
|
}
|
|
|
|
$errors = $this->validateRequest($validationPayload, $rules);
|
|
if (!empty($errors)) {
|
|
return $this->respondValidationError($errors);
|
|
}
|
|
|
|
if ($isParent && !$noSecondParentInfo && !$this->hasSecondParentData($formatted)) {
|
|
return $this->respondValidationError([
|
|
'second_parent_info' => ['As a parent, please provide second parent information or check "No second parent info".'],
|
|
]);
|
|
}
|
|
|
|
if ($captcha !== (string) session()->get('captcha_answer')) {
|
|
return $this->respondValidationError([
|
|
'captcha' => ['Incorrect CAPTCHA answer. Please try again.'],
|
|
], 'Incorrect CAPTCHA answer.');
|
|
}
|
|
|
|
$email = $formatted['email'] ?? '';
|
|
$existingUser = $this->user->newQuery()->where('email', $email)->first();
|
|
|
|
if ($existingUser) {
|
|
$isVerified = (int) ($existingUser->is_verified ?? 0);
|
|
$token = $existingUser->token ?? null;
|
|
|
|
if (!empty($token) && $isVerified === 0) {
|
|
return $this->respondError(
|
|
'This email address is already registered and is pending activation. Please check your email to activate your account.',
|
|
Response::HTTP_CONFLICT
|
|
);
|
|
}
|
|
|
|
return $this->respondError(
|
|
'The email address you entered is already in use. Please try a different one.',
|
|
Response::HTTP_CONFLICT
|
|
);
|
|
}
|
|
|
|
$roleName = $isParent ? 'parent' : 'guest';
|
|
$role = $this->role->newQuery()->where('name', $roleName)->first();
|
|
if (!$role) {
|
|
return $this->respondError('Role not found', Response::HTTP_BAD_REQUEST);
|
|
}
|
|
|
|
$token = bin2hex(random_bytes(48));
|
|
$userData = [
|
|
'firstname' => $formatted['firstname'] ?? null,
|
|
'lastname' => $formatted['lastname'] ?? null,
|
|
'gender' => $formatted['gender'] ?? null,
|
|
'email' => $email,
|
|
'cellphone' => $formatted['cellphone'] ?? null,
|
|
'address_street' => $formatted['address_street'] ?? null,
|
|
'apt' => $formatted['apt'] ?? null,
|
|
'city' => $formatted['city'] ?? null,
|
|
'state' => $formatted['state'] ?? null,
|
|
'zip' => $formatted['zip'] ?? null,
|
|
'token' => $token,
|
|
'is_verified' => 0,
|
|
'accept_school_policy' => (int) ($formatted['accept_school_policy'] ?? 0),
|
|
'status' => 'Inactive',
|
|
'school_id' => $this->schoolIdService->generate(),
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
];
|
|
|
|
DB::beginTransaction();
|
|
try {
|
|
$user = $this->user->newQuery()->create($userData);
|
|
$firstParentId = $user->id ?? null;
|
|
|
|
if (!$firstParentId) {
|
|
DB::rollBack();
|
|
return $this->respondError('Registration failed, please try again.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
$this->userRole->newQuery()->create([
|
|
'user_id' => $firstParentId,
|
|
'role_id' => $role->id,
|
|
'semester' => $this->semester,
|
|
'school_year'=> $this->schoolYear,
|
|
'created_at' => utc_now(),
|
|
]);
|
|
|
|
if ($isParent && !$noSecondParentInfo && $this->hasSecondParentData($formatted)) {
|
|
$this->parent->newQuery()->create([
|
|
'secondparent_firstname' => $formatted['second_firstname'] ?? null,
|
|
'secondparent_lastname' => $formatted['second_lastname'] ?? null,
|
|
'secondparent_gender' => $formatted['second_gender'] ?? null,
|
|
'secondparent_email' => $formatted['second_email'] ?? null,
|
|
'secondparent_phone' => $formatted['second_cellphone'] ?? null,
|
|
'firstparent_id' => $firstParentId,
|
|
'semester' => $this->semester,
|
|
'school_year' => $this->schoolYear,
|
|
]);
|
|
}
|
|
|
|
DB::commit();
|
|
} catch (\Throwable $e) {
|
|
DB::rollBack();
|
|
log_message('error', 'Registration failed: ' . $e->getMessage());
|
|
return $this->respondError('Registration failed, please try again.', Response::HTTP_INTERNAL_SERVER_ERROR);
|
|
}
|
|
|
|
$recipientName = trim(($formatted['firstname'] ?? '') . ' ' . ($formatted['lastname'] ?? ''));
|
|
$emailData = [
|
|
'recipientName' => $recipientName,
|
|
'activationLink'=> url('/user/confirm/' . $token),
|
|
'orgName' => 'Al Rahma Sunday School',
|
|
'contactInfo' => 'alrahma.isgl@gmail.com',
|
|
'logoUrl' => 'https://alrahmaisgl.org/assets/images/alrahma_logo.png',
|
|
];
|
|
|
|
$html = $this->buildEmailBody($emailData, $isParent);
|
|
$this->emailService->send($email, 'Email Confirmation', $html, 'general');
|
|
|
|
session()->put('user_email', $email);
|
|
session()->forget('captcha_answer');
|
|
|
|
return $this->respondSuccess([
|
|
'email' => $email,
|
|
], 'Registration successful');
|
|
}
|
|
|
|
private function generateCaptchaText(int $length): string
|
|
{
|
|
$characters = 'ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz0123456789';
|
|
$captchaText = '';
|
|
|
|
for ($i = 0; $i < $length; $i++) {
|
|
$captchaText .= $characters[random_int(0, strlen($characters) - 1)];
|
|
}
|
|
|
|
return $captchaText;
|
|
}
|
|
|
|
private function formatUserInput(array $post): array
|
|
{
|
|
$g = static function (string $key) use ($post): string {
|
|
return trim((string) ($post[$key] ?? ''));
|
|
};
|
|
|
|
$data = [
|
|
'firstname' => $this->formatName($g('firstname')),
|
|
'lastname' => $this->formatName($g('lastname')),
|
|
'gender' => $g('gender'),
|
|
'email' => $this->formatEmail($g('email')),
|
|
'confirm_email' => $this->formatEmail($g('confirm_email')),
|
|
'cellphone' => $this->phoneFormatter->formatPhoneNumber($g('cellphone')),
|
|
'address_street' => $this->nullIfEmpty($this->formatAddress($g('address_street'))),
|
|
'apt' => $this->nullIfEmpty(strtoupper($g('apt'))),
|
|
'city' => $this->formatName($g('city')),
|
|
'state' => strtoupper($g('state')),
|
|
'zip' => $g('zip'),
|
|
'accept_school_policy' => (int) ($post['accept_school_policy'] ?? 0),
|
|
];
|
|
|
|
$noSecondInfo = !empty($post['no_second_parent_info']);
|
|
|
|
$second_firstname = $this->formatName($g('second_firstname'));
|
|
$second_lastname = $this->formatName($g('second_lastname'));
|
|
$second_gender = $g('second_gender');
|
|
$second_email = $this->formatEmail($g('second_email'));
|
|
$second_cellphone = $this->phoneFormatter->formatPhoneNumber($g('second_cellphone'));
|
|
|
|
$secondProvided = !$noSecondInfo && (
|
|
$second_firstname !== '' ||
|
|
$second_lastname !== '' ||
|
|
$second_gender !== '' ||
|
|
$second_email !== '' ||
|
|
preg_replace('/\D/', '', $second_cellphone) !== ''
|
|
);
|
|
|
|
if ($secondProvided) {
|
|
$data['second_firstname'] = $second_firstname;
|
|
$data['second_lastname'] = $second_lastname;
|
|
$data['second_gender'] = $second_gender;
|
|
$data['second_email'] = $second_email;
|
|
$data['second_cellphone'] = $second_cellphone;
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
private function formatName(string $name): string
|
|
{
|
|
$name = trim($name);
|
|
return ucwords(strtolower($name), " -");
|
|
}
|
|
|
|
private function formatEmail(?string $email): string
|
|
{
|
|
return strtolower(trim((string) $email));
|
|
}
|
|
|
|
private function formatAddress(?string $address): string
|
|
{
|
|
$address = trim((string) $address);
|
|
if ($address === '') {
|
|
return '';
|
|
}
|
|
|
|
return ucwords(strtolower($address));
|
|
}
|
|
|
|
private function nullIfEmpty(?string $value): ?string
|
|
{
|
|
if ($value === null) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim((string) $value);
|
|
return $value === '' ? null : $value;
|
|
}
|
|
|
|
private function hasSecondParentData(array $data): bool
|
|
{
|
|
return !empty($data['second_firstname'])
|
|
|| !empty($data['second_lastname'])
|
|
|| !empty($data['second_gender'])
|
|
|| !empty($data['second_email'])
|
|
|| (!empty($data['second_cellphone']) && preg_replace('/\D/', '', $data['second_cellphone']) !== '');
|
|
}
|
|
|
|
private function buildEmailBody(array $data, bool $isParent): string
|
|
{
|
|
$audience = $isParent ? 'Parent' : 'Staff Member';
|
|
|
|
return <<<HTML
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Email Confirmation</title>
|
|
</head>
|
|
<body>
|
|
<p>Dear {$data['recipientName']},</p>
|
|
<p>Thank you for registering with {$data['orgName']} as a {$audience}. Please confirm your email address by clicking the link below:</p>
|
|
<p><a href="{$data['activationLink']}" target="_blank">Activate My Account</a></p>
|
|
<p>If the link above does not work, copy and paste this URL into your browser:</p>
|
|
<p>{$data['activationLink']}</p>
|
|
<p>For questions, reach us at {$data['contactInfo']}.</p>
|
|
<p>Thank you,<br>{$data['orgName']}</p>
|
|
</body>
|
|
</html>
|
|
HTML;
|
|
}
|
|
}
|