From e09f763db32deac32030bed0b8045f53031704e0 Mon Sep 17 00:00:00 2001 From: essae Date: Fri, 23 May 2025 14:23:08 -0400 Subject: [PATCH] feat(verification): improve expired and invalid code handling + unified view styling - Updated VerificationController::verify() to: - Distinguish between invalid and expired verification codes - Preserve expired codes to allow resend and proper feedback - Log verification attempts with safe type handling - Display a clear message for already-verified submissions - Avoid DB errors from unknown 'type' values (supports contact+newsletter) - Updated verify_success.php and verify_failed.php: - Unified layout using Arsha theme with View::render() wrapping buffered output - `verify_failed.php` now shows a resend form only if applicable - If code is invalid and no email context is known, redirect prompt is shown - Ensured fallback logic for all messaging variables is robust and user-safe --- app/Controllers/ContactController.php | 111 +++-- .../ResendVerficationController.php | 104 ----- .../ResendVerificationController.php | 60 +++ app/Controllers/SubscriberController.php | 6 +- app/Controllers/UnsubscribeController.php | 6 +- app/Controllers/VerificationController.php | 190 +++++++-- app/Models/ContactModel.php | 23 +- app/Services/EmailService.php | 165 ++++++++ app/Services/NewsletterService.php | 8 +- app/Services/ResendVerificationService.php | 110 +++++ app/Services/VerificationService.php | 104 +++++ app/Utilities/EmailHelper.php | 224 ++++------ app/Utilities/EmailUtility.php | 393 ------------------ app/Utilities/SubmissionCheck.php | 17 +- ...tworks-logo-v2-no-slogan-blue-1119x303.png | Bin 0 -> 38936 bytes public/assets/img/wizdom-networks-logo-v2.png | Bin 0 -> 51568 bytes public/assets/js/contact-form.js | 10 +- public/index.php | 4 +- .../emails/contact_confirmation_email.html | 66 --- .../views/emails/admin_contact_alert.php | 22 + .../views/emails/contact_and_newsletter.php | 61 +++ resources/views/emails/newsletter_welcome.php | 71 ++++ .../views/emails/resend_verification.php | 24 ++ resources/views/emails/sales_lead_alert.php | 21 + resources/views/emails/system_alert.php | 57 +++ .../views/emails/verified_confirmation.php | 28 ++ resources/views/emails/verify_contact.php | 41 ++ resources/views/emails/verify_email.php | 26 ++ resources/views/emails/verify_newsletter.php | 43 ++ resources/views/layouts/arsha.php | 5 +- resources/views/layouts/email_layout.php | 40 ++ resources/views/layouts/footer.php | 2 +- resources/views/pages/verify_failed.php | 34 +- resources/views/pages/verify_success.php | 9 + resources/views/partials/contact.php | 6 +- scripts/empty_database.sql | 4 + 36 files changed, 1254 insertions(+), 841 deletions(-) delete mode 100644 app/Controllers/ResendVerficationController.php create mode 100644 app/Controllers/ResendVerificationController.php create mode 100644 app/Services/EmailService.php create mode 100644 app/Services/ResendVerificationService.php create mode 100644 app/Services/VerificationService.php delete mode 100644 app/Utilities/EmailUtility.php create mode 100644 public/assets/img/wizdom-networks-logo-v2-no-slogan-blue-1119x303.png create mode 100644 public/assets/img/wizdom-networks-logo-v2.png delete mode 100644 resources/templates/emails/contact_confirmation_email.html create mode 100644 resources/views/emails/admin_contact_alert.php create mode 100644 resources/views/emails/contact_and_newsletter.php create mode 100644 resources/views/emails/newsletter_welcome.php create mode 100644 resources/views/emails/resend_verification.php create mode 100644 resources/views/emails/sales_lead_alert.php create mode 100644 resources/views/emails/system_alert.php create mode 100644 resources/views/emails/verified_confirmation.php create mode 100644 resources/views/emails/verify_contact.php create mode 100644 resources/views/emails/verify_email.php create mode 100644 resources/views/emails/verify_newsletter.php create mode 100644 resources/views/layouts/email_layout.php create mode 100644 scripts/empty_database.sql diff --git a/app/Controllers/ContactController.php b/app/Controllers/ContactController.php index e31a2ad..e6c4ddd 100644 --- a/app/Controllers/ContactController.php +++ b/app/Controllers/ContactController.php @@ -1,9 +1,11 @@ emailService = new EmailService(); + $this->verificationService = new VerificationService(); + } + + /** + * Renders the landing page containing the contact form. + */ public function index(): void { View::render('pages/landing'); } + /** + * Handles form submission: validates, logs, checks abuse, stores, and triggers verification. + * If user opted in to the newsletter, flags for follow-up after verification. + */ public function submit(): void { Logger::info("Executing controller: ContactController::submit"); @@ -42,12 +65,14 @@ class ContactController 'message' => Sanitizer::sanitizeString($_POST['message'] ?? ''), 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', + 'pending_newsletter_opt_in' => isset($_POST['subscribe_newsletter']) && $_POST['subscribe_newsletter'] === '1' ? 1 : 0 ]; foreach ($formData as $key => $value) { Logger::info("Sanitized input: {$key} = {$value}"); } + // Validate required fields and email format if ( empty($formData['first_name']) || empty($formData['last_name']) || @@ -58,26 +83,26 @@ class ContactController !Validator::isEmail($formData['email']) ) { Logger::info("Validation failed for contact form submission"); - $_SESSION['contact_error'] = 'An internal error occurred. Please try again later.'; + $_SESSION['contact_error'] = 'Validation error. Please try again.'; SessionHelper::writeClose(); - header("Location: /#contact"); - exit; + $this->respondOrRedirect(false, 'Validation error.'); } $db = Database::getConnection(); + // Run submission abuse heuristics $evaluation = SubmissionCheck::evaluate($db, $formData['email'], $formData['phone'], $formData['ip_address']); Logger::info("Submission evaluation result: " . json_encode($evaluation)); if ($evaluation['action'] === 'block') { $_SESSION['contact_error'] = "Submission blocked due to suspicious activity. If this is a mistake, please contact us directly."; Logger::warning("Blocked submission from IP: {$formData['ip_address']}, Reason: {$evaluation['reason']}"); - EmailHelper::alertAdmins('Blocked Submission Detected', "A submission was blocked for the following reason: {$evaluation['reason']}", $formData); + $this->emailService->alertAdmins('Blocked Submission Detected', $evaluation['reason'], $formData); SessionHelper::writeClose(); - header("Location: /#contact"); - exit; + $this->respondOrRedirect(false, 'Submission blocked.'); } + // Log submission intent $logId = null; try { $logStmt = $db->prepare("INSERT INTO submission_logs (email, phone, ip_address, user_agent, was_saved, reason) VALUES (:email, :phone, :ip, :ua, :saved, :reason)"); @@ -94,46 +119,68 @@ class ContactController Logger::error("Failed to insert into submission_logs: " . $e->getMessage()); } + // Save form content $contactModel = new ContactModel($db); $saveSuccess = $contactModel->saveContactForm($formData); - $contactId = $db->lastInsertId(); - $verificationCode = bin2hex(random_bytes(16)); - $expiresAt = (new \DateTime('+72 hours'))->format('Y-m-d H:i:s'); + // Assign verification code if ($saveSuccess) { - $stmt = $db->prepare("UPDATE contact_messages SET verification_code = ?, is_verified = 0, verification_expires_at = ? WHERE id = ?"); - $stmt->execute([$verificationCode, $expiresAt, $contactId]); - ContactService::sendVerificationEmail($formData['email'], $verificationCode); + $verificationCode = $this->verificationService->generateCode(); + $expiresAt = $this->verificationService->getExpirationTime(); + $this->verificationService->assignCodeToRecord('contact_messages', $contactId, $verificationCode, $expiresAt); + + $this->emailService->sendVerificationEmail( + $formData['email'], + $verificationCode, + 'verify_contact', + ['first_name' => $formData['first_name']] + ); } + // Update log if save succeeded if ($saveSuccess && $logId) { $update = $db->prepare("UPDATE submission_logs SET was_saved = 1 WHERE id = :id"); $update->execute([':id' => $logId]); } - // Newsletter opt-in logic - if (!empty($_POST['subscribe_newsletter'])) { - Logger::info("Contact opted into newsletter: {$formData['email']}"); - NewsletterService::subscribeOrResend($formData['email']); - } - - Logger::info("✅ Writing session flag: contact_success = true"); - Logger::info("✅ Session content before redirect: " . json_encode($_SESSION)); - SessionHelper::writeClose(); - View::render('pages/contact_check_email'); - return; + $this->respondOrRedirect(true, 'Your message was submitted. Please check your email to verify.'); } catch (\Throwable $e) { Logger::error("Fatal error in ContactController::submit: " . $e->getMessage()); - EmailHelper::alertAdmins('ContactController::submit - Uncaught Exception', $e->getMessage(), $_POST ?? []); + $this->emailService->alertAdmins('ContactController::submit - Uncaught Exception', $e->getMessage(), $_POST ?? []); $_SESSION['contact_error'] = 'An internal error occurred. Please try again later.'; SessionHelper::writeClose(); - Logger::info("✅ Writing session flag: catch contact_error = " . $_SESSION['contact_error']); - Logger::info("✅ Session content before redirect: " . json_encode($_SESSION)); - header("Location: /#contact"); - exit; + $this->respondOrRedirect(false, 'An internal error occurred.'); } } + + /** + * Responds to client depending on request type (AJAX vs standard). + * @param bool $success Indicates if the operation succeeded + * @param string $message Message to return or display + */ + private function respondOrRedirect(bool $success, string $message): void + { + $isAjax = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && + strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; + + Logger::debug('Detected request type: ' . ($_SERVER['HTTP_X_REQUESTED_WITH'] ?? 'none')); + Logger::debug('Will respond with: ' . ($isAjax ? 'JSON' : 'HTML fallback')); + + if ($isAjax) { + header('Content-Type: application/json'); + echo json_encode(['success' => $success, 'message' => $message]); + exit; + } + + if ($success) { + View::render('pages/contact_check_email'); + } else { + header("Location: /#contact"); + } + + exit; + } } diff --git a/app/Controllers/ResendVerficationController.php b/app/Controllers/ResendVerficationController.php deleted file mode 100644 index 1742120..0000000 --- a/app/Controllers/ResendVerficationController.php +++ /dev/null @@ -1,104 +0,0 @@ - 'Invalid email or type.']); - return; - } - - try { - $db = Database::getConnection(); - - // Rate limit: no more than 3 per day - $dailyCheck = $db->prepare("SELECT COUNT(*) FROM verification_attempts WHERE email = ? AND type = ? AND attempted_at >= NOW() - INTERVAL 1 DAY"); - $dailyCheck->execute([$email, $type]); - $dailyCount = $dailyCheck->fetchColumn(); - - if ($dailyCount >= 3) { - View::render('pages/verify_failed', ['reason' => 'You have reached the daily resend limit. Please try again tomorrow.']); - return; - } - - // Rate limit: no more than 1 every 5 minutes - $recentCheck = $db->prepare("SELECT COUNT(*) FROM verification_attempts WHERE email = ? AND type = ? AND attempted_at >= NOW() - INTERVAL 5 MINUTE"); - $recentCheck->execute([$email, $type]); - $recentCount = $recentCheck->fetchColumn(); - - if ($recentCount > 0) { - View::render('pages/verify_failed', ['reason' => 'You must wait a few minutes before requesting another verification email.']); - return; - } - - // Log attempt - $log = $db->prepare("INSERT INTO verification_attempts (email, type, attempted_at, ip_address, user_agent) VALUES (?, ?, NOW(), ?, ?)"); - $log->execute([ - $email, - $type, - $_SERVER['REMOTE_ADDR'] ?? 'unknown', - $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', - ]); - - $code = bin2hex(random_bytes(16)); - $expiry = (new \DateTime())->modify('+72 hours')->format('Y-m-d H:i:s'); - - if ($type === 'newsletter') { - $stmt = $db->prepare("SELECT id, is_verified FROM subscribers WHERE email = ?"); - $stmt->execute([$email]); - $row = $stmt->fetch(); - - if (!$row || (int)$row['is_verified'] === 1) { - View::render('pages/verify_failed', ['reason' => 'Email is already verified or not found.']); - return; - } - - $update = $db->prepare("UPDATE subscribers SET verification_code = ?, is_verified = 0, verification_expires_at = ? WHERE id = ?"); - $update->execute([$code, $expiry, $row['id']]); - NewsletterService::sendVerificationEmail($email, $code); - } - - if ($type === 'contact') { - $stmt = $db->prepare("SELECT id, is_verified FROM contact_messages WHERE email = ? ORDER BY created_at DESC LIMIT 1"); - $stmt->execute([$email]); - $row = $stmt->fetch(); - - if (!$row || (int)$row['is_verified'] === 1) { - View::render('pages/verify_failed', ['reason' => 'Email is already verified or not found.']); - return; - } - - $update = $db->prepare("UPDATE contact_messages SET verification_code = ?, is_verified = 0, verification_expires_at = ? WHERE id = ?"); - $update->execute([$code, $expiry, $row['id']]); - ContactService::sendVerificationEmail($email, $code); - } - - View::render('pages/verify_success', [ - 'type' => $type, - 'message' => 'We just sent you a new verification link.' - ]); - } catch (\Throwable $e) { - Logger::error("Resend verification failed: " . $e->getMessage()); - View::render('pages/verify_failed', ['reason' => 'Unexpected error occurred.']); - } - } -} \ No newline at end of file diff --git a/app/Controllers/ResendVerificationController.php b/app/Controllers/ResendVerificationController.php new file mode 100644 index 0000000..4a236ad --- /dev/null +++ b/app/Controllers/ResendVerificationController.php @@ -0,0 +1,60 @@ +resendService = new ResendVerificationService(); + } + + /** + * Handles a POST request to resend a verification email. + * Validates email and type, and then delegates the resend attempt to the service. + * Renders either a success or failure view based on outcome. + * + * Expects 'email' and 'type' keys to be set in $_POST. + * + * @return void + */ + public function handle(): void + { + $email = trim($_POST['email'] ?? ''); + $type = trim($_POST['type'] ?? ''); + + if (!$email || !$type || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + View::render('pages/verify_failed', ['reason' => 'Invalid email or type.']); + return; + } + + $result = $this->resendService->attemptResend($type, $email); + + if (!$result['success']) { + View::render('pages/verify_failed', ['reason' => $result['message']]); + } else { + View::render('pages/verify_success', [ + 'type' => $type, + 'message' => $result['message'] + ]); + } + } +} diff --git a/app/Controllers/SubscriberController.php b/app/Controllers/SubscriberController.php index 6f8affb..d9fa604 100644 --- a/app/Controllers/SubscriberController.php +++ b/app/Controllers/SubscriberController.php @@ -10,9 +10,9 @@ namespace WizdomNetworks\WizeWeb\Controllers; use WizdomNetworks\WizeWeb\Core\View; -use WizdomNetworks\WizeWeb\Utils\Database; -use WizdomNetworks\WizeWeb\Utils\Logger; -use WizdomNetworks\WizeWeb\Utils\ErrorHandler; +use WizdomNetworks\WizeWeb\Utilities\Database; +use WizdomNetworks\WizeWeb\Utilities\Logger; +use WizdomNetworks\WizeWeb\Utilities\ErrorHandler; class SubscriberController { diff --git a/app/Controllers/UnsubscribeController.php b/app/Controllers/UnsubscribeController.php index 8d8d161..59c1bb7 100644 --- a/app/Controllers/UnsubscribeController.php +++ b/app/Controllers/UnsubscribeController.php @@ -10,9 +10,9 @@ namespace WizdomNetworks\WizeWeb\Controllers; use WizdomNetworks\WizeWeb\Core\View; -use WizdomNetworks\WizeWeb\Utils\Database; -use WizdomNetworks\WizeWeb\Utils\Logger; -use WizdomNetworks\WizeWeb\Utils\ErrorHandler; +use WizdomNetworks\WizeWeb\Utilities\Database; +use WizdomNetworks\WizeWeb\Utilities\Logger; +use WizdomNetworks\WizeWeb\Utilities\ErrorHandler; class UnsubscribeController { diff --git a/app/Controllers/VerificationController.php b/app/Controllers/VerificationController.php index f372abc..9bf13d0 100644 --- a/app/Controllers/VerificationController.php +++ b/app/Controllers/VerificationController.php @@ -1,71 +1,177 @@ 'No verification code provided.']); - return; - } + $this->emailService = new EmailService(); + } - $db = Database::getConnection(); + /** + * Handles email verification for newsletter and contact submissions using a unique code. + * + * - If the code matches an unverified record: marks it as verified and sends confirmations. + * - If already verified: shows a message. + * - If expired: prompts user to resend. + * - If invalid: redirects user to restart the process. + * + * @param string $code The verification code from the URL path. + * @return void + */ +public function verify(string $code): void +{ + try { + if (empty($code)) { + Logger::error("Email verification attempted without a code."); + View::render('pages/verify_failed', [ + 'reason' => 'No verification code provided.', + 'redirect' => true + ]); + return; + } - // Check subscribers table - $stmt = $db->prepare("SELECT id, is_verified, email, verification_expires_at FROM subscribers WHERE verification_code = ?"); + $db = Database::getConnection(); + $subscriber = null; + $table = null; + $type = null; + + // Attempt to locate the subscriber record by code in either table + $stmt = $db->prepare("SELECT * FROM subscribers WHERE verification_code = ?"); + $stmt->execute([$code]); + $subscriber = $stmt->fetch(); + + if ($subscriber) { + $table = 'subscribers'; + $type = 'newsletter'; + } else { + $stmt = $db->prepare("SELECT * FROM contact_messages WHERE verification_code = ?"); $stmt->execute([$code]); $subscriber = $stmt->fetch(); - // Log verification attempt (even if failed) - $logAttempt = $db->prepare("INSERT INTO verification_attempts (email, type, attempted_at, ip_address, user_agent) VALUES (?, ?, NOW(), ?, ?)"); - $logAttempt->execute([ - $subscriber['email'] ?? '[unknown]', - 'newsletter', - $_SERVER['REMOTE_ADDR'] ?? 'unknown', - $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' - ]); - if ($subscriber) { - if (!empty($subscriber['verification_expires_at']) && strtotime($subscriber['verification_expires_at']) < time()) { - View::render('pages/verify_failed', ['reason' => 'Your verification link has expired. Please request a new one.']); - return; - } + $table = 'contact_messages'; + $type = 'contact'; + } + } - if ((int) $subscriber['is_verified'] === 1) { - View::render('pages/verify_success', ['type' => 'newsletter', 'message' => 'This subscription has already been verified.']); - return; - } + // If no record was found at all + if (!$subscriber) { + Logger::error("Invalid verification code attempted: $code"); + View::render('pages/verify_failed', [ + 'reason' => 'That link is invalid. You may need to start a new submission.', + 'redirect' => true + ]); + return; + } - $update = $db->prepare("UPDATE subscribers SET is_verified = 1, verification_code = NULL WHERE id = ?"); - $update->execute([$subscriber['id']]); + // Handle expired code case + if (!empty($subscriber['verification_expires_at']) && strtotime($subscriber['verification_expires_at']) < time()) { + Logger::info("Verification link expired: $code"); + View::render('pages/verify_failed', [ + 'reason' => 'Your verification link has expired. Please request a new one.', + 'type' => $type ?? 'unknown' + ]); + return; + } - Logger::info("Subscriber verified: ID " . $subscriber['id']); - View::render('pages/verify_success', ['type' => 'newsletter']); - return; + // Log the verification attempt regardless of outcome + $safeType = in_array($type, ['contact', 'newsletter', 'contact+newsletter'], true) ? $type : 'unknown'; + $logAttempt = $db->prepare(" + INSERT INTO verification_attempts (email, type, attempted_at, ip_address, user_agent) + VALUES (?, ?, NOW(), ?, ?) + "); + $logAttempt->execute([ + $subscriber['email'] ?? '[unknown]', + $safeType, + $_SERVER['REMOTE_ADDR'] ?? 'unknown', + $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' + ]); + + // If already verified + if ((int) $subscriber['is_verified'] === 1) { + View::render('pages/verify_success', [ + 'type' => $type ?? 'unknown', + 'message' => 'This submission has already been verified.' + ]); + return; + } + + // Mark the submission as verified + $update = $db->prepare("UPDATE $table SET is_verified = 1, verification_code = NULL WHERE id = ?"); + $update->execute([$subscriber['id']]); + + Logger::info("Subscriber verified: ID {$subscriber['id']} via $type"); + + // Handle post-verification logic for contact submissions + if ($type === 'contact') { + $stmt = $db->prepare(" + SELECT first_name, last_name, subject, message, pending_newsletter_opt_in + FROM contact_messages WHERE id = ? + "); + $stmt->execute([$subscriber['id']]); + $details = $stmt->fetch(); + + $emailData = [ + 'email' => $subscriber['email'], + 'first_name' => $details['first_name'] ?? '', + 'last_name' => $details['last_name'] ?? '', + 'subject' => $details['subject'] ?? '', + 'message' => $details['message'] ?? '', + 'ip_address' => $_SERVER['REMOTE_ADDR'] ?? 'unknown', + 'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown' + ]; + + // If opted in to newsletter from contact form + if (!empty($details['pending_newsletter_opt_in'])) { + $this->emailService->sendContactAndNewsletterWelcome($emailData); + + $db->prepare("UPDATE contact_messages SET pending_newsletter_opt_in = 0 WHERE id = ?") + ->execute([$subscriber['id']]); + + $db->prepare(" + INSERT INTO subscribers (email, is_verified, created_at) + VALUES (?, 1, NOW()) + ON DUPLICATE KEY UPDATE is_verified = 1 + ")->execute([$subscriber['email']]); + + $type = 'contact+newsletter'; // Refined to reflect both intents + } else { + $this->emailService->sendConfirmationToUser($emailData); } - Logger::error("Invalid or expired verification code: $code"); - View::render('pages/verify_failed', ['reason' => 'Verification code is invalid or expired.']); - } catch (\Throwable $e) { - Logger::error("Verification exception: " . $e->getMessage()); - View::render('pages/verify_failed', ['reason' => 'An error occurred during verification.']); + $this->emailService->sendSalesNotification($emailData); } + + // Final success render + View::render('pages/verify_success', [ + 'type' => $type ?? 'unknown', + 'message' => null + ]); + + } catch (\Throwable $e) { + Logger::error("Verification exception: " . $e->getMessage()); + View::render('pages/verify_failed', [ + 'reason' => 'An error occurred during verification.', + 'redirect' => true + ]); } } + +} diff --git a/app/Models/ContactModel.php b/app/Models/ContactModel.php index 9d7218a..b8edc68 100644 --- a/app/Models/ContactModel.php +++ b/app/Models/ContactModel.php @@ -1,9 +1,9 @@ db->prepare(" - INSERT INTO contacts (name, email, message) - VALUES (:name, :email, :message) - "); + $stmt = $this->db->prepare("INSERT INTO contacts (name, email, message) VALUES (:name, :email, :message)"); $name = trim(($contactData['name'] ?? '') ?: (($contactData['first_name'] ?? '') . ' ' . ($contactData['last_name'] ?? ''))); $stmt->bindParam(':name', $name); @@ -57,6 +54,7 @@ class ContactModel /** * Saves full contact form submission to the `contact_messages` table. + * Includes newsletter opt-in flag. * * @param array $formData Associative array of form input * @return bool True on success, false on failure @@ -64,15 +62,13 @@ class ContactModel public function saveContactForm(array $formData): bool { try { - $stmt = $this->db->prepare(" - INSERT INTO contact_messages ( + $stmt = $this->db->prepare("INSERT INTO contact_messages ( first_name, last_name, email, phone, subject, message, - ip_address, user_agent + ip_address, user_agent, pending_newsletter_opt_in ) VALUES ( :first_name, :last_name, :email, :phone, :subject, :message, - :ip_address, :user_agent - ) - "); + :ip_address, :user_agent, :pending_newsletter_opt_in + )"); $stmt->bindParam(':first_name', $formData['first_name']); $stmt->bindParam(':last_name', $formData['last_name']); @@ -83,6 +79,9 @@ class ContactModel $stmt->bindParam(':ip_address', $formData['ip_address']); $stmt->bindParam(':user_agent', $formData['user_agent']); + $newsletterOptIn = $formData['pending_newsletter_opt_in'] ?? 0; + $stmt->bindParam(':pending_newsletter_opt_in', $newsletterOptIn); + return $stmt->execute(); } catch (Exception $e) { Logger::error("ContactModel::saveContactForm failed: " . $e->getMessage()); diff --git a/app/Services/EmailService.php b/app/Services/EmailService.php new file mode 100644 index 0000000..4c1e8bb --- /dev/null +++ b/app/Services/EmailService.php @@ -0,0 +1,165 @@ +verificationService = new VerificationService(); + } + + /** + * Sends a verification email using the specified template and context. + * + * @param string $email Recipient email address + * @param string $code Verification code + * @param string $template Email template to render + * @param array $context Template variables to inject + * @return bool True on success, false on failure + */ + public function sendVerificationEmail(string $email, string $code, string $template, array $context = []): bool + { + $context['verification_link'] = rtrim($_ENV['APP_URL'], '/') . "/verify/" . $code; + $body = EmailHelper::renderTemplate($template, $context); + $subject = 'Please verify your email'; + + return EmailHelper::send($email, $subject, $body); + } + + /** + * Handles a new or existing newsletter subscription and sends a verification email. + * + * @param string $email User's email address + * @param string $ip User's IP address + * @param string $userAgent User agent string + * @return bool True if verification email sent, false otherwise + */ + public function subscribeNewsletter(string $email, string $ip, string $userAgent): bool + { + try { + $db = Database::getConnection(); + + $stmt = $db->prepare("SELECT is_verified FROM subscribers WHERE email = ?"); + $stmt->execute([$email]); + $row = $stmt->fetch(); + + if ($row && (int)$row['is_verified'] === 1) { + Logger::info("Newsletter signup skipped (already verified): $email"); + return false; + } + + $code = $this->verificationService->generateCode(); + $expiresAt = $this->verificationService->getExpirationTime(); + + if ($row) { + $stmt = $db->prepare("UPDATE subscribers SET verification_code = ?, ip_address = ?, user_agent = ?, created_at = NOW() WHERE email = ?"); + $stmt->execute([$code, $ip, $userAgent, $email]); + } else { + $stmt = $db->prepare("INSERT INTO subscribers (email, verification_code, is_verified, ip_address, user_agent, created_at) VALUES (?, ?, 0, ?, ?, NOW())"); + $stmt->execute([$email, $code, $ip, $userAgent]); + } + + Logger::info("Newsletter subscription initiated for $email, verification code generated."); + + return $this->sendVerificationEmail($email, $code, self::TEMPLATE_VERIFICATION_NEWSLETTER); + } catch (\Throwable $e) { + Logger::error("Newsletter subscription failed for $email: " . $e->getMessage()); + ErrorHandler::exception($e); + return false; + } + } + + /** + * Sends a confirmation email to a contact form submitter after successful verification. + * + * @param array $data Associative array containing user data and message details + * @return bool True on success, false on failure + */ + public function sendConfirmationToUser(array $data): bool + { + $body = EmailHelper::renderTemplate(self::TEMPLATE_CONFIRMATION_CONTACT, $data); + $subject = 'Your Email is Verified – Wizdom Networks'; + + return EmailHelper::send($data['email'], $subject, $body); + } + + /** + * Sends a notification to the internal sales team when a new contact form submission is received. + * + * @param array $data The contact form data + * @return bool True if at least one email sent, false otherwise + */ + public function sendSalesNotification(array $data): bool + { + $recipients = $_ENV['SALES_EMAILS'] ?? ''; + if (empty($recipients)) { + return false; + } + + $body = EmailHelper::renderTemplate(self::TEMPLATE_SALES_ALERT, $data); + $subject = 'New Contact Form Submission'; + + foreach (explode(',', $recipients) as $email) { + $trimmed = trim($email); + if (!empty($trimmed)) { + EmailHelper::send($trimmed, $subject, $body); + } + } + + return true; + } + + /** + * Sends a unified welcome email when a user both contacts us and subscribes to the newsletter. + * + * @param array $data Associative array containing contact form fields and metadata + * @return bool True on successful send, false otherwise + */ + public function sendContactAndNewsletterWelcome(array $data): bool + { + $body = EmailHelper::renderTemplate(self::TEMPLATE_CONTACT_NEWSLETTER, $data); + $subject = 'Thanks for reaching out – and welcome!'; + + return EmailHelper::send($data['email'], $subject, $body); + } + + /** + * Sends a system alert to configured admin recipients. + * + * @param string $context Description of the error context or origin + * @param string $errorMessage The error message or exception + * @param array|string $data Optional contextual data to include in the alert + * @return void + */ + public function alertAdmins(string $context, string $errorMessage, $data = []): void + { + EmailHelper::alertAdmins($context, $errorMessage, $data); + } +} diff --git a/app/Services/NewsletterService.php b/app/Services/NewsletterService.php index 4446bf7..952ad24 100644 --- a/app/Services/NewsletterService.php +++ b/app/Services/NewsletterService.php @@ -9,9 +9,9 @@ namespace WizdomNetworks\WizeWeb\Services; -use WizdomNetworks\WizeWeb\Utils\Logger; -use WizdomNetworks\WizeWeb\Utils\ErrorHandler; -use WizdomNetworks\WizeWeb\Utils\Database; +use WizdomNetworks\WizeWeb\Utilities\Logger; +use WizdomNetworks\WizeWeb\Utilities\ErrorHandler; +use WizdomNetworks\WizeWeb\Utilities\Database; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception as MailException; @@ -81,7 +81,7 @@ class NewsletterService private static function sendVerificationEmail(string $email, string $code): bool { try { - $verifyUrl = $_ENV['BASE_URL'] . "/verify?code=" . urlencode($code); + $verifyUrl = $_ENV['APP_URL'] . "/verify?code=" . urlencode($code); $mail = new PHPMailer(true); $mail->isSMTP(); diff --git a/app/Services/ResendVerificationService.php b/app/Services/ResendVerificationService.php new file mode 100644 index 0000000..ccafbb6 --- /dev/null +++ b/app/Services/ResendVerificationService.php @@ -0,0 +1,110 @@ +emailService = new EmailService(); + $this->verificationService = new VerificationService(); + } + + /** + * Attempts to resend a verification email for a given type and address. + * Performs rate limiting checks and logs the attempt if permitted. + * Generates and assigns a new verification code and triggers an email send. + * + * @param string $type Either 'contact' or 'newsletter' + * @param string $email Email address to resend to + * @return array ['success' => bool, 'message' => string] Outcome and message for user feedback + */ + public function attemptResend(string $type, string $email): array + { + try { + $db = Database::getConnection(); + + // Rate limit: no more than 3 per day + $stmt = $db->prepare("SELECT COUNT(*) FROM verification_attempts WHERE email = ? AND type = ? AND attempted_at >= NOW() - INTERVAL 1 DAY"); + $stmt->execute([$email, $type]); + if ((int)$stmt->fetchColumn() >= 3) { + return ['success' => false, 'message' => 'You have reached the daily resend limit. Please try again tomorrow.']; + } + + // Rate limit: no more than 1 every 5 minutes + $stmt = $db->prepare("SELECT COUNT(*) FROM verification_attempts WHERE email = ? AND type = ? AND attempted_at >= NOW() - INTERVAL 5 MINUTE"); + $stmt->execute([$email, $type]); + if ((int)$stmt->fetchColumn() > 0) { + return ['success' => false, 'message' => 'You must wait a few minutes before requesting another verification email.']; + } + + // Log attempt + $stmt = $db->prepare("INSERT INTO verification_attempts (email, type, attempted_at, ip_address, user_agent) VALUES (?, ?, NOW(), ?, ?)"); + $stmt->execute([ + $email, + $type, + $_SERVER['REMOTE_ADDR'] ?? 'unknown', + $_SERVER['HTTP_USER_AGENT'] ?? 'unknown', + ]); + + $code = $this->verificationService->generateCode(); + $expiry = $this->verificationService->getExpirationTime(); + + if ($type === 'newsletter') { + $stmt = $db->prepare("SELECT id, is_verified FROM subscribers WHERE email = ?"); + $stmt->execute([$email]); + $row = $stmt->fetch(); + + if (!$row || (int)$row['is_verified'] === 1) { + return ['success' => false, 'message' => 'Email is already verified or not found.']; + } + + $this->verificationService->assignCodeToRecord('subscribers', $row['id'], $code, $expiry); + $this->emailService->sendVerificationEmail($email, $code, 'verify_newsletter'); + } elseif ($type === 'contact') { + $stmt = $db->prepare("SELECT id, is_verified FROM contact_messages WHERE email = ? ORDER BY created_at DESC LIMIT 1"); + $stmt->execute([$email]); + $row = $stmt->fetch(); + + if (!$row || (int)$row['is_verified'] === 1) { + return ['success' => false, 'message' => 'Email is already verified or not found.']; + } + + $this->verificationService->assignCodeToRecord('contact_messages', $row['id'], $code, $expiry); + $this->emailService->sendVerificationEmail($email, $code, 'verify_contact'); + } else { + return ['success' => false, 'message' => 'Invalid verification type specified.']; + } + + return ['success' => true, 'message' => 'We just sent you a new verification link.']; + } catch (\Throwable $e) { + Logger::error("ResendVerificationService::attemptResend exception: " . $e->getMessage()); + return ['success' => false, 'message' => 'An unexpected error occurred.']; + } + } +} diff --git a/app/Services/VerificationService.php b/app/Services/VerificationService.php new file mode 100644 index 0000000..e3c803d --- /dev/null +++ b/app/Services/VerificationService.php @@ -0,0 +1,104 @@ +format('Y-m-d H:i:s'); + } + + /** + * Assigns a verification code to a contact or subscriber record. + * + * @param string $table Table name (e.g., 'subscribers', 'contact_messages') + * @param int $id Record ID + * @param string $code Verification code + * @param string $expiresAt Expiration timestamp + * @return bool True on success, false on failure + */ + public function assignCodeToRecord(string $table, int $id, string $code, string $expiresAt): bool + { + try { + $db = Database::getConnection(); + $stmt = $db->prepare("UPDATE {$table} SET verification_code = ?, is_verified = 0, verification_expires_at = ? WHERE id = ?"); + return $stmt->execute([$code, $expiresAt, $id]); + } catch (Exception $e) { + Logger::error("Failed to assign verification code to {$table} ID {$id}: " . $e->getMessage()); + ErrorHandler::exception($e); + return false; + } + } + + /** + * Deletes expired verification codes from a table. + * + * @param string $table Table name (e.g., 'subscribers', 'contact_messages') + * @return int Number of rows deleted + */ + public function deleteExpiredCodes(string $table): int + { + try { + $db = Database::getConnection(); + $stmt = $db->prepare("UPDATE {$table} SET verification_code = NULL WHERE verification_expires_at IS NOT NULL AND verification_expires_at < NOW()"); + $stmt->execute(); + return $stmt->rowCount(); + } catch (Exception $e) { + Logger::error("Failed to clear expired codes in {$table}: " . $e->getMessage()); + ErrorHandler::exception($e); + return 0; + } + } + + /** + * Removes the verification code from a specific record. + * + * @param string $table Table name + * @param int $id Record ID + * @return bool + */ + public function clearCode(string $table, int $id): bool + { + try { + $db = Database::getConnection(); + $stmt = $db->prepare("UPDATE {$table} SET verification_code = NULL WHERE id = ?"); + return $stmt->execute([$id]); + } catch (Exception $e) { + Logger::error("Failed to clear verification code for {$table} ID {$id}: " . $e->getMessage()); + ErrorHandler::exception($e); + return false; + } + } +} diff --git a/app/Utilities/EmailHelper.php b/app/Utilities/EmailHelper.php index 7ec3b39..62f4a3b 100644 --- a/app/Utilities/EmailHelper.php +++ b/app/Utilities/EmailHelper.php @@ -1,9 +1,9 @@ isSMTP(); @@ -48,6 +54,12 @@ class EmailHelper ]; } + /** + * Parses a comma-separated list of emails and returns an array of valid addresses. + * + * @param string $rawList + * @return array + */ private static function parseRecipients(string $rawList): array { $emails = explode(',', $rawList); @@ -63,173 +75,83 @@ class EmailHelper return $validEmails; } + /** + * Sends a basic HTML email. + * + * @param string $to + * @param string $subject + * @param string $body + * @return bool + */ public static function send(string $to, string $subject, string $body): bool -{ - try { - $mail = self::getMailer(); - $mail->addAddress($to); - $mail->Subject = $subject; - $mail->Body = $body; - $mail->isHTML(true); - - return $mail->send(); - } catch (\Throwable $e) { - Logger::error("Email send failed to $to: " . $e->getMessage()); - return false; - } -} - - - private static function buildContactHtmlBody(array $data): string - { - return " - Name: {$data['first_name']} {$data['last_name']}
- Email: {$data['email']}
- Phone: {$data['phone']}
- Subject: {$data['subject']}
- Message:
-
{$data['message']}
- "; - } - - private static function buildErrorReportHtml(string $context, string $errorMessage, $data = []): string - { - if (is_string($data)) { - $decoded = json_decode($data, true); - $data = is_array($decoded) ? $decoded : ['raw_data' => $data]; - } - - $body = " - Context: {$context}
- Error Message:
-
{$errorMessage}
- "; - - if (!empty($data)) { - $body .= "
Associated Data:
"; - } - - return $body; - } - - private static function buildSalesHtmlBody(array $data): string - { - $submittedAt = date('Y-m-d H:i:s'); - return " -

New contact submission received on {$submittedAt}.

- -
-

IP: {$data['ip_address']}
User-Agent: {$data['user_agent']}

- "; - } - - private static function buildConfirmationHtmlBody(array $data): string - { - $submittedAt = date('Y-m-d H:i:s'); - return " -

Hi {$data['first_name']},

-

Thank you for contacting Wizdom Networks. This message confirms that we received your inquiry on {$submittedAt}.

- -

We’ll be in touch shortly. If it’s urgent, call us at 416-USE-WISE.

-

IP: {$data['ip_address']}
User-Agent: {$data['user_agent']}

- "; - } - - public static function sendContactNotification(array $data): bool { try { $mail = new PHPMailer(true); self::configureMailer($mail); - - $recipients = self::parseRecipients($_ENV['SALES_EMAILS'] ?? ''); - foreach ($recipients as $email) { - $mail->addAddress($email); - } - - if (empty($mail->getToAddresses())) { - Logger::error("EmailHelper: No valid SALES_EMAILS configured."); - return false; - } - + $mail->addAddress($to); + $mail->Subject = $subject; + $mail->Body = $body; $mail->isHTML(true); - $mail->Subject = 'New Contact Form Submission'; - $mail->Body = self::buildSalesHtmlBody($data); $mail->send(); + Logger::info("Email sent successfully to $to with subject: $subject"); return true; - } catch (Exception $e) { - Logger::error("Email send failure to sales: " . $e->getMessage()); - self::alertAdmins("sendContactNotification", $e->getMessage(), $data); - return false; - } - } - - public static function sendConfirmationToUser(array $data): bool - { - try { - $mail = new PHPMailer(true); - self::configureMailer($mail); - - $mail->addAddress($data['email'], "{$data['first_name']} {$data['last_name']}"); - $mail->isHTML(true); - $mail->Subject = 'Your Wizdom Networks Contact Form Submission'; - $mail->Body = self::buildConfirmationHtmlBody($data); - - $mail->send(); - return true; - } catch (Exception $e) { - Logger::error("Email send failure to user: " . $e->getMessage()); - self::alertAdmins("sendConfirmationToUser", $e->getMessage(), $data); + } catch (\Throwable $e) { + Logger::error("Email send failed to $to: " . $e->getMessage()); return false; } } /** - * Sends a system alert to ADMIN_EMAILS with error context and message. - * - * @param string $context - * @param string $errorMessage - * @param array|string $data Data array or JSON string for the report - * @return void - */ -public static function alertAdmins(string $context, string $errorMessage, $data = []): void + * Sends a system-level alert email to admins using an HTML template. + * + * @param string $context + * @param string $errorMessage + * @param array|string $data + * @return void + */ + public static function alertAdmins(string $context, string $errorMessage, $data = []): void + { + $recipients = self::parseRecipients($_ENV['ADMIN_EMAILS'] ?? ''); + if (empty($recipients)) { + Logger::error("EmailHelper: No valid ADMIN_EMAILS configured."); + return; + } + + $htmlBody = self::renderTemplate('system_alert', [ + 'context' => $context, + 'errorMessage' => $errorMessage, + 'data' => $data + ]); + + foreach ($recipients as $email) { + self::send($email, "[System Alert] Error in {$context}", $htmlBody); + } + } + + /** + * Renders an email template with dynamic variables. + * + * @param string $templateName + * @param array $vars + * @return string + */ + public static function renderTemplate(string $templateName, array $vars = []): string { try { - $mail = new PHPMailer(true); - self::configureMailer($mail); - - $recipients = self::parseRecipients($_ENV['ADMIN_EMAILS'] ?? ''); - foreach ($recipients as $email) { - $mail->addAddress($email); + $templatePath = __DIR__ . '/../../resources/views/emails/' . $templateName . '.php'; + if (!file_exists($templatePath)) { + throw new \Exception("Template not found: $templateName"); } - if (empty($mail->getToAddresses())) { - Logger::error("EmailHelper: No valid ADMIN_EMAILS configured."); - return; - } - - $mail->isHTML(true); - $mail->Subject = "[System Alert] Error in {$context}"; - $mail->Body = self::buildErrorReportHtml($context, $errorMessage, $data); - - $mail->send(); - } catch (Exception $e) { - Logger::error("EmailHelper::alertAdmins failed: " . $e->getMessage()); + extract($vars); + ob_start(); + include $templatePath; + return ob_get_clean(); + } catch (\Throwable $e) { + Logger::error("Failed to render email template: $templateName - " . $e->getMessage()); + ErrorHandler::exception($e); + return ''; } } } diff --git a/app/Utilities/EmailUtility.php b/app/Utilities/EmailUtility.php deleted file mode 100644 index cc42b91..0000000 --- a/app/Utilities/EmailUtility.php +++ /dev/null @@ -1,393 +0,0 @@ - $_ENV['SMTP_HOST'] ?? 'localhost', - 'port' => $_ENV['SMTP_PORT'] ?? '25', - 'from_email' => $_ENV['SMTP_FROM_EMAIL'] ?? 'concierge@helpdeskplus.ca', - 'from_name' => $_ENV['SMTP_FROM_NAME'] ?? 'HelpDesk+', - 'auth' => false - ]; - - foreach (['host', 'port', 'from_email'] as $field) { - if (empty($config[$field])) { - Logger::logError("Missing email configuration: $field"); - throw new \RuntimeException("Missing email configuration: $field"); - } - } - } - else { - - $config = [ - 'host' => $_ENV['SMTP_HOST'] ?? 'localhost', - 'username' => $_ENV['SMTP_USERNAME'] ?? null, - 'password' => $_ENV['SMTP_PASSWORD'] ?? null, - 'port' => $_ENV['SMTP_PORT'] ?? '25', - 'encryption' => $_ENV['SMTP_ENCRYPTION'] ?? 'none', - 'from_email' => $_ENV['SMTP_FROM_EMAIL'] ?? 'concierge@helpdeskplus.ca', - 'from_name' => $_ENV['SMTP_FROM_NAME'] ?? 'HelpDesk+', - 'smtpsecure' => $_ENV['SMTP_AUTH'], - 'auth' => true, - 'autotls' => true - ]; - - foreach (['host', 'port', 'username', 'password', 'from_email'] as $field) { - if (empty($config[$field])) { - Logger::logError("Missing email configuration: $field"); - throw new \RuntimeException("Missing email configuration: $field"); - } - } - } - return $config; - } - - /** - * Render an email template with dynamic data. - * - * @param string $templatePath The path to the email template file. - * @param array $data Key-value pairs for template placeholders. - * @return string The rendered email content. - */ - public static function renderTemplate(string $templatePath, array $data): string - { - if (!file_exists($templatePath)) { - Logger::logError("Email template not found: $templatePath"); - return ''; - } - - $content = file_get_contents($templatePath); - foreach ($data as $key => $value) { - $content = str_replace("{{{$key}}}", $value, $content); - } - - return $content; - } - - /** - * Log email status into the database. - * - * @param string $recipient The recipient email address. - * @param string $status The status of the email (e.g., 'queued', 'sent', 'failed'). - * @param string|null $errorMessage An optional error message. - * @return void - */ - private static function logEmailStatus(string $recipient, string $status, ?string $errorMessage = null): void - { - try { - $db = Database::getInstance(); - $query = "INSERT INTO email_status (recipient, status, error_message, created_at) VALUES (:recipient, :status, :error_message, NOW())"; - $params = [ - ':recipient' => $recipient, - ':status' => $status, - ':error_message' => $errorMessage, - ]; - $db->executeQuery($query, $params); - } catch (\Throwable $e) { - Logger::logError("Failed to log email status: " . $e->getMessage()); - } - } - - /** - * Notify admin or sales team via email. - * - * @param string $emailType The type of notification (e.g., 'admin', 'sales'). - * @param string $subject The email subject. - * @param string $templatePath Path to the notification template. - * @param array $templateData Data for the template placeholders. - * @return void - */ - public static function notifyTeam(string $emailType, string $subject, string $templatePath, array $templateData): void - { - $recipients = $emailType === 'admin' ? explode(',', $_ENV['ADMIN_EMAILS']) : explode(',', $_ENV['SALES_EMAILS']); - - foreach ($recipients as $recipient) { - $recipient = trim($recipient); - if (!self::sendEmail($recipient, $subject, $templatePath, $templateData)) { - Logger::logError("Failed to send $emailType notification to: $recipient"); - } - } - } - - /** - * Send an email with enhanced error categorization. - * - * @param string $recipient Recipient email address. - * @param string $subject Email subject. - * @param string $templatePath Path to the email template. - * @param array $templateData Data to replace placeholders in the template. - * @param array $options Optional configurations (e.g., CC, BCC). - * @param int $retryLimit The maximum number of retries for transient failures. - * @return bool Returns true on success, false otherwise. - */ - public static function sendEmail(string $recipient, string $subject, string $templatePath, array $templateData = [], array $options = [], int $retryLimit = 3): bool - { - $mail = new PHPMailer(true); - $config = self::getConfig(); - $retryCount = 0; - - while ($retryCount <= $retryLimit) { - try { - - $mail->isSMTP(); - $mail->SMTPAutoTLS = false; - $mail->SMTPAuth = false; - /* If authentication is enabled setup the connection */ - if ( $config['auth'] === 'true' ){ - $mail->SMTPAuth = $config['auth']; - $mail->Username = $config['username']; - $mail->Password = $config['password']; - $mail->SMTPSecure = $config['encryption']; - $mail->SMTPAutoTLS = $config['autotls']; - } - - $mail->Host = $config['host']; - $mail->Port = $config['port']; -/****************************** - $mail->SMTPDebug = $_ENV['APP_ENV'] === 'development' ? 2 : 0; - /$mail->Debugoutput = function ($message, $level) { - Logger::logInfo("SMTP Debug [$level]: $message"); - }; -*******************************/ - $mail->setFrom($config['from_email'], $config['from_name']); - $mail->addAddress($recipient); - - if (!empty($options['cc'])) { - foreach ((array)$options['cc'] as $cc) { - $mail->addCC($cc); - } - } - - if (!empty($options['bcc'])) { - foreach ((array)$options['bcc'] as $bcc) { - $mail->addBCC($bcc); - } - } - - $mail->isHTML(true); - $mail->Subject = $subject; - $mail->Body = self::renderTemplate($templatePath, $templateData); - - $mail->send(); - Logger::logInfo("Email sent to $recipient with subject: $subject"); - self::logEmailStatus($recipient, 'sent'); - return true; - } catch (Exception $e) { - $retryCount++; - $error = $mail->ErrorInfo; - Logger::logWarning("Email send failed for $recipient (Attempt $retryCount/$retryLimit): $error"); - - if (str_contains($error, '452 4.3.1')) { - Logger::logWarning("Transient error detected for $recipient: $error"); - } elseif (str_contains($error, '550')) { - Logger::logError("Permanent error detected for $recipient: $error"); - self::logEmailStatus($recipient, 'failed', $error); - return false; - } elseif (str_contains($error, '421')) { - Logger::logWarning("Rate-limiting error detected for $recipient: $error"); - } else { - Logger::logError("Unhandled SMTP error for $recipient: $error"); - } - - if (str_contains($error, '452') || str_contains($error, '421')) { - if ($retryCount > $retryLimit) { - Logger::logError("Exceeded retry limit for email to $recipient: $error"); - self::logEmailStatus($recipient, 'failed', $error); - return false; - } - - sleep(5); - continue; - } - - Logger::logError("Email permanently failed for $recipient: $error"); - self::logEmailStatus($recipient, 'failed', $error); - return false; - } - } - - return false; - } - - /** - * Process the email queue and send emails in batches. - * - * @param int $batchSize Number of emails to process in a single batch. - * @param int $maxRetries Maximum retry attempts for failed emails. - * @return void - */ - public static function processEmailQueue(int $batchSize = 10, int $maxRetries = 3): void - { - for ($i = 0; $i < $batchSize; $i++) { - $emailData = self::$queueUtility->dequeue('email'); - - if ($emailData === null) { - Logger::logInfo("No more emails to process in the queue."); - break; - } - - $success = self::sendEmail( - $emailData['recipient'], - $emailData['subject'], - $emailData['templatePath'], - $emailData['templateData'], - $emailData['options'] - ); - - if (!$success) { - $retries = $emailData['retries'] ?? 0; - - if ($retries < $maxRetries) { - $priority = $emailData['priority'] ?? 0; - $emailData['retries'] = $retries + 1; - self::$queueUtility->enqueue('email', $emailData, $priority); - Logger::logWarning( - "Email re-queued for recipient: {$emailData['recipient']} (Attempt {$emailData['retries']})" - ); - } else { - Logger::logError("Email permanently failed for recipient: {$emailData['recipient']}"); - self::logEmailStatus($emailData['recipient'], 'failed', 'Max retry limit reached.'); - } - } else { - self::logEmailStatus($emailData['recipient'], 'sent'); - } - } - - Logger::logInfo("Email queue processing completed."); - } - - /** - * Process contact-related email queue. - * - * @param int $batchSize Number of emails to process in a single batch. - * @param int $maxRetries Maximum retry attempts for failed emails. - * @return void - */ -public static function processContactQueue(int $batchSize = 10, int $maxRetries = 3): void -{ - Logger::logInfo("Processing contact email queue..."); - - for ($i = 0; $i < $batchSize; $i++) { - $emailData = self::$queueUtility->dequeue('contact_email'); - - if ($emailData === null) { - Logger::logInfo("No more emails to process in the contact queue."); - break; - } - - $success = self::sendEmail( - $emailData['recipient'], - $emailData['subject'], - $emailData['templatePath'], - $emailData['templateData'], - $emailData['options'] - ); - - if (!$success) { - $retries = $emailData['retries'] ?? 0; - - if ($retries < $maxRetries) { - $priority = $emailData['priority'] ?? 0; - $emailData['retries'] = $retries + 1; - self::$queueUtility->enqueue('contact_email', $emailData, $priority); - Logger::logWarning( - "Contact email re-queued for recipient: {$emailData['recipient']} (Attempt {$emailData['retries']})" - ); - } else { - Logger::logError("Contact email permanently failed for recipient: {$emailData['recipient']}"); - self::logEmailStatus($emailData['recipient'], 'failed', 'Max retry limit reached.'); - } - } else { - self::logEmailStatus($emailData['recipient'], 'sent'); - } - } - - Logger::logInfo("Contact email queue processing completed."); -} - -/** - * Retrieve the status of a specific email by recipient. - * - * @param string $recipient Email address of the recipient. - * @return array|null The email status or null if not found. - */ -public static function getEmailStatus(string $recipient): ?array -{ - try { - $db = Database::getInstance(); - $query = "SELECT * FROM email_status WHERE recipient = :recipient ORDER BY created_at DESC LIMIT 1"; - $params = [':recipient' => $recipient]; - return $db->fetchOne($query, $params); - } catch (\Throwable $e) { - Logger::logError("Failed to retrieve email status for $recipient: " . $e->getMessage()); - return null; - } -} - -/** - * Clear the email queue. - * - * @param string $queueName The name of the queue to clear (default: 'email'). - * @return void - */ -public static function clearQueue(string $queueName = 'email'): void -{ - Logger::logInfo("Clearing queue: $queueName"); - - try { - self::$queueUtility->clearQueue($queueName); - Logger::logInfo("Queue $queueName cleared successfully."); - } catch (\Throwable $e) { - Logger::logError("Failed to clear queue $queueName: " . $e->getMessage()); - } -} - -/** - * List all queued emails in a specific queue. - * - * @param string $queueName The name of the queue to inspect (default: 'email'). - * @return array List of queued emails. - */ -public static function listQueuedEmails(string $queueName = 'email'): array -{ - try { - Logger::logInfo("Listing emails in queue: $queueName"); - return self::$queueUtility->listQueue($queueName); - } catch (\Throwable $e) { - Logger::logError("Failed to list emails in queue $queueName: " . $e->getMessage()); - return []; - } -} -} diff --git a/app/Utilities/SubmissionCheck.php b/app/Utilities/SubmissionCheck.php index 9e03213..6676f95 100644 --- a/app/Utilities/SubmissionCheck.php +++ b/app/Utilities/SubmissionCheck.php @@ -1,7 +1,7 @@ = NOW() - INTERVAL :days DAY) AS email_hits, - (SELECT COUNT(*) FROM submission_logs WHERE phone = :phone AND created_at >= NOW() - INTERVAL :days DAY) AS phone_hits, - (SELECT COUNT(*) FROM submission_logs WHERE ip_address = :ip AND created_at >= NOW() - INTERVAL :days DAY) AS ip_hits, - (SELECT COUNT(*) FROM submission_logs WHERE ip_address = :ip AND created_at >= NOW() - INTERVAL 1 HOUR) AS ip_hourly + (SELECT COUNT(*) FROM submission_logs WHERE email = :email AND created_at >= NOW() - INTERVAL $lookback DAY) AS email_hits, + (SELECT COUNT(*) FROM submission_logs WHERE phone = :phone AND created_at >= NOW() - INTERVAL $lookback DAY) AS phone_hits, + (SELECT COUNT(*) FROM submission_logs WHERE ip_address = :ip1 AND created_at >= NOW() - INTERVAL $lookback DAY) AS ip_hits, + (SELECT COUNT(*) FROM submission_logs WHERE ip_address = :ip2 AND created_at >= NOW() - INTERVAL 1 HOUR) AS ip_hourly "; $stmt = $pdo->prepare($query); $stmt->bindValue(':email', $email); $stmt->bindValue(':phone', $phone); - $stmt->bindValue(':ip', $ip); - $stmt->bindValue(':days', self::LOOKBACK_DAYS, PDO::PARAM_INT); + $stmt->bindValue(':ip1', $ip); + $stmt->bindValue(':ip2', $ip); $stmt->execute(); $data = $stmt->fetch(PDO::FETCH_ASSOC); diff --git a/public/assets/img/wizdom-networks-logo-v2-no-slogan-blue-1119x303.png b/public/assets/img/wizdom-networks-logo-v2-no-slogan-blue-1119x303.png new file mode 100644 index 0000000000000000000000000000000000000000..dd6fe799f8a9068ff6ce0f5c7decf351eb0e844a GIT binary patch literal 38936 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 F{{xUFlu`fy literal 0 HcmV?d00001 diff --git a/public/assets/img/wizdom-networks-logo-v2.png b/public/assets/img/wizdom-networks-logo-v2.png new file mode 100644 index 0000000000000000000000000000000000000000..fef9f9733b8da08949ed1cd6e7113a58cad628c2 GIT binary patch literal 51568 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 literal 0 HcmV?d00001 diff --git a/public/assets/js/contact-form.js b/public/assets/js/contact-form.js index fffed69..7b15465 100644 --- a/public/assets/js/contact-form.js +++ b/public/assets/js/contact-form.js @@ -1,5 +1,5 @@ // File: public/assets/js/contact-form.js -// Version: 1.2 +// Version: 1.3 // Purpose: Handles JS-based form submission with feedback and duplicate prevention @@ -48,9 +48,11 @@ document.addEventListener('DOMContentLoaded', function () { try { const response = await fetch(form.action, { - method: 'POST', - body: formData - }); + method: 'POST', + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + body: formData + }); + const result = await response.json(); loading.style.display = 'none'; diff --git a/public/index.php b/public/index.php index ab08efe..86acea3 100644 --- a/public/index.php +++ b/public/index.php @@ -3,7 +3,7 @@ /** * ============================================ * File: public/index.php - * Version: v1.7 + * Version: v1.8 * Purpose: Application entry point for Arsha one-pager with dynamic & closure route support * Project: Wizdom Networks Website * ============================================ @@ -61,7 +61,7 @@ $router->add('/contact', ContactController::class, 'submit', 'POST'); // Verification Routes $router->add('/verify', VerificationController::class, 'verify', 'GET'); // e.g. /verify?email=... -$router->add('/verify/{code}', ContactController::class, 'verify', 'GET'); // e.g. /verify/abc123... +$router->add('/verify/{code}', VerificationController::class, 'verify', 'GET'); // e.g. /verify/abc123... // Resend / Newsletter / Unsubscribe $router->add('/resend-verification', ResendVerificationController::class, 'handle', 'POST'); diff --git a/resources/templates/emails/contact_confirmation_email.html b/resources/templates/emails/contact_confirmation_email.html deleted file mode 100644 index dcf074a..0000000 --- a/resources/templates/emails/contact_confirmation_email.html +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - Email from HelpDesk+ - - - - - - diff --git a/resources/views/emails/admin_contact_alert.php b/resources/views/emails/admin_contact_alert.php new file mode 100644 index 0000000..3c62596 --- /dev/null +++ b/resources/views/emails/admin_contact_alert.php @@ -0,0 +1,22 @@ + + + +

New Contact Form Submission Received

+ +

Name:
+Email:
+IP Address:
+User Agent:

+ +

Message:

+ + + + diff --git a/resources/views/emails/contact_and_newsletter.php b/resources/views/emails/contact_and_newsletter.php new file mode 100644 index 0000000..29d1e55 --- /dev/null +++ b/resources/views/emails/contact_and_newsletter.php @@ -0,0 +1,61 @@ + + + + + + + + Thanks for Contacting Us & Welcome + + + +
+

Thanks for reaching out!

+ +

We've received your message and appreciate you taking the time to contact Wizdom Networks. A member of our team will follow up shortly.

+ +

Here’s what you submitted:

+
    +
  • Subject:
  • +
  • Message:
  • +
+ +

You also signed up for our newsletter — welcome aboard! We’ll occasionally send you updates, tips, and insights straight from our team.

+ +

What to expect:

+
    +
  • ✅ A response to your inquiry
  • +
  • ✅ Occasional email updates and insights
  • +
  • ✅ You can unsubscribe at any time
  • +
+ + +
+ + diff --git a/resources/views/emails/newsletter_welcome.php b/resources/views/emails/newsletter_welcome.php new file mode 100644 index 0000000..3296d0f --- /dev/null +++ b/resources/views/emails/newsletter_welcome.php @@ -0,0 +1,71 @@ + + + + + + + Welcome to Wizdom Networks + + + +
+

Welcome to Wizdom Networks!

+ +

Thanks for subscribing to the Wizdom Networks newsletter. We're glad to have you with us.

+ +

You can expect expert tips, behind-the-scenes insights, and smart strategies that help make technology work for your business—not the other way around.

+ +

If you ever want to update your preferences or tell us more about yourself, just click below.

+ + Update Preferences + + +
+ + diff --git a/resources/views/emails/resend_verification.php b/resources/views/emails/resend_verification.php new file mode 100644 index 0000000..ad7f8c6 --- /dev/null +++ b/resources/views/emails/resend_verification.php @@ -0,0 +1,24 @@ + + + + +

Hello ,

+ +

You recently requested to re-send your email verification link. Please click below to verify your email address:

+ +

Verify My Email

+ +

If you didn't request this, no further action is needed.

+ +

— The Wizdom Networks Team

+ + + + diff --git a/resources/views/emails/sales_lead_alert.php b/resources/views/emails/sales_lead_alert.php new file mode 100644 index 0000000..76be271 --- /dev/null +++ b/resources/views/emails/sales_lead_alert.php @@ -0,0 +1,21 @@ + + + +

New Sales Lead Submitted

+ +

Name:
+Email:
+Phone:

+ +

Message:

+ + + + \ No newline at end of file diff --git a/resources/views/emails/system_alert.php b/resources/views/emails/system_alert.php new file mode 100644 index 0000000..2ea9484 --- /dev/null +++ b/resources/views/emails/system_alert.php @@ -0,0 +1,57 @@ + + + + + + + + System Alert + + + +
+

🚨 System Alert:

+ +

Error Message:

+
+ + +

Contextual Data:

+
    + + $value): ?> +
  • :
  • + + +
  • + +
+ + + +
+ + diff --git a/resources/views/emails/verified_confirmation.php b/resources/views/emails/verified_confirmation.php new file mode 100644 index 0000000..2a8b2dc --- /dev/null +++ b/resources/views/emails/verified_confirmation.php @@ -0,0 +1,28 @@ + + + +

Hello ,

+ +

Your email has been successfully verified. Thank you for connecting with Wizdom Networks.

+ +

Here's a summary of your submission:

+
    +
  • Name:
  • +
  • Email:
  • +
  • Message:
  • +
+ +

We’ll be in touch soon if your message requires a response. In the meantime, feel free to reach out at 416-USE-WISE if you need immediate assistance.

+ +

— The Wizdom Networks Team

+ + + + diff --git a/resources/views/emails/verify_contact.php b/resources/views/emails/verify_contact.php new file mode 100644 index 0000000..38a5cd5 --- /dev/null +++ b/resources/views/emails/verify_contact.php @@ -0,0 +1,41 @@ + + + + + + + + Verify Your Email + + + +
+

Hi ,

+ +

Thanks for reaching out to Wizdom Networks. To ensure we received your inquiry and can respond to you promptly, please confirm your email address below:

+ +

Verify My Email

+ + +
+ + \ No newline at end of file diff --git a/resources/views/emails/verify_email.php b/resources/views/emails/verify_email.php new file mode 100644 index 0000000..36c6a14 --- /dev/null +++ b/resources/views/emails/verify_email.php @@ -0,0 +1,26 @@ + + + +

Hello ,

+ +

Thank you for connecting with Wizdom Networks. Please confirm your email by clicking the link below:

+ +

Verify My Email

+ +

This link will expire in 48 hours.

+ +

If you didn’t request this, please ignore this email.

+ +

— The Wizdom Networks Team

+ + + + diff --git a/resources/views/emails/verify_newsletter.php b/resources/views/emails/verify_newsletter.php new file mode 100644 index 0000000..c83ef5b --- /dev/null +++ b/resources/views/emails/verify_newsletter.php @@ -0,0 +1,43 @@ + + + + + + + + Confirm Your Subscription + + + +
+

Welcome to Wizdom!

+ +

You're almost done! Please confirm your subscription to the Wizdom Networks newsletter by verifying your email address.

+ +

Confirm My Subscription

+ + +
+ + diff --git a/resources/views/layouts/arsha.php b/resources/views/layouts/arsha.php index eb269c7..7c14f82 100644 --- a/resources/views/layouts/arsha.php +++ b/resources/views/layouts/arsha.php @@ -37,9 +37,11 @@ + + - diff --git a/resources/views/layouts/email_layout.php b/resources/views/layouts/email_layout.php new file mode 100644 index 0000000..4c19afc --- /dev/null +++ b/resources/views/layouts/email_layout.php @@ -0,0 +1,40 @@ + + + + + + + <?= htmlspecialchars($subject ?? 'Wizdom Networks') ?> + + + + + + diff --git a/resources/views/layouts/footer.php b/resources/views/layouts/footer.php index 978b7cb..26618a2 100644 --- a/resources/views/layouts/footer.php +++ b/resources/views/layouts/footer.php @@ -28,7 +28,7 @@ + $html]); +?> diff --git a/resources/views/pages/verify_success.php b/resources/views/pages/verify_success.php index 6a29d80..b7fbdd6 100644 --- a/resources/views/pages/verify_success.php +++ b/resources/views/pages/verify_success.php @@ -2,7 +2,12 @@ // File: verify_success.php // Version: 1.2 // Purpose: Displays confirmation message after email verification and optional personalization update. + +use WizdomNetworks\WizeWeb\Core\View; + +ob_start(); ?> +
@@ -51,3 +56,7 @@
+ $html]); +?> \ No newline at end of file diff --git a/resources/views/partials/contact.php b/resources/views/partials/contact.php index 02b6099..cd75cc5 100644 --- a/resources/views/partials/contact.php +++ b/resources/views/partials/contact.php @@ -1,7 +1,7 @@
-
+
@@ -102,7 +102,7 @@
- + diff --git a/scripts/empty_database.sql b/scripts/empty_database.sql new file mode 100644 index 0000000..9b1e63e --- /dev/null +++ b/scripts/empty_database.sql @@ -0,0 +1,4 @@ +delete from verification_attempts; +delete from subscribers; +delete from contact_messages; +delete from submission_logs; \ No newline at end of file