WizdomWeb/app/Controllers/ContactController.php

187 lines
7.8 KiB
PHP

<?php
/**
* File: ContactController.php
* Version: 2.16
* Path: /app/Controllers/ContactController.php
* Purpose: Handles contact form submission, abuse checks, and verification logic.
* Defers contact/sales email sending until verification.
* Tracks newsletter opt-in flag for unified post-verification messaging.
* Project: Wizdom Networks Website
*/
namespace WizdomNetworks\WizeWeb\Controllers;
use WizdomNetworks\WizeWeb\Core\View;
use WizdomNetworks\WizeWeb\Utilities\Logger;
use WizdomNetworks\WizeWeb\Utilities\Validator;
use WizdomNetworks\WizeWeb\Utilities\Sanitizer;
use WizdomNetworks\WizeWeb\Utilities\Database;
use WizdomNetworks\WizeWeb\Utilities\SessionHelper;
use WizdomNetworks\WizeWeb\Utilities\SubmissionCheck;
use WizdomNetworks\WizeWeb\Utilities\ErrorHandler;
use WizdomNetworks\WizeWeb\Utilities\Response;
use WizdomNetworks\WizeWeb\Services\EmailService;
use WizdomNetworks\WizeWeb\Services\VerificationService;
use WizdomNetworks\WizeWeb\Models\ContactModel;
use Exception;
class ContactController
{
private EmailService $emailService;
private VerificationService $verificationService;
/**
* Initializes email and verification service dependencies.
*/
public function __construct()
{
$this->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");
try {
$formData = [
'first_name' => Sanitizer::sanitizeString($_POST['first_name'] ?? ''),
'last_name' => Sanitizer::sanitizeString($_POST['last_name'] ?? ''),
'email' => Sanitizer::sanitizeString($_POST['email'] ?? ''),
'phone' => Sanitizer::sanitizeString($_POST['phone'] ?? ''),
'subject' => Sanitizer::sanitizeString($_POST['subject'] ?? ''),
'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']) ||
empty($formData['email']) ||
empty($formData['phone']) ||
empty($formData['subject']) ||
empty($formData['message']) ||
!Validator::isEmail($formData['email'])
) {
Logger::info("Validation failed for contact form submission");
$_SESSION['contact_error'] = 'Validation error. Please try again.';
SessionHelper::writeClose();
$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']}");
$this->emailService->alertAdmins('Blocked Submission Detected', $evaluation['reason'], $formData);
SessionHelper::writeClose();
$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)");
$logStmt->execute([
':email' => $formData['email'],
':phone' => $formData['phone'],
':ip' => $formData['ip_address'],
':ua' => $formData['user_agent'],
':saved' => 0,
':reason' => $evaluation['reason'],
]);
$logId = $db->lastInsertId();
} catch (\Throwable $e) {
Logger::error("Failed to insert into submission_logs: " . $e->getMessage());
}
// Save form content
$contactModel = new ContactModel($db);
$saveSuccess = $contactModel->saveContactForm($formData);
$contactId = $db->lastInsertId();
// Assign verification code
if ($saveSuccess) {
$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]);
}
SessionHelper::writeClose();
$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());
$this->emailService->alertAdmins('ContactController::submit - Uncaught Exception', $e->getMessage(), $_POST ?? []);
$_SESSION['contact_error'] = 'An internal error occurred. Please try again later.';
SessionHelper::writeClose();
$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;
}
}