db = \Config\Database::connect(); // Check if the database connection is established if (!$this->db->connect()) { log_message('error', 'Database connection failed.'); throw new \Exception('Database connection failed.'); } else { log_message('info', 'Database connection successful.'); } $this->configModel = new ConfigurationModel(); $this->userRoleModel = new UserRoleModel(); $this->roleModel = new RoleModel(); $this->userModel = new UserModel(); $this->parentModel = new ParentModel(); $this->semester = $this->configModel->getConfig('semester'); $this->schoolYear = $this->configModel->getConfig('school_year'); } public function index() { log_message('debug', 'CAPTCHA session value: ' . session()->get('captcha_answer')); // Only generate CAPTCHA if not already set (first visit or after submission) if (!session()->has('captcha_answer')) { $length = rand(4, 8); $captchaText = $this->generateCaptchaText($length); session()->set('captcha_answer', $captchaText); } $data['captchaQuestion'] = session()->get('captcha_answer'); // ✅ NO need to check policy_accepted here return view('user/register', $data); } private function generateCaptchaText($length) { $characters = 'ABCDEFGHJKLMNOPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz0123456789'; $captchaText = ''; for ($i = 0; $i < $length; $i++) { $captchaText .= $characters[rand(0, strlen($characters) - 1)]; } return $captchaText; } public function success() { // Start session $session = session(); // Check if the session has 'user_email' if (!$session->has('user_email')) { // If not, redirect to the registration page return redirect()->to('/register'); } // Get 'user_email' from session $email = $session->get('user_email'); // Pass the 'email' variable to the success view return view('success', ['email' => $email]); } public function register() { helper(['url', 'form']); log_message('debug', 'CAPTCHA session value: ' . session()->get('captcha_answer')); /* ───────────── 1. Load services ───────────── */ $schoolIdService = new SchoolIdService(); /* ───────────── 2. Sanitize & raw data ───────────── */ $post = $this->formatUserInput($this->request); // safe / formatted $rawPost = $this->request->getPost(); // raw (for flags etc.) $captcha = $rawPost['captcha'] ?? ''; /* ───────────── 3. Validation rules ───────────── */ $rules = [ 'firstname' => 'required|regex_match[/^[a-zA-Z\s-]+$/]|min_length[2]|max_length[30]', 'lastname' => 'required|regex_match[/^[a-zA-Z\s-]+$/]|min_length[2]|max_length[30]', 'gender' => 'required|in_list[Male,Female]', 'email' => 'required|valid_email|max_length[50]', 'confirm_email' => 'required|matches[email]', 'cellphone' => 'required|regex_match[/^[\d\s\-\(\)\.]+$/]|min_length[10]|max_length[20]', 'address_street' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s\-]+$/]|max_length[150]', 'apt' => 'permit_empty|regex_match[/^[a-zA-Z0-9\s-]+$/]|max_length[10]', 'city' => 'required|alpha_space|min_length[2]|max_length[30]', 'state' => 'required|in_list[CT,ME,MA,NH,NY,RI,VT]', 'zip' => 'required|regex_match[/^\d{5}$/]', 'accept_school_policy' => 'required', 'captcha' => 'required|alpha_numeric|min_length[4]|max_length[10]', ]; // 1. Check if "I am a parent" checkbox is ticked $isParent = isset($rawPost['is_parent']) && $rawPost['is_parent'] == '1'; // 2. If parent, handle second parent logic if ($isParent) { $noSecondParent = isset($rawPost['no_second_parent_info']) && $rawPost['no_second_parent_info'] == '1'; $secondParentProvided = !empty($rawPost['second_firstname']) || !empty($rawPost['second_lastname']) || !empty($rawPost['second_gender']) || !empty($rawPost['second_email']) || !empty($rawPost['second_cellphone']); // 3. Error: must provide second parent or check "no second parent" if (!$noSecondParent && !$secondParentProvided) { return redirect()->back()->withInput()->with('errors', [ 'second_parent_info' => 'As a parent, please provide second parent information or check "No second parent info".' ]); } // 4. Add second parent field rules only if "no second parent" is NOT checked if (!$noSecondParent) { $rules += [ 'second_firstname' => 'required|regex_match[/^[a-zA-Z\s\-]+$/]|min_length[2]|max_length[30]', 'second_lastname' => 'required|regex_match[/^[a-zA-Z\s\-]+$/]|min_length[2]|max_length[30]', 'second_gender' => 'required|in_list[Male,Female]', 'second_email' => 'required|valid_email|max_length[50]', 'second_cellphone' => 'required|regex_match[/^[\d\s\-\(\)\.]+$/]|min_length[10]|max_length[20]', ]; } } log_message('debug', '🧪 Starting validation check...'); if (!$this->validate($rules)) { return redirect()->back()->withInput()->with('errors', $this->validator->getErrors()); } /* ───────────── 4. CAPTCHA check ───────────── */ if ($captcha !== session()->get('captcha_answer')) { return redirect()->back() ->withInput() ->with('errors', ['captcha' => '']) // keeps input marked as invalid ->with('top_error', 'Incorrect CAPTCHA answer. Please try again.'); } /* ───────────── 5. Unique e-mail ───────────── */ // Step 1: Check if a user with this email exists $existingUser = $this->userModel->where('email', $post['email'])->first(); if ($existingUser) { // Step 2: Check if the user has a token (i.e., not verified yet) if (!empty($existingUser['token']) && $existingUser['is_verified'] == 0) { // User exists and is unverified return redirect()->back()->withInput()->with('error', 'This email address is already registered and is pending activation. Please check your email to activate your account.'); } else { // User exists and is already active or has no token return redirect()->back()->withInput()->with('error', 'The email address you entered is already in use. Please try a different one.'); } } /* ───────────── 6. Determine role ───────────── */ $roleName = !empty($rawPost['is_parent']) ? 'parent' : 'guest'; $role = $this->roleModel->where('name', $roleName)->first(); if (!$role) { return redirect()->back()->withInput()->with('errors', ['role' => 'Role not found']); } /* ───────────── 7. Build & insert user ───────────── */ $token = bin2hex(random_bytes(48)); $userData = [ 'firstname' => $post['firstname'], 'lastname' => $post['lastname'], 'gender' => $post['gender'], 'email' => $post['email'], 'cellphone' => $post['cellphone'], 'address_street' => $post['address_street'] ?? null, 'apt' => $post['apt'] ?? null, 'city' => $post['city'], 'state' => $post['state'], 'zip' => $post['zip'], 'token' => $token, 'is_verified'=> 0, 'accept_school_policy' => (int) $post['accept_school_policy'], 'status' => 'Inactive', 'school_id' => $schoolIdService->generateUserSchoolId(), 'semester' => $this->semester, 'school_year'=> $this->schoolYear, ]; $this->db->transStart(); if (!$this->userModel->insert($userData)) { $this->db->transRollback(); return redirect()->back()->withInput()->with('errors', $this->userModel->errors()); } $firstParentId = $this->userModel->getInsertID(); $this->userRoleModel->insert([ 'user_id' => $firstParentId, 'role_id' => $role['id'], 'created_at' => utc_now(), ]); /* ───────────── 8. Optional second-parent insert ───────────── */ if ($isParent) { if (empty($rawPost['no_second_parent_info'])) { $this->parentModel->insert([ 'secondparent_firstname' => $post['second_firstname'], 'secondparent_lastname' => $post['second_lastname'], 'secondparent_gender' => $post['second_gender'], 'secondparent_email' => $post['second_email'], 'secondparent_phone' => $post['second_cellphone'], 'firstparent_id' => $firstParentId, 'semester' => $this->semester, 'school_year' => $this->schoolYear, ]); } } $this->db->transComplete(); if (!$this->db->transStatus()) { return redirect()->back()->withInput()->with('error', 'Registration failed, please try again.'); } /* ───────────── 9. Confirmation e-mail ───────────── */ $recipientName = $post['firstname'] . ' ' . $post['lastname']; $activationLink = site_url('/user/confirm/' . $token); $orgName = 'Al Rahma Sunday School'; $contactInfo = 'alrahma.isgl@gmail.com'; $logoUrl = 'https://alrahmaisgl.org/assets/images/alrahma_logo.png'; $emailData = [ 'recipientName' => $recipientName, 'activationLink' => $activationLink, 'orgName' => $orgName, 'contactInfo' => $contactInfo, 'logoUrl' => $logoUrl, 'subject' => 'Email Confirmation' ]; if ($isParent) { $html = view('emails/welcome_parent', $emailData); } else { $html = view('emails/welcome_staff', $emailData); } (new EmailService())->send($post['email'], 'Email Confirmation', $html, 'general'); /* ───────────── 10. Redirect to success ───────────── */ session()->set('user_email', $post['email']); session()->remove('captcha_answer'); // Clear CAPTCHA after success return redirect()->to('/register/success'); } private function formatName(string $name): string { $name = trim($name); return ucwords(strtolower($name), " -"); } private function formatEmail(?string $email): string { $email = strtolower(trim($email)); /* if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { throw new \InvalidArgumentException('Please enter a valid email address.'); }*/ return $email; } private function formatAddress(string $address): string { return ucwords(strtolower(trim($address))); } private function formatUserInput($request): array { $formatter = new PhoneFormatterService(); $post = (array) $request->getPost(); // tiny helper: safe getter + trim + cast to string $g = static function (string $key) use ($post): string { return trim((string) ($post[$key] ?? '')); }; $data = [ 'firstname' => $this->formatName($g('firstname')), 'lastname' => $this->formatName($g('lastname')), 'email' => $this->formatEmail($g('email')), 'gender' => $g('gender'), 'city' => $this->formatName($g('city')), 'cellphone' => $formatter->formatPhoneNumber($g('cellphone')), // ✅ safe 'address_street' => $this->formatAddress($g('address_street')), 'apt' => strtoupper($g('apt')), 'state' => strtoupper($g('state')), 'zip' => $g('zip'), 'accept_school_policy' => (int) ($post['accept_school_policy'] ?? 0), ]; // if user says "no second parent info", skip entirely $noSecondInfo = !empty($post['no_second_parent_info']); // collect (safely) and see if anything was provided $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 = $formatter->formatPhoneNumber($g('second_cellphone')); // ✅ safe $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; } }