feat: Implement email verification and refactor form logic (partial)
- Added ContactService.php and NewsletterService.php for handling verification emails - Introduced POST-based verification and session-driven success rendering - Replaced redirect-based confirmation with inline JS and conditional rendering - Reorganized routes in index.php for controller and closure-based actions - Minor JS improvements to prevent double submission (contact-form.js) - Honeypot field temporarily missing despite prior implementation and HoneypotHelper - SubmissionCheck needs fix for SQL parameter mismatch - EmailHelper missing getMailer() (referenced in services) - General structure drift noted — inventory and cleanup pending
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
/**
|
||||
* File: ContactController.php
|
||||
* Version: 2.0
|
||||
* Version: 2.8
|
||||
* Path: /app/Controllers/ContactController.php
|
||||
* Purpose: Handles contact form submission with error alerts to admins and success to sales.
|
||||
* Purpose: Handles contact form submission and verification, including expiration enforcement.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
|
|
@ -15,7 +15,10 @@ use WizdomNetworks\WizeWeb\Utilities\Validator;
|
|||
use WizdomNetworks\WizeWeb\Utilities\Sanitizer;
|
||||
use WizdomNetworks\WizeWeb\Utilities\Database;
|
||||
use WizdomNetworks\WizeWeb\Utilities\EmailHelper;
|
||||
use WizdomNetworks\WizeWeb\Utilities\SessionHelper;
|
||||
use WizdomNetworks\WizeWeb\Utilities\SubmissionCheck;
|
||||
use WizdomNetworks\WizeWeb\Models\ContactModel;
|
||||
use WizdomNetworks\WizeWeb\Services\ContactService;
|
||||
use Exception;
|
||||
|
||||
class ContactController
|
||||
|
|
@ -26,89 +29,111 @@ class ContactController
|
|||
}
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
Logger::info("Executing controller: ContactController::submit");
|
||||
Logger::info("📦 PHP Session ID: " . session_id());
|
||||
{
|
||||
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',
|
||||
];
|
||||
|
||||
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',
|
||||
];
|
||||
foreach ($formData as $key => $value) {
|
||||
Logger::info("Sanitized input: {$key} = {$value}");
|
||||
}
|
||||
|
||||
foreach ($formData as $key => $value) {
|
||||
Logger::info("Sanitized input: {$key} = {$value}");
|
||||
}
|
||||
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'] = 'An internal error occurred. Please try again later.';
|
||||
SessionHelper::writeClose();
|
||||
header("Location: /#contact");
|
||||
exit;
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
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");
|
||||
$db = Database::getConnection();
|
||||
|
||||
$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);
|
||||
SessionHelper::writeClose();
|
||||
header("Location: /#contact");
|
||||
exit;
|
||||
}
|
||||
|
||||
$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());
|
||||
}
|
||||
|
||||
$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');
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Fatal error in ContactController::submit: " . $e->getMessage());
|
||||
EmailHelper::alertAdmins('ContactController::submit - Uncaught Exception', $e->getMessage(), $_POST ?? []);
|
||||
$_SESSION['contact_error'] = 'An internal error occurred. Please try again later.';
|
||||
|
||||
header("Location: /?contact_error=1#contact");
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
|
||||
// Save to DB
|
||||
$db = Database::getConnection();
|
||||
$contactModel = new ContactModel($db);
|
||||
$saveSuccess = $contactModel->saveContactForm($formData);
|
||||
|
||||
// Send to sales team
|
||||
$emailSuccess = EmailHelper::sendContactNotification($formData);
|
||||
|
||||
// Send confirmation to user
|
||||
$confirmationSuccess = EmailHelper::sendConfirmationToUser($formData);
|
||||
|
||||
if ($saveSuccess && $emailSuccess) {
|
||||
$_SESSION['contact_success'] = true;
|
||||
|
||||
} else {
|
||||
Logger::error("Form processed but saveSuccess={$saveSuccess}, emailSuccess={$emailSuccess}");
|
||||
$_SESSION['contact_error'] = 'Your message was received but an internal error occurred. A confirmation may not have been sent.';
|
||||
|
||||
EmailHelper::alertAdmins('ContactController::submit - DB or email failure', 'Partial failure', $formData);
|
||||
}
|
||||
|
||||
if (!$confirmationSuccess) {
|
||||
Logger::error("Confirmation email failed to send to user: {$formData['email']}");
|
||||
// Don't show user error — it's non-critical
|
||||
}
|
||||
Logger::info("✅ Writing session flag: contact_success = true");
|
||||
Logger::info("✅ Session content before redirect: " . json_encode($_SESSION));
|
||||
|
||||
header("Location: /?contact_submitted=1#contact");
|
||||
|
||||
exit;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Fatal error in ContactController::submit: " . $e->getMessage());
|
||||
EmailHelper::alertAdmins('ContactController::submit - Uncaught Exception', $e->getMessage(), $_POST ?? []);
|
||||
$_SESSION['contact_error'] = 'An internal error occurred. Please try again later.';
|
||||
|
||||
Logger::info("✅ Writing session flag: catch contact_error = " . $_SESSION['contact_error']);
|
||||
Logger::info("✅ Session content before redirect: " . json_encode($_SESSION));
|
||||
header("Location: /?contact_error=2#contact");
|
||||
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
/**
|
||||
* File: ResendVerificationController.php
|
||||
* Version: 1.2
|
||||
* Path: /app/Controllers/ResendVerificationController.php
|
||||
* Purpose: Handles logic for resending verification emails for both newsletter and contact types with rate-limiting and expiration.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Controllers;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
use WizdomNetworks\WizeWeb\Utils\Database;
|
||||
use WizdomNetworks\WizeWeb\Utils\Logger;
|
||||
use WizdomNetworks\WizeWeb\Services\NewsletterService;
|
||||
use WizdomNetworks\WizeWeb\Services\ContactService;
|
||||
|
||||
class ResendVerificationController
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* File: SubscriberController.php
|
||||
* Version: 1.1
|
||||
* Path: /app/Controllers/SubscriberController.php
|
||||
* Purpose: Handles subscriber updates including optional name personalization.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Controllers;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
use WizdomNetworks\WizeWeb\Utils\Database;
|
||||
use WizdomNetworks\WizeWeb\Utils\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utils\ErrorHandler;
|
||||
|
||||
class SubscriberController
|
||||
{
|
||||
/**
|
||||
* POST /subscriber/update
|
||||
* Allows a verified subscriber to add their name.
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
try {
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo "Method Not Allowed";
|
||||
return;
|
||||
}
|
||||
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$firstName = trim($_POST['first_name'] ?? '');
|
||||
$lastName = trim($_POST['last_name'] ?? '');
|
||||
|
||||
if (empty($email)) {
|
||||
Logger::error("Subscriber update failed: email missing.");
|
||||
View::render('pages/verify_failed', ['reason' => 'Missing email address.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Database::getConnection();
|
||||
$stmt = $db->prepare("SELECT id FROM subscribers WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$subscriber = $stmt->fetch();
|
||||
|
||||
if (!$subscriber) {
|
||||
Logger::error("Subscriber update failed: not found [$email].");
|
||||
View::render('pages/verify_failed', ['reason' => 'Subscriber not found.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE subscribers SET first_name = ?, last_name = ? WHERE id = ?");
|
||||
$stmt->execute([$firstName, $lastName, $subscriber['id']]);
|
||||
|
||||
Logger::info("Subscriber updated: $email");
|
||||
$_SESSION['update_success'] = true;
|
||||
$_SESSION['update_type'] = 'newsletter';
|
||||
|
||||
header("Location: /verify-success");
|
||||
exit;
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Subscriber update error for $email: " . $e->getMessage());
|
||||
ErrorHandler::exception($e);
|
||||
View::render('pages/verify_failed', ['reason' => 'An error occurred while updating your info.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
/**
|
||||
* File: UnsubscribeController.php
|
||||
* Version: 1.0
|
||||
* Path: app/Controllers/
|
||||
* Purpose: Handles newsletter unsubscribe confirmation and processing.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Controllers;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
use WizdomNetworks\WizeWeb\Utils\Database;
|
||||
use WizdomNetworks\WizeWeb\Utils\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utils\ErrorHandler;
|
||||
|
||||
class UnsubscribeController
|
||||
{
|
||||
/**
|
||||
* GET /unsubscribe
|
||||
* Show confirmation form for unsubscribing.
|
||||
*/
|
||||
public function confirm(): void
|
||||
{
|
||||
try {
|
||||
$email = trim($_GET['email'] ?? '');
|
||||
|
||||
if (empty($email)) {
|
||||
Logger::error("Unsubscribe access without email.");
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'No email provided.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Database::getConnection();
|
||||
$stmt = $db->prepare("SELECT is_verified, unsubscribed_at FROM subscribers WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$subscriber = $stmt->fetch();
|
||||
|
||||
if (!$subscriber) {
|
||||
Logger::error("Unsubscribe: Subscriber not found [$email]");
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'Subscriber not found.']);
|
||||
return;
|
||||
}
|
||||
|
||||
if ($subscriber['unsubscribed_at']) {
|
||||
View::render('pages/unsubscribe_success', ['email' => $email, 'alreadyUnsubscribed' => true]);
|
||||
return;
|
||||
}
|
||||
|
||||
View::render('pages/unsubscribe_confirm', ['email' => $email]);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Unsubscribe view error: " . $e->getMessage());
|
||||
ErrorHandler::exception($e);
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'An unexpected error occurred.']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /unsubscribe
|
||||
* Perform the actual unsubscribe action.
|
||||
*/
|
||||
public function process(): void
|
||||
{
|
||||
try {
|
||||
$email = trim($_POST['email'] ?? '');
|
||||
$reason = trim($_POST['unsubscribe_reason'] ?? '');
|
||||
|
||||
if (empty($email)) {
|
||||
Logger::error("Unsubscribe form submitted without email.");
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'No email address was provided.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Database::getConnection();
|
||||
$stmt = $db->prepare("SELECT id FROM subscribers WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$subscriber = $stmt->fetch();
|
||||
|
||||
if (!$subscriber) {
|
||||
Logger::error("Unsubscribe: Subscriber not found during processing [$email]");
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'Subscriber not found.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare("UPDATE subscribers SET unsubscribed_at = NOW(), unsubscribe_reason = ? WHERE id = ?");
|
||||
$stmt->execute([$reason, $subscriber['id']]);
|
||||
|
||||
Logger::info("Subscriber unsubscribed: $email");
|
||||
View::render('pages/unsubscribe_success', ['email' => $email]);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Unsubscribe processing error for $email: " . $e->getMessage());
|
||||
ErrorHandler::exception($e);
|
||||
View::render('pages/unsubscribe_failed', ['reason' => 'An error occurred while processing your unsubscribe.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* File: VerificationController.php
|
||||
* Version: 1.2
|
||||
* Path: /app/Controllers/VerificationController.php
|
||||
* Purpose: Handles email verification for newsletter and contact messages, including code expiration and attempt logging.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Controllers;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
use WizdomNetworks\WizeWeb\Utils\Database;
|
||||
use WizdomNetworks\WizeWeb\Utils\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utils\EmailHelper;
|
||||
|
||||
class VerificationController
|
||||
{
|
||||
public function verify(): void
|
||||
{
|
||||
try {
|
||||
$code = $_GET['code'] ?? '';
|
||||
if (empty($code)) {
|
||||
Logger::error("Email verification attempted without a code.");
|
||||
View::render('pages/verify_failed', ['reason' => 'No verification code provided.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$db = Database::getConnection();
|
||||
|
||||
// Check subscribers table
|
||||
$stmt = $db->prepare("SELECT id, is_verified, email, verification_expires_at FROM subscribers 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;
|
||||
}
|
||||
|
||||
if ((int) $subscriber['is_verified'] === 1) {
|
||||
View::render('pages/verify_success', ['type' => 'newsletter', 'message' => 'This subscription has already been verified.']);
|
||||
return;
|
||||
}
|
||||
|
||||
$update = $db->prepare("UPDATE subscribers SET is_verified = 1, verification_code = NULL WHERE id = ?");
|
||||
$update->execute([$subscriber['id']]);
|
||||
|
||||
Logger::info("Subscriber verified: ID " . $subscriber['id']);
|
||||
View::render('pages/verify_success', ['type' => 'newsletter']);
|
||||
return;
|
||||
}
|
||||
|
||||
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.']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,9 @@
|
|||
* ============================================
|
||||
* File: Router.php
|
||||
* Path: /app/Core/
|
||||
* Purpose: Core router handling HTTP method–specific route dispatching.
|
||||
* Version: 1.1
|
||||
* Purpose: Core router handling HTTP method–specific route dispatching with dynamic path and closure support.
|
||||
* Version: 1.3
|
||||
* Author: Wizdom Networks
|
||||
* Usage: Handles all GET/POST routing to controllers.
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
|
|
@ -22,16 +21,45 @@ class Router
|
|||
/**
|
||||
* Registers a new route.
|
||||
*
|
||||
* @param string $path The URL path (e.g. /contact).
|
||||
* @param string $path The URL path (e.g. /contact or /verify/{code}).
|
||||
* @param string $controller The fully qualified controller class.
|
||||
* @param string $method The method name in the controller.
|
||||
* @param string $httpMethod HTTP method (GET, POST, etc.), defaults to GET.
|
||||
*/
|
||||
public function add(string $path, string $controller, string $method, string $httpMethod = 'GET'): void
|
||||
{
|
||||
$routeKey = strtoupper($httpMethod) . ':' . trim($path, '/');
|
||||
$normalizedPath = trim($path, '/');
|
||||
$routeKey = strtoupper($httpMethod) . ':' . $normalizedPath;
|
||||
|
||||
// Convert path with {param} to regex and store original keys
|
||||
$paramKeys = [];
|
||||
$regexPath = preg_replace_callback('/\{([a-zA-Z_][a-zA-Z0-9_]*)\}/', function ($matches) use (&$paramKeys) {
|
||||
$paramKeys[] = $matches[1];
|
||||
return '([^\/]+)';
|
||||
}, $normalizedPath);
|
||||
|
||||
$this->routes[$routeKey] = [
|
||||
'controller' => $controller,
|
||||
'method' => $method,
|
||||
'pattern' => "#^" . $regexPath . "$#",
|
||||
'params' => $paramKeys
|
||||
];
|
||||
|
||||
Logger::debug("Registering route: [$httpMethod] $path -> $controller::$method");
|
||||
$this->routes[$routeKey] = [$controller, $method];
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a closure-based route.
|
||||
*
|
||||
* @param string $path
|
||||
* @param \Closure $callback
|
||||
* @param string $httpMethod
|
||||
*/
|
||||
public function addClosure(string $path, \Closure $callback, string $httpMethod = 'GET'): void
|
||||
{
|
||||
$routeKey = strtoupper($httpMethod) . ':' . trim($path, '/');
|
||||
Logger::debug("Registering closure route: [$httpMethod] $path");
|
||||
$this->routes[$routeKey] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,41 +69,61 @@ class Router
|
|||
*/
|
||||
public function dispatch($path)
|
||||
{
|
||||
$httpMethod = $_SERVER['REQUEST_METHOD'];
|
||||
$routeKey = $httpMethod . ':' . trim($path, '/');
|
||||
$httpMethod = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET');
|
||||
$cleanPath = trim($path, '/');
|
||||
$routeKeyBase = $httpMethod . ':';
|
||||
|
||||
Logger::debug("Dispatching [$httpMethod] $path");
|
||||
|
||||
if (isset($this->routes[$routeKey])) {
|
||||
[$controllerName, $method] = $this->routes[$routeKey];
|
||||
Logger::debug("Matched route -> $controllerName::$method");
|
||||
|
||||
try {
|
||||
if (!class_exists($controllerName)) {
|
||||
throw new \Exception("Controller not found: $controllerName");
|
||||
}
|
||||
|
||||
$controller = new $controllerName();
|
||||
|
||||
if (!method_exists($controller, $method)) {
|
||||
throw new \Exception("Method $method not found in $controllerName");
|
||||
}
|
||||
|
||||
Logger::info("Executing controller: $controllerName::$method");
|
||||
$controller->$method();
|
||||
} catch (\Throwable $e) {
|
||||
echo "<pre>";
|
||||
echo "Exception: " . $e->getMessage() . "\n";
|
||||
echo "File: " . $e->getFile() . "\n";
|
||||
echo "Line: " . $e->getLine() . "\n";
|
||||
echo "Trace:\n" . $e->getTraceAsString();
|
||||
echo "</pre>";
|
||||
exit;
|
||||
foreach ($this->routes as $key => $route) {
|
||||
if (strpos($key, $routeKeyBase) !== 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$routePattern = $route['pattern'] ?? null;
|
||||
|
||||
// Handle Closure
|
||||
if ($route instanceof \Closure && $key === $routeKeyBase . $cleanPath) {
|
||||
Logger::info("Executing closure route: [$httpMethod] $cleanPath");
|
||||
$route();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle dynamic path matches
|
||||
if ($routePattern && preg_match($routePattern, $cleanPath, $matches)) {
|
||||
array_shift($matches); // first match is the whole path
|
||||
|
||||
$params = array_combine($route['params'], $matches) ?: [];
|
||||
$controllerName = $route['controller'];
|
||||
$method = $route['method'];
|
||||
|
||||
try {
|
||||
if (!class_exists($controllerName)) {
|
||||
throw new \Exception("Controller not found: $controllerName");
|
||||
}
|
||||
|
||||
$controller = new $controllerName();
|
||||
|
||||
if (!method_exists($controller, $method)) {
|
||||
throw new \Exception("Method $method not found in $controllerName");
|
||||
}
|
||||
|
||||
Logger::info("Executing controller: $controllerName::$method with params: " . json_encode($params));
|
||||
call_user_func_array([$controller, $method], $params);
|
||||
return;
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
echo "<pre>";
|
||||
echo "Exception: " . $e->getMessage() . "\n";
|
||||
echo "File: " . $e->getFile() . "\n";
|
||||
echo "Line: " . $e->getLine() . "\n";
|
||||
echo "Trace:\n" . $e->getTraceAsString();
|
||||
echo "</pre>";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger::error("Route not found: [$httpMethod] $path");
|
||||
http_response_code(404);
|
||||
echo "404 Not Found";
|
||||
}
|
||||
|
||||
Logger::error("Route not found: [$httpMethod] $path");
|
||||
http_response_code(404);
|
||||
echo "404 Not Found";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
// ============================================
|
||||
// File: View.php
|
||||
// Version: 1.1
|
||||
// Version: 1.2
|
||||
// Path: app/Core/View.php
|
||||
// Purpose: Handles dynamic view rendering with optional layout wrapping
|
||||
// Project: Wizdom Networks Website
|
||||
|
|
@ -33,6 +33,10 @@ class View
|
|||
public static function render(string $view, array $data = [], ?string $layout = null): void
|
||||
{
|
||||
Logger::debug("Rendering view: $view");
|
||||
if (!class_exists('View')) {
|
||||
class_alias(self::class, 'View');
|
||||
}
|
||||
|
||||
|
||||
// Extract data to make variables available in the view
|
||||
extract($data);
|
||||
|
|
@ -68,4 +72,39 @@ class View
|
|||
Logger::debug("Successfully rendered view: $view (no layout)");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Renders a partial view without applying a layout.
|
||||
*
|
||||
* Use this for modular components like hero, services, faq, etc.
|
||||
* Partial views must reside in: /resources/views/partials/
|
||||
*
|
||||
* @param string $partial The name of the partial (e.g., 'hero', 'faq').
|
||||
* You may use dot notation for subdirectories (e.g., 'admin.nav').
|
||||
* @param array $data Optional associative array of data to be extracted into the view.
|
||||
*
|
||||
* @throws \Exception if the partial file does not exist.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function renderPartial(string $partial, array $data = []): void
|
||||
{
|
||||
Logger::debug("Rendering partial: $partial");
|
||||
|
||||
// Convert dot notation to path and resolve to full filesystem path
|
||||
$partialPath = realpath(__DIR__ . "/../../resources/views/partials/" . str_replace('.', '/', $partial) . ".php");
|
||||
|
||||
Logger::debug("Resolved partial path: $partialPath");
|
||||
|
||||
if (!$partialPath || !file_exists($partialPath)) {
|
||||
Logger::error("Partial view not found: $partial | Resolved path: $partialPath");
|
||||
throw new \Exception("Partial view not found: $partial");
|
||||
}
|
||||
|
||||
// Extract data and include partial
|
||||
extract($data);
|
||||
include $partialPath;
|
||||
|
||||
Logger::debug("Successfully rendered partial: $partial");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* File: SubmissionLogModel.php
|
||||
* Version: 1.0
|
||||
* Path: /app/Models/SubmissionLogModel.php
|
||||
* Purpose: Logs every contact form submission attempt for auditing and spam control.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Models;
|
||||
|
||||
use PDO;
|
||||
use WizdomNetworks\WizeWeb\Utilities\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utilities\ErrorHandler;
|
||||
|
||||
/**
|
||||
* Class SubmissionLogModel
|
||||
*
|
||||
* Handles insertion of contact form submission logs into the database.
|
||||
*/
|
||||
class SubmissionLogModel
|
||||
{
|
||||
private PDO $db;
|
||||
|
||||
/**
|
||||
* SubmissionLogModel constructor.
|
||||
*
|
||||
* @param PDO $db A valid database connection.
|
||||
*/
|
||||
public function __construct(PDO $db)
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a contact form submission attempt.
|
||||
*
|
||||
* @param array $data {
|
||||
* @type string $email The submitted email address.
|
||||
* @type string|null $phone The submitted phone number.
|
||||
* @type string|null $ip_address The user's IP address.
|
||||
* @type string|null $user_agent The user agent string.
|
||||
* @type bool $was_saved True if saved to contact_messages.
|
||||
* @type string $reason Classification reason (e.g. 'valid', 'blocked:honeypot').
|
||||
* }
|
||||
* @return bool True on success, false on failure.
|
||||
*/
|
||||
public function logAttempt(array $data): bool
|
||||
{
|
||||
try {
|
||||
$sql = "INSERT INTO submission_logs (email, phone, ip_address, user_agent, was_saved, reason)
|
||||
VALUES (:email, :phone, :ip, :agent, :saved, :reason)";
|
||||
$stmt = $this->db->prepare($sql);
|
||||
return $stmt->execute([
|
||||
':email' => $data['email'],
|
||||
':phone' => $data['phone'] ?? null,
|
||||
':ip' => $data['ip_address'] ?? null,
|
||||
':agent' => $data['user_agent'] ?? null,
|
||||
':saved' => $data['was_saved'] ? 1 : 0,
|
||||
':reason' => $data['reason']
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Failed to log submission attempt.");
|
||||
ErrorHandler::exception($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
/**
|
||||
* File: ContactService.php
|
||||
* Version: 1.0
|
||||
* Path: /app/Services/ContactService.php
|
||||
* Purpose: Sends verification email for contact form submissions
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Services;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Utilities\EmailHelper;
|
||||
use WizdomNetworks\WizeWeb\Utilities\Logger;
|
||||
|
||||
class ContactService
|
||||
{
|
||||
/**
|
||||
* Sends a contact email verification message
|
||||
*
|
||||
* @param string $email The user's email address
|
||||
* @param string $code The unique verification code
|
||||
* @return bool True if email was sent successfully
|
||||
*/
|
||||
public static function sendVerificationEmail(string $email, string $code): bool
|
||||
{
|
||||
$appUrl = $_ENV['APP_URL'] ?? 'https://wizdom.ca';
|
||||
$verifyUrl = "$appUrl/verify/$code";
|
||||
|
||||
$subject = "Please verify your email address";
|
||||
|
||||
$body = <<<HTML
|
||||
<p>Hello,</p>
|
||||
<p>Thank you for reaching out to Wizdom Networks. To complete your contact request, please verify your email address by clicking the link below:</p>
|
||||
<p><a href="$verifyUrl">Verify Email Address</a></p>
|
||||
<p>If you did not submit this request, you can safely ignore this message.</p>
|
||||
<p>– The Wizdom Networks Team</p>
|
||||
HTML;
|
||||
|
||||
Logger::info("Sending contact verification email to: $email");
|
||||
return EmailHelper::send($email, $subject, $body);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
/**
|
||||
* File: NewsletterService.php
|
||||
* Version: 1.1
|
||||
* Path: app/Services/
|
||||
* Purpose: Handles newsletter subscriptions including verification email flow.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Services;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Utils\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utils\ErrorHandler;
|
||||
use WizdomNetworks\WizeWeb\Utils\Database;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
use PHPMailer\PHPMailer\Exception as MailException;
|
||||
|
||||
class NewsletterService
|
||||
{
|
||||
/**
|
||||
* Subscribes a user to the newsletter if not already subscribed.
|
||||
* Sends a verification email with a unique code.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $ip
|
||||
* @param string $userAgent
|
||||
* @return bool True if subscription initiated successfully, false otherwise
|
||||
*/
|
||||
public static function subscribeIfNew(string $email, string $ip, string $userAgent): bool
|
||||
{
|
||||
try {
|
||||
$db = Database::getConnection();
|
||||
|
||||
// Check if already subscribed
|
||||
$stmt = $db->prepare("SELECT is_verified FROM subscribers WHERE email = ?");
|
||||
$stmt->execute([$email]);
|
||||
$row = $stmt->fetch();
|
||||
|
||||
if ($row) {
|
||||
if ((int) $row['is_verified'] === 1) {
|
||||
Logger::info("Newsletter signup skipped (already verified): $email");
|
||||
return false;
|
||||
} else {
|
||||
Logger::info("Newsletter re-verification triggered for $email");
|
||||
// Optionally regenerate and resend code here
|
||||
}
|
||||
}
|
||||
|
||||
$verificationCode = bin2hex(random_bytes(16));
|
||||
|
||||
if ($row) {
|
||||
// Update existing unverified entry
|
||||
$stmt = $db->prepare("UPDATE subscribers SET verification_code = ?, ip_address = ?, user_agent = ?, created_at = NOW() WHERE email = ?");
|
||||
$stmt->execute([$verificationCode, $ip, $userAgent, $email]);
|
||||
} else {
|
||||
// Insert new record
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO subscribers (email, verification_code, is_verified, ip_address, user_agent, created_at)
|
||||
VALUES (?, ?, 0, ?, ?, NOW())
|
||||
");
|
||||
$stmt->execute([$email, $verificationCode, $ip, $userAgent]);
|
||||
}
|
||||
|
||||
Logger::info("Newsletter subscription initiated for $email, verification code generated.");
|
||||
|
||||
return self::sendVerificationEmail($email, $verificationCode);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("Newsletter subscription failed for $email: " . $e->getMessage());
|
||||
ErrorHandler::exception($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the newsletter verification email.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string $code
|
||||
* @return bool True if sent successfully, false otherwise
|
||||
*/
|
||||
private static function sendVerificationEmail(string $email, string $code): bool
|
||||
{
|
||||
try {
|
||||
$verifyUrl = $_ENV['BASE_URL'] . "/verify?code=" . urlencode($code);
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $_ENV['SMTP_HOST'];
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $_ENV['SMTP_USER'];
|
||||
$mail->Password = $_ENV['SMTP_PASS'];
|
||||
$mail->SMTPSecure = $_ENV['SMTP_SECURE'] ?? 'tls';
|
||||
$mail->Port = $_ENV['SMTP_PORT'] ?? 587;
|
||||
|
||||
$mail->setFrom($_ENV['MAIL_FROM'], $_ENV['MAIL_FROM_NAME']);
|
||||
$mail->addAddress($email);
|
||||
$mail->Subject = 'Confirm your subscription to Wizdom Networks';
|
||||
$mail->isHTML(true);
|
||||
$mail->Body = "
|
||||
<p>Thank you for subscribing to the Wizdom Networks newsletter!</p>
|
||||
<p>Please click the link below to confirm your subscription:</p>
|
||||
<p><a href='{$verifyUrl}'>Confirm My Subscription</a></p>
|
||||
<p>If you did not request this, you can safely ignore this email.</p>
|
||||
";
|
||||
|
||||
$mail->send();
|
||||
Logger::info("Verification email sent to $email");
|
||||
return true;
|
||||
} catch (MailException $e) {
|
||||
Logger::error("Failed to send verification email to $email: " . $e->getMessage());
|
||||
ErrorHandler::exception($e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
/**
|
||||
* File: EmailHelper.php
|
||||
* Version: 2.4
|
||||
* Version: 2.5
|
||||
* Path: /app/Utilities/EmailHelper.php
|
||||
* Purpose: Sends contact confirmations, sales notifications, and admin alerts with proper formatting and logic.
|
||||
* Project: Wizdom Networks Website
|
||||
|
|
@ -63,6 +63,23 @@ class EmailHelper
|
|||
return $validEmails;
|
||||
}
|
||||
|
||||
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 "
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
/**
|
||||
* File: SessionHelper.php
|
||||
* Version: 1.0
|
||||
* Version: 1.1
|
||||
* Path: /app/Utilities/SessionHelper.php
|
||||
* Purpose: Utility to simplify session handling, especially flash messages.
|
||||
* Project: Wizdom Networks Website
|
||||
|
|
@ -87,4 +87,29 @@ class SessionHelper
|
|||
{
|
||||
return isset($_SESSION[$key]);
|
||||
}
|
||||
/**
|
||||
* Finalize the session and persist data to disk.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function writeClose(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
session_write_close();
|
||||
Logger::info("✅ Session write closed via SessionHelper.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Destroy the session and clear all session data.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function destroy(): void
|
||||
{
|
||||
if (session_status() === PHP_SESSION_ACTIVE) {
|
||||
$_SESSION = [];
|
||||
session_destroy();
|
||||
Logger::info("Session destroyed via SessionHelper.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,73 @@
|
|||
<?php
|
||||
/**
|
||||
* ============================================
|
||||
* File: SubmissionCheck.php
|
||||
* Path: /app/Utilities/
|
||||
* Purpose: Utility to check for recent form submissions by email
|
||||
* Namespace: WizdomNetworks\WizeWeb\Utilities
|
||||
* Version: 1.0
|
||||
* Author: Wizdom Networks
|
||||
* ============================================
|
||||
* Version: 1.1
|
||||
* Purpose: Helper to detect and block repeated or abusive contact form submissions
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
|
||||
namespace WizdomNetworks\WizeWeb\Utilities;
|
||||
|
||||
use WizdomNetworks\WizeWeb\Utilities\Database;
|
||||
use PDO;
|
||||
use Exception;
|
||||
use WizdomNetworks\WizeWeb\Utilities\Logger;
|
||||
|
||||
class SubmissionCheck
|
||||
{
|
||||
private const LOOKBACK_DAYS = 30;
|
||||
|
||||
/**
|
||||
* Check if the given email has a recent submission in the specified table.
|
||||
* Evaluates whether a submission is likely spam or abuse.
|
||||
*
|
||||
* @param string $email - The email address to check.
|
||||
* @param string $table - The name of the table (e.g., contact_messages, subscribers).
|
||||
* @param string $emailField - The name of the email field in the table.
|
||||
* @param string $timestampField - The name of the datetime/timestamp field.
|
||||
* @param int $days - The number of days to consider as "recent" (default: 7).
|
||||
* @return array|null - Returns array with date of last submission if found, or null if none.
|
||||
* @throws Exception - If query fails.
|
||||
* @param PDO $pdo
|
||||
* @param string $email
|
||||
* @param string|null $phone
|
||||
* @param string|null $ip
|
||||
* @return array [action: accept|flag|block|notify, reason: string, count: int]
|
||||
*/
|
||||
public static function hasRecentSubmission(string $email, string $table, string $emailField, string $timestampField, int $days = 7): ?array
|
||||
public static function evaluate(PDO $pdo, string $email, ?string $phone, ?string $ip): array
|
||||
{
|
||||
$pdo = Database::getConnection();
|
||||
try {
|
||||
$query = "
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM submission_logs WHERE email = :email AND created_at >= 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
|
||||
";
|
||||
|
||||
$sql = "SELECT $timestampField FROM $table WHERE $emailField = :email ORDER BY $timestampField DESC LIMIT 1";
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute(['email' => $email]);
|
||||
$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->execute();
|
||||
$data = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$emailHits = (int)($data['email_hits'] ?? 0);
|
||||
$phoneHits = (int)($data['phone_hits'] ?? 0);
|
||||
$ipHits = (int)($data['ip_hits'] ?? 0);
|
||||
$ipHourly = (int)($data['ip_hourly'] ?? 0);
|
||||
|
||||
if ($row && isset($row[$timestampField])) {
|
||||
$last = new \DateTime($row[$timestampField]);
|
||||
$cutoff = (new \DateTime())->modify("-{$days} days");
|
||||
$totalScore = $emailHits + $phoneHits + $ipHits;
|
||||
|
||||
if ($last >= $cutoff) {
|
||||
return ['submitted_at' => $last->format('Y-m-d H:i:s')];
|
||||
if ($emailHits >= 4 || $phoneHits >= 4 || $ipHits >= 5) {
|
||||
return ['action' => 'block', 'reason' => 'IP/email/phone threshold exceeded', 'count' => $totalScore];
|
||||
}
|
||||
if ($ipHourly >= 3) {
|
||||
return ['action' => 'flag', 'reason' => 'Multiple submissions from IP in last hour', 'count' => $ipHourly];
|
||||
}
|
||||
if ($totalScore >= 6) {
|
||||
return ['action' => 'notify', 'reason' => 'Cumulative signal from all identifiers', 'count' => $totalScore];
|
||||
}
|
||||
if ($emailHits >= 2 || $phoneHits >= 2 || $ipHits >= 2) {
|
||||
return ['action' => 'flag', 'reason' => 'Repeated pattern detected', 'count' => $totalScore];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return ['action' => 'accept', 'reason' => 'accepted', 'count' => $totalScore];
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Logger::error("SubmissionCheck evaluation failed: " . $e->getMessage());
|
||||
return ['action' => 'error', 'reason' => 'Evaluation error', 'count' => 0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
/var/www/html/dev.wizdom.ca/public/assets/img
|
||||
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 113 KiB |
|
After Width: | Height: | Size: 87 KiB |
|
|
@ -1,26 +1,30 @@
|
|||
// File: public/assets/js/contact-form.js
|
||||
// Version: 1.2
|
||||
// Purpose: Handles JS-based form submission with feedback and duplicate prevention
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('contact_submitted') || params.get('contact_error')) {
|
||||
const contactSection = document.getElementById('contact');
|
||||
if (contactSection) {
|
||||
contactSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
if (params.get('contact_submitted') || params.get('contact_error')) {
|
||||
const contactSection = document.getElementById('contact');
|
||||
if (contactSection) {
|
||||
contactSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
const form = document.querySelector('.php-email-form');
|
||||
if (!form) return;
|
||||
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
|
||||
const loading = document.createElement('div');
|
||||
const errorMsg = document.createElement('div');
|
||||
const successMsg = document.createElement('div');
|
||||
|
||||
loading.className = 'loading mt-3';
|
||||
loading.textContent = 'Sending message...';
|
||||
|
||||
errorMsg.className = 'error-message mt-3';
|
||||
errorMsg.style.display = 'none';
|
||||
|
||||
successMsg.className = 'sent-message mt-3';
|
||||
successMsg.style.display = 'none';
|
||||
|
||||
|
|
@ -33,6 +37,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
form.addEventListener('submit', async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (submitButton.disabled) return; // prevent double click
|
||||
submitButton.disabled = true;
|
||||
|
||||
loading.style.display = 'block';
|
||||
errorMsg.style.display = 'none';
|
||||
successMsg.style.display = 'none';
|
||||
|
|
@ -47,6 +54,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
|
||||
const result = await response.json();
|
||||
loading.style.display = 'none';
|
||||
submitButton.disabled = false;
|
||||
|
||||
if (response.ok && result.success) {
|
||||
successMsg.textContent = result.message || 'Your message was sent successfully.';
|
||||
|
|
@ -57,9 +65,17 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
}
|
||||
} catch (err) {
|
||||
loading.style.display = 'none';
|
||||
submitButton.disabled = false;
|
||||
errorMsg.textContent = err.message || 'An unexpected error occurred.';
|
||||
errorMsg.style.display = 'block';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
window.prefillService = function (serviceName) {
|
||||
const subjectField = document.getElementById('subject');
|
||||
if (subjectField) {
|
||||
subjectField.value = `Inquiry about ${serviceName}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -1,53 +1,89 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* ============================================
|
||||
* File: public/index.php
|
||||
* Version: v1.4
|
||||
* Purpose: Application entry point for Arsha one-pager with early session start
|
||||
* Version: v1.7
|
||||
* Purpose: Application entry point for Arsha one-pager with dynamic & closure route support
|
||||
* Project: Wizdom Networks Website
|
||||
* ============================================
|
||||
*/
|
||||
|
||||
// Enable strict error reporting
|
||||
// ------------------------------------------------------------
|
||||
// Environment & Error Configuration
|
||||
// ------------------------------------------------------------
|
||||
ini_set('display_errors', 1);
|
||||
ini_set('display_startup_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
// Autoload classes via Composer
|
||||
// ------------------------------------------------------------
|
||||
// Composer Autoload & Environment Variables
|
||||
// ------------------------------------------------------------
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// Load environment variables
|
||||
use Dotenv\Dotenv;
|
||||
$dotenv = Dotenv::createImmutable(__DIR__ . '/../');
|
||||
$dotenv->load();
|
||||
|
||||
// Start session before any router logic
|
||||
// ------------------------------------------------------------
|
||||
// Core Utilities & Session Boot
|
||||
// ------------------------------------------------------------
|
||||
use WizdomNetworks\WizeWeb\Utilities\SessionHelper;
|
||||
SessionHelper::start();
|
||||
|
||||
// Import core system utilities
|
||||
use WizdomNetworks\WizeWeb\Core\Router;
|
||||
use WizdomNetworks\WizeWeb\Utilities\Logger;
|
||||
use WizdomNetworks\WizeWeb\Utilities\ErrorHandler;
|
||||
use WizdomNetworks\WizeWeb\Core\Router;
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
|
||||
// Import controllers
|
||||
use WizdomNetworks\WizeWeb\Controllers\LandingController;
|
||||
use WizdomNetworks\WizeWeb\Controllers\ContactController;
|
||||
use WizdomNetworks\WizeWeb\Controllers\VerificationController;
|
||||
use WizdomNetworks\WizeWeb\Controllers\ResendVerificationController;
|
||||
use WizdomNetworks\WizeWeb\Controllers\SubscriberController;
|
||||
use WizdomNetworks\WizeWeb\Controllers\UnsubscribeController;
|
||||
|
||||
// Boot the application
|
||||
// Start session before routing
|
||||
SessionHelper::start();
|
||||
Logger::info("Bootstrapping application");
|
||||
|
||||
// Initialize router and define routes
|
||||
// ------------------------------------------------------------
|
||||
// Route Registration
|
||||
// ------------------------------------------------------------
|
||||
$router = new Router();
|
||||
|
||||
// Landing page routes
|
||||
// Landing Page Routes
|
||||
$router->add('', LandingController::class, 'index');
|
||||
$router->add('/', LandingController::class, 'index');
|
||||
$router->add('index.php', LandingController::class, 'index');
|
||||
|
||||
// Contact form routes
|
||||
// Contact Form Routes
|
||||
$router->add('/contact', ContactController::class, 'index', 'GET');
|
||||
$router->add('/contact', ContactController::class, 'submit', 'POST');
|
||||
|
||||
// Dispatch the incoming request
|
||||
// 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...
|
||||
|
||||
// Resend / Newsletter / Unsubscribe
|
||||
$router->add('/resend-verification', ResendVerificationController::class, 'handle', 'POST');
|
||||
$router->add('/subscriber/update', SubscriberController::class, 'update', 'POST');
|
||||
$router->add('/unsubscribe', UnsubscribeController::class, 'confirm', 'GET');
|
||||
$router->add('/unsubscribe', UnsubscribeController::class, 'process', 'POST');
|
||||
|
||||
// Post-Redirect-GET success confirmation
|
||||
$router->addClosure('/verify-success', function () {
|
||||
$type = $_SESSION['update_type'] ?? 'newsletter';
|
||||
$message = $_SESSION['update_success'] ? 'Thank you! Your information has been updated.' : null;
|
||||
|
||||
View::render('pages/verify_success', [
|
||||
'type' => $type,
|
||||
'message' => $message,
|
||||
]);
|
||||
|
||||
unset($_SESSION['update_success'], $_SESSION['update_type']);
|
||||
}, 'GET');
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Dispatch Request
|
||||
// ------------------------------------------------------------
|
||||
$requestedPath = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
$router->dispatch($requestedPath);
|
||||
|
|
|
|||
|
|
@ -1,75 +1,24 @@
|
|||
<?php
|
||||
// use WizdomNetworks\WizeWeb\Utilities\SessionHelper;
|
||||
// SessionHelper::start();
|
||||
|
||||
// ============================================
|
||||
// File: arsha.php
|
||||
// Version: 1.2
|
||||
// Version: 1.3.1
|
||||
// Path: resources/views/layouts/arsha.php
|
||||
// Purpose: Complete Arsha layout wrapper with mobile nav, preloader, scroll-top, and dynamic content support
|
||||
// Purpose: Layout wrapper for Arsha one-pager using modular header, footer, and injected content
|
||||
// ============================================
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||
<title><?= $pageTitle ?? 'Wizdom Networks' ?></title>
|
||||
<meta content="" name="description">
|
||||
<meta content="" name="keywords">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link href="/assets/img/favicon.png" rel="icon">
|
||||
<link href="/assets/img/apple-touch-icon.png" rel="apple-touch-icon">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Raleway:300,400,500,600,700|Poppins:300,400,500,600,700" rel="stylesheet">
|
||||
|
||||
<!-- Vendor CSS Files -->
|
||||
<link href="/assets/vendor/aos/aos.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/boxicons/css/boxicons.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/glightbox/css/glightbox.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/remixicon/remixicon.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/swiper/swiper-bundle.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Main CSS File -->
|
||||
<link href="/assets/css/main.css" rel="stylesheet">
|
||||
</head>
|
||||
<?php include __DIR__ . '/head.php'; ?>
|
||||
|
||||
<body>
|
||||
|
||||
<!-- Preloader -->
|
||||
<div id="preloader"></div>
|
||||
|
||||
<!-- ======= Header ======= -->
|
||||
<header id="header" class="header fixed-top">
|
||||
<div class="container-fluid container-xl d-flex align-items-center justify-content-between">
|
||||
<?php include __DIR__ . '/header.php'; ?>
|
||||
|
||||
<h1 class="logo">
|
||||
<a href="/"><img src="/assets/img/wizdom-networks-logo-v2-no-slogan.webp" alt="Wizdom Networks Logo" class="img-fluid"></a>
|
||||
</h1>
|
||||
|
||||
<nav id="navbar" class="navmenu">
|
||||
<ul>
|
||||
<li><a class="nav-link scrollto active" href="#hero">Home</a></li>
|
||||
<li><a class="nav-link scrollto" href="#about">About</a></li>
|
||||
<li><a class="nav-link scrollto" href="#services">Services</a></li>
|
||||
<!-- <li><a class="nav-link scrollto" href="#portfolio">Portfolio</a></li> -->
|
||||
<li><a class="nav-link scrollto" href="#team">Team</a></li>
|
||||
<li><a class="nav-link scrollto" href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
<i class="bi bi-list mobile-nav-toggle"></i>
|
||||
</nav>
|
||||
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
|
||||
<!-- ======= Dynamic Page Content ======= -->
|
||||
<!-- Page Content -->
|
||||
<?= $content ?>
|
||||
|
||||
<!-- Scroll-to-Top Button -->
|
||||
|
|
@ -77,26 +26,31 @@
|
|||
<i class="bi bi-arrow-up-short"></i>
|
||||
</a>
|
||||
|
||||
<!-- Vendor JS Files -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/purecounterjs@1.5.0/dist/purecounter_vanilla.js"></script>
|
||||
<script src="/assets/vendor/aos/aos.js"></script>
|
||||
<script src="/assets/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/vendor/glightbox/js/glightbox.min.js"></script>
|
||||
<script src="/assets/vendor/isotope-layout/isotope.pkgd.min.js"></script>
|
||||
<script src="/assets/vendor/swiper/swiper-bundle.min.js"></script>
|
||||
<script src="/assets/vendor/waypoints/noframework.waypoints.js"></script>
|
||||
<script>
|
||||
if (document.querySelector('.php-email-form')) {
|
||||
const script = document.createElement('script');
|
||||
script.src = '/assets/js/contact-form.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
</script>
|
||||
|
||||
<?php include __DIR__ . '/footer.php'; ?>
|
||||
|
||||
<!-- Vendor JS Files -->
|
||||
<script src="/assets/vendor/aos/aos.js"></script>
|
||||
<script src="/assets/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/assets/vendor/glightbox/js/glightbox.min.js"></script>
|
||||
<script src="/assets/vendor/isotope-layout/isotope.pkgd.min.js"></script>
|
||||
<script src="/assets/vendor/swiper/swiper-bundle.min.js"></script>
|
||||
<script src="/assets/vendor/waypoints/noframework.waypoints.js"></script>
|
||||
<script src="/assets/vendor/imagesloaded/imagesloaded.pkgd.min.js"></script>
|
||||
<script src="/assets/js/contact-form.js"></script>
|
||||
|
||||
<!-- Conditional Form Script -->
|
||||
<script>
|
||||
if (document.querySelector('.php-email-form')) {
|
||||
const script = document.createElement('script');
|
||||
script.src = '/assets/vendor/php-email-form/validate.js';
|
||||
document.body.appendChild(script);
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Main JS File (must come last) -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
|
||||
<!-- Main JS File -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,94 @@
|
|||
<?php
|
||||
<!-- ======= Footer ======= -->
|
||||
<footer id="footer" class="footer">
|
||||
|
||||
/**
|
||||
* Footer Partial
|
||||
*
|
||||
* This file includes the footer section, including copyright information and necessary scripts.
|
||||
*/
|
||||
|
||||
?><footer class="footer mt-auto py-3 bg-dark text-white">
|
||||
<div class="container text-center">
|
||||
<p>© <?php echo date('Y'); ?> Wizdom Networks. All rights reserved.</p>
|
||||
<p>
|
||||
<a href="/privacy" class="text-white">Privacy Policy</a> |
|
||||
<a href="/terms" class="text-white">Terms of Service</a>
|
||||
</p>
|
||||
<!-- Newsletter -->
|
||||
<div class="footer-newsletter">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-6">
|
||||
<h4>Join Our Newsletter</h4>
|
||||
<p>Get updates on new services, insights, and best practices from the Wizdom Networks team.</p>
|
||||
<form action="forms/newsletter.php" method="post" class="php-email-form">
|
||||
<div class="newsletter-form">
|
||||
<input type="email" name="email" placeholder="Your Email">
|
||||
<input type="submit" value="Subscribe">
|
||||
</div>
|
||||
<div class="loading">Loading</div>
|
||||
<div class="error-message"></div>
|
||||
<div class="sent-message">Subscription successful. Thank you!</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap JS with Fallback -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
|
||||
onerror="this.onerror=null;this.src='/assets/bootstrap/js/bootstrap.bundle.min.js';"></script>
|
||||
<!-- Custom Scripts -->
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<!-- Footer Links & Contact -->
|
||||
<div class="container footer-top">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-4 col-md-6 footer-about">
|
||||
<a href="/" class="d-flex align-items-center">
|
||||
<span class="sitename">Wizdom Networks</span>
|
||||
</a>
|
||||
<div class="footer-contact pt-3">
|
||||
<p>Mississauga, ON, Canada</p>
|
||||
<p><strong>Phone:</strong> <span>416-USE-WISE</span></p>
|
||||
<p><strong>Email:</strong> <span>concierge@wizdom.ca</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Quick Links</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#hero">Home</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#about">About</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Services</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">IT Strategy & Alignment</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Cloud Strategy</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">HelpDesk+</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#stealthascope-pricing">Stealthascope</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-12">
|
||||
<h4>Follow Us</h4>
|
||||
<p>Stay connected with Wizdom Networks on social.</p>
|
||||
<div class="social-links d-flex">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container copyright text-center mt-4">
|
||||
<p>© <span>Copyright</span> <strong class="px-1 sitename">Wizdom Networks</strong>. <span>All Rights Reserved</span></p>
|
||||
</div>
|
||||
|
||||
</footer><!-- End Footer -->
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
'use strict'
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,49 +1,44 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Head Partial (v6)
|
||||
*
|
||||
* This file includes metadata, stylesheets, and scripts necessary for rendering the site.
|
||||
* Implements improved error handling to prevent undefined variable issues and corrects file path references.
|
||||
*/
|
||||
|
||||
// Ensure all optional configurations have default values
|
||||
$heroConfig = $heroConfig ?? ['enabled' => false];
|
||||
$sliderConfig = $sliderConfig ?? ['enabled' => false];
|
||||
$sidebarConfig = $sidebarConfig ?? ['enabled' => false];
|
||||
$pageId = $pageId ?? null;
|
||||
|
||||
<?php
|
||||
// ============================================
|
||||
// File: head.php
|
||||
// Version: 1.2
|
||||
// Path: resources/views/layouts/head.php
|
||||
// Purpose: Shared <head> content across all layouts
|
||||
// Project: Wizdom Networks Website
|
||||
// ============================================
|
||||
?>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?php echo $title ?? 'Wizdom Networks'; ?></title>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= $pageTitle ?? 'Wizdom Networks' ?></title>
|
||||
|
||||
<!-- Bootstrap CSS with Fallback -->
|
||||
<link rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
onerror="this.onerror=null;this.href='/assets/bootstrap/css/bootstrap.min.css';">
|
||||
|
||||
<!-- Main Stylesheets -->
|
||||
<link rel="stylesheet" href="/assets/css/main.css">
|
||||
<link rel="stylesheet" href="/assets/css/utilities.css">
|
||||
|
||||
<!-- Conditionally Load Component Stylesheets -->
|
||||
<?php if (!empty($heroConfig) && isset($heroConfig['enabled']) && $heroConfig['enabled']) : ?>
|
||||
<link rel="stylesheet" href="/assets/css/hero.css">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($sliderConfig) && isset($sliderConfig['enabled']) && $sliderConfig['enabled']) : ?>
|
||||
<link rel="stylesheet" href="/assets/css/slider.css">
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($sidebarConfig) && isset($sidebarConfig['enabled']) && $sidebarConfig['enabled']) : ?>
|
||||
<link rel="stylesheet" href="/assets/css/sidebar.css">
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Load Page-Specific Styles if Defined and File Exists -->
|
||||
<?php
|
||||
$pageCssFile = "/assets/css/" . htmlspecialchars($pageId, ENT_QUOTES, 'UTF-8') . ".css";
|
||||
if (!empty($pageId) && file_exists($_SERVER['DOCUMENT_ROOT'] . $pageCssFile)) : ?>
|
||||
<link rel="stylesheet" href="<?php echo $pageCssFile; ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="/assets/images/favicon.png">
|
||||
<!-- Meta Description & Keywords -->
|
||||
<meta name="description" content="Wizdom Networks provides expert IT consulting, infrastructure, and support services across Canada.">
|
||||
<meta name="keywords" content="Wizdom Networks, IT consulting, HelpDesk, Microsoft 365, virtualization, cloud strategy, managed IT">
|
||||
|
||||
<!-- Open Graph (OG) Tags -->
|
||||
<meta property="og:title" content="<?= $pageTitle ?? 'Wizdom Networks' ?>">
|
||||
<meta property="og:description" content="Strategic IT consulting and support from trusted Canadian experts.">
|
||||
<meta property="og:image" content="/assets/img/wizdom-networks-logo-v2-no-slogan.webp">
|
||||
<meta property="og:url" content="<?= $_ENV['SITE_URL'] ?? 'https://wizdom.ca' ?>">
|
||||
<meta property="og:type" content="website">
|
||||
|
||||
<!-- Favicons -->
|
||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||
<link rel="apple-touch-icon" href="/assets/img/apple-touch-icon.png">
|
||||
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700|Raleway:300,400,500,600,700|Poppins:300,400,500,600,700" rel="stylesheet">
|
||||
|
||||
<!-- Vendor CSS Files -->
|
||||
<link href="/assets/vendor/aos/aos.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/glightbox/css/glightbox.min.css" rel="stylesheet">
|
||||
<link href="/assets/vendor/swiper/swiper-bundle.min.css" rel="stylesheet">
|
||||
|
||||
|
||||
|
||||
<!-- Main CSS File -->
|
||||
<link href="/assets/css/main.css" rel="stylesheet">
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,16 @@
|
|||
<header class="bg-light py-3">
|
||||
|
||||
</header>
|
||||
<header id="header" class="header fixed-top">
|
||||
<div class="container-fluid container-xl d-flex align-items-center justify-content-between">
|
||||
|
||||
<h1 class="logo">
|
||||
<a href="/"><img src="/assets/img/wizdom-networks-logo-v2-no-slogan.webp" alt="Wizdom Networks Logo" class="img-fluid"></a>
|
||||
</h1>
|
||||
|
||||
<!-- insert actual nav here -->
|
||||
<?php
|
||||
use WizdomNetworks\WizeWeb\Core\View;
|
||||
View::renderPartial('navbar');
|
||||
?>
|
||||
<!-- end nav -->
|
||||
</div>
|
||||
</header>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
/**
|
||||
* File: contact_check_email.php
|
||||
* Version: 1.1
|
||||
* Path: /resources/views/pages/contact_check_email.php
|
||||
* Purpose: Informs the user that verification is required and offers a resend option.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
?>
|
||||
<section class="verify-section section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-envelope-check text-info" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">Please Verify Your Email</h2>
|
||||
<p class="lead">We’ve sent you a confirmation link. Please check your inbox to verify your message.</p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/newsletter-thanks.webp" alt="Check Email" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="/" class="btn btn-outline-primary">Return to Home</a>
|
||||
</div>
|
||||
|
||||
<!-- Resend Verification Form -->
|
||||
<div class="mt-5 pt-4 border-top">
|
||||
<h5 class="mb-3">Didn't get the email?</h5>
|
||||
<p>Enter your email again to receive a new verification link.</p>
|
||||
<form action="/resend-verification" method="post" class="row justify-content-center" style="max-width: 500px; margin: 0 auto;">
|
||||
<input type="hidden" name="type" value="contact">
|
||||
<div class="col-12 mb-2">
|
||||
<input type="email" name="email" class="form-control" placeholder="Enter your email" required>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-secondary">Resend Verification Link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
/**
|
||||
* ============================================
|
||||
* File: landing.php
|
||||
* Path: /resources/views/pages/home/
|
||||
* Purpose: Loads the Arsha one-pager layout content
|
||||
* Version: 1.2.2
|
||||
* Author: Wizdom Networks
|
||||
* Usage: Rendered via LandingController::index()
|
||||
* ============================================
|
||||
*/
|
||||
echo "<!-- SESSION: " . json_encode($_SESSION) . " -->";
|
||||
?>
|
||||
|
||||
|
||||
|
||||
<?php View::renderPartial('hero'); ?>
|
||||
|
||||
<?php View::renderPartial('clients'); ?>
|
||||
<?php View::renderPartial('about'); ?>
|
||||
|
||||
<?php View::renderPartial('why-us'); ?>
|
||||
<?php View::renderPartial('skills'); ?>
|
||||
<?php View::renderPartial('services'); ?>
|
||||
|
||||
<?php View::renderPartial('work-process'); ?>
|
||||
<?php View::renderPartial('call-to-action'); ?>
|
||||
<!-- ======= Portfolio Section ======= -->
|
||||
<!-- ======= End Portfolio Section ======= -->
|
||||
|
||||
<?php View::renderPartial('team'); ?>
|
||||
<?php View::renderPartial('testimonials'); ?>
|
||||
<?php View::renderPartial('pricing'); ?>
|
||||
<?php View::renderPartial('contact'); ?>
|
||||
<?php View::renderPartial('faq'); ?>
|
||||
|
||||
<!-- ======= Footer ======= -->
|
||||
<footer id="footer" class="footer">
|
||||
|
||||
<!-- Newsletter -->
|
||||
<div class="footer-newsletter">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-6">
|
||||
<h4>Join Our Newsletter</h4>
|
||||
<p>Get updates on new services, insights, and best practices from the Wizdom Networks team.</p>
|
||||
<form action="forms/newsletter.php" method="post" class="php-email-form">
|
||||
<div class="newsletter-form">
|
||||
<input type="email" name="email" placeholder="Your Email">
|
||||
<input type="submit" value="Subscribe">
|
||||
</div>
|
||||
<div class="loading">Loading</div>
|
||||
<div class="error-message"></div>
|
||||
<div class="sent-message">Subscription successful. Thank you!</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Links & Contact -->
|
||||
<div class="container footer-top">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-4 col-md-6 footer-about">
|
||||
<a href="/" class="d-flex align-items-center">
|
||||
<span class="sitename">Wizdom Networks</span>
|
||||
</a>
|
||||
<div class="footer-contact pt-3">
|
||||
<p>Mississauga, ON, Canada</p>
|
||||
<p><strong>Phone:</strong> <span>416-USE-WISE</span></p>
|
||||
<p><strong>Email:</strong> <span>concierge@wizdom.ca</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Quick Links</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#hero">Home</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#about">About</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Services</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">IT Strategy & Alignment</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Cloud Strategy</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">HelpDesk+</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#stealthascope-pricing">Stealthascope</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-12">
|
||||
<h4>Follow Us</h4>
|
||||
<p>Stay connected with Wizdom Networks on social.</p>
|
||||
<div class="social-links d-flex">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container copyright text-center mt-4">
|
||||
<p>© <span>Copyright</span> <strong class="px-1 sitename">Wizdom Networks</strong>. <span>All Rights Reserved</span></p>
|
||||
</div>
|
||||
|
||||
</footer><!-- End Footer -->
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
'use strict'
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
/**
|
||||
* ============================================
|
||||
* File: landing.php
|
||||
* Path: /resources/views/pages/home/
|
||||
* Path: /resources/views/pages/landing.php
|
||||
* Purpose: Loads the Arsha one-pager layout content
|
||||
* Version: 1.2.2
|
||||
* Version: 1.3.3
|
||||
* Author: Wizdom Networks
|
||||
* Usage: Rendered via LandingController::index()
|
||||
* ============================================
|
||||
|
|
@ -12,939 +12,17 @@
|
|||
echo "<!-- SESSION: " . json_encode($_SESSION) . " -->";
|
||||
?>
|
||||
|
||||
<?php View::renderPartial('hero'); ?>
|
||||
<?php View::renderPartial('clients'); ?>
|
||||
<?php View::renderPartial('about'); ?>
|
||||
<?php View::renderPartial('why-us'); ?>
|
||||
<?php View::renderPartial('skills'); ?>
|
||||
<?php View::renderPartial('services'); ?>
|
||||
<?php View::renderPartial('work-process'); ?>
|
||||
<?php View::renderPartial('call-to-action'); ?>
|
||||
<?php View::renderPartial('team'); ?>
|
||||
<?php View::renderPartial('testimonials'); ?>
|
||||
<?php View::renderPartial('helpdeskplus-pricing'); ?>
|
||||
<?php View::renderPartial('contact'); ?>
|
||||
<?php View::renderPartial('faq'); ?>
|
||||
|
||||
|
||||
<!-- ======= Hero Section ======= -->
|
||||
<section id="hero" class="hero section dark-background">
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
|
||||
<!-- Text Content -->
|
||||
<div class="col-lg-6 order-2 order-lg-1 d-flex flex-column justify-content-center" data-aos="zoom-out">
|
||||
<h1>Tech Strategy Meets Business Reality</h1>
|
||||
<p>Since 2002, Wizdom has helped organizations align IT with what actually matters: results.</p>
|
||||
<div class="d-flex">
|
||||
<a href="#contact" class="btn-get-started">Get in Touch</a>
|
||||
<a href="https://www.youtube.com/watch?v=Y7f98aduVJ8" class="glightbox btn-watch-video d-flex align-items-center">
|
||||
<i class="bi bi-play-circle"></i><span>Watch Overview</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero Image -->
|
||||
<div class="col-lg-6 order-1 order-lg-2 hero-img" data-aos="zoom-out" data-aos-delay="200">
|
||||
<img src="assets/img/hero-img.png" class="img-fluid animated" alt="Wizdom Networks Hero Image">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Hero Section -->
|
||||
|
||||
<!-- ======= Clients Section ======= -->
|
||||
<section id="clients" class="clients section light-background">
|
||||
<div class="container" data-aos="zoom-in">
|
||||
|
||||
<div class="swiper init-swiper clients-slider">
|
||||
<script type="application/json" class="swiper-config">
|
||||
{
|
||||
"loop": true,
|
||||
"speed": 600,
|
||||
"autoplay": {
|
||||
"delay": 5000,
|
||||
"disableOnInteraction": false
|
||||
},
|
||||
"slidesPerView": "auto",
|
||||
"breakpoints": {
|
||||
"320": {
|
||||
"slidesPerView": 2,
|
||||
"spaceBetween": 40
|
||||
},
|
||||
"480": {
|
||||
"slidesPerView": 3,
|
||||
"spaceBetween": 60
|
||||
},
|
||||
"640": {
|
||||
"slidesPerView": 4,
|
||||
"spaceBetween": 80
|
||||
},
|
||||
"992": {
|
||||
"slidesPerView": 6,
|
||||
"spaceBetween": 120
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="swiper-wrapper align-items-center">
|
||||
<!-- 6 unique logos -->
|
||||
<div class="swiper-slide"><img src="assets/img/clients/bbbst-logo.webp" class="img-fluid" alt="Big Brothers & Big Sisters of Toronto"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/bellmedia-logo.webp" class="img-fluid" alt="Bell Media"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ibm-logo.webp" class="img-fluid" alt="IBM"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/Microsoft-logo.webp" class="img-fluid" alt="Microsoft"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/nissan-logo.webp" class="img-fluid" alt="Nissan"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ontario-logo.webp" class="img-fluid" alt="Government of Ontario"></div>
|
||||
<!-- repeat same 6 for looping -->
|
||||
<div class="swiper-slide"><img src="assets/img/clients/celestica-logo.webp" class="img-fluid" alt="Celestica"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/smartcentres-logo.webp" class="img-fluid" alt="SMART CENTRES REIT"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/cabip-logo.webp" class="img-fluid" alt="Canadian Association of Black Insurance Professionals"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/TZLogo-4K.webp" class="img-fluid" alt="Toronto Zoo"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/von-logo.webp" class="img-fluid" alt="Victorian Order of Nurses"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ccah-logo.webp" class="img-fluid" alt="Canadian Caribbean Association of Halton"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<!-- End Clients Section -->
|
||||
<!-- ======= About Section ======= -->
|
||||
<section id="about" class="about section">
|
||||
|
||||
<!-- Section Title -->
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>About Us</h2>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-6 content" data-aos="fade-up" data-aos-delay="100">
|
||||
<p>
|
||||
We’re not your average IT consultancy — and that’s the point.
|
||||
At Wizdom, we blend sharp technical expertise with grounded business sense.
|
||||
For over 20 years, we've helped clients design smarter infrastructure, manage complex projects, and adapt to change without missing a beat.
|
||||
</p>
|
||||
<ul>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>Infrastructure strategy meets execution — without the buzzwords.</span></li>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>We don’t just work with tech — we translate it into results.</span></li>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>Collaborative, transparent, and battle-tested since 2002.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="200">
|
||||
<p>
|
||||
Whether you need an architect, a troubleshooter, or a translator between tech and executive — we've been that partner.
|
||||
Our engagements go beyond checklists — we help build what's next.
|
||||
</p>
|
||||
<a href="#services" class="read-more"><span>Explore Our Services</span><i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- End About Section -->
|
||||
|
||||
<!-- ======= Why Us Section ======= -->
|
||||
<section id="why-us" class="section why-us light-background" data-builder="section">
|
||||
<div class="container-fluid">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-7 d-flex flex-column justify-content-center order-2 order-lg-1">
|
||||
|
||||
<div class="content px-xl-5" data-aos="fade-up" data-aos-delay="100">
|
||||
<h3><span>Why Organizations Choose </span><strong>Wizdom Networks</strong></h3>
|
||||
<p>
|
||||
There’s no shortage of IT consultants out there. But few bring the same mix of creativity, pragmatism, and proven experience. We do.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-container px-xl-5" data-aos="fade-up" data-aos-delay="200">
|
||||
|
||||
<div class="faq-item faq-active">
|
||||
<h3><span>01</span> What makes Wizdom different?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We combine deep technical chops with strategic insight and hands-on execution. No fluff. No shortcuts.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
<div class="faq-item">
|
||||
<h3><span>02</span> Who do you typically work with?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We support SMBs and mid-market orgs across retail, public sector, professional services, and non-profits — but we scale easily into enterprise territory.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
<div class="faq-item">
|
||||
<h3><span>03</span> Do you offer flexible or project-based engagements?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Absolutely. From one-off projects to long-term partnerships, we adapt to your business needs and growth stage.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5 order-1 order-lg-2 why-us-img">
|
||||
<img src="assets/img/why-us.png" class="img-fluid" alt="" data-aos="zoom-in" data-aos-delay="100">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Why Us Section -->
|
||||
<!-- ======= Skills Section ======= -->
|
||||
<section id="skills" class="skills section">
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-lg-6 d-flex align-items-center">
|
||||
<img src="assets/img/illustration/illustration-10.webp" class="img-fluid" alt="">
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 pt-4 pt-lg-0 content">
|
||||
<h3>Expertise Across Core IT Domains</h3>
|
||||
<p class="fst-italic">
|
||||
Our team is fluent in the systems, protocols, and technologies that keep your organization productive and secure.
|
||||
</p>
|
||||
|
||||
<div class="skills-content skills-animation">
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Active Directory & Exchange</span> <i class="val">100%</i></span>
|
||||
<div class="progress-bar-wrap">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Virtualization & Infrastructure</span> <i class="val">95%</i></span>
|
||||
<div class="progress-bar-wrap">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="95" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Network Design & Security</span> <i class="val">90%</i></span>
|
||||
<div class="progress-bar-wrap">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="90" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Cloud Strategy & M365</span> <i class="val">85%</i></span>
|
||||
<div class="progress-bar-wrap">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="85" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- End Skills Section -->
|
||||
<!-- ======= Services Section ======= -->
|
||||
<section id="services" class="services section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Services</h2>
|
||||
<p>Discover how Wizdom Networks transforms businesses through modern IT consulting and powerful digital infrastructure solutions.</p>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-5">
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="200">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-briefcase"></i></div>
|
||||
<h4><a href="#" class="stretched-link">IT Strategy & Alignment</a></h4>
|
||||
<p>Align your technology with your business roadmap — not the other way around.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="300">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-card-checklist"></i></div>
|
||||
<h4><a href="#" class="stretched-link">Project & Implementation Support</a></h4>
|
||||
<p>From kickoff to go-live, we keep your projects moving and your outcomes clear.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="400">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-bar-chart"></i></div>
|
||||
<h4><a href="#" class="stretched-link">Managed IT Services</a></h4>
|
||||
<p>Full-service support for infrastructure, endpoints, cloud, and more.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="500">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-binoculars"></i></div>
|
||||
<h4><a href="#" class="stretched-link">Network & Infrastructure Design</a></h4>
|
||||
<p>Firewalls, cabling, wireless, data centers — optimized and future-ready.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="600">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-brightness-high"></i></div>
|
||||
<h4><a href="#" class="stretched-link">Training & Documentation</a></h4>
|
||||
<p>Executive briefings. End-user workshops. Tailored sessions that stick.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="700">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-calendar4-week"></i></div>
|
||||
<h4><a href="#" class="stretched-link">Branding & Web Consulting</a></h4>
|
||||
<p>From domain to delivery — we help you tell your story and back it up with solid tech.</p>
|
||||
</div>
|
||||
</div><!-- End Service Item -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- ======= End Services Section ======= -->
|
||||
<!-- ======= Work Process Section ======= -->
|
||||
<section id="work-process" class="work-process section">
|
||||
|
||||
<!-- Section Title -->
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Work Process</h2>
|
||||
<p>Our proven approach helps organizations move faster, stay secure, and grow smarter.</p>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="row gy-5">
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-1.webp" alt="Step 1" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">01</div>
|
||||
<h3>Discovery & Assessment</h3>
|
||||
<p>We begin with a collaborative review of your systems, needs, risks, and goals to chart the best path forward.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Infrastructure Audits</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Cloud Readiness</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Security Gaps</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Step 1 -->
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="300">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-2.webp" alt="Step 2" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">02</div>
|
||||
<h3>Planning & Design</h3>
|
||||
<p>We design robust solutions aligned with your budget, timelines, and business objectives — built for scale and longevity.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>System Architecture</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Migration Planning</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Compliance Considerations</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Step 2 -->
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="400">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-3.webp" alt="Step 3" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">03</div>
|
||||
<h3>Implementation & Support</h3>
|
||||
<p>From deployment to post-project monitoring, Wizdom Networks ensures success with hands-on support every step of the way.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Project Execution</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Training & Onboarding</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>HelpDesk+ Ongoing Support</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Step 3 -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Work Process Section -->
|
||||
<!-- ======= Call To Action Section ======= -->
|
||||
<section id="call-to-action" class="call-to-action section dark-background">
|
||||
|
||||
<img src="assets/img/bg/bg-8.webp" alt="Call to Action Background">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row" data-aos="zoom-in" data-aos-delay="100">
|
||||
<div class="col-xl-9 text-center text-xl-start">
|
||||
<h3>Let’s Make Tech Make Sense</h3>
|
||||
<p>Whether you're solving today’s fires or planning for tomorrow’s growth, we’d love to hear your story — and help write the next chapter.</p>
|
||||
</div>
|
||||
<div class="col-xl-3 cta-btn-container text-center">
|
||||
<a class="cta-btn align-middle" href="#contact">Get In Touch</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End Call To Action Section -->
|
||||
<!-- ======= Portfolio Section ======= -->
|
||||
<!-- ======= End Portfolio Section ======= -->
|
||||
|
||||
<!-- ======= Team Section ======= -->
|
||||
<section id="team" class="team section">
|
||||
|
||||
<!-- Section Title -->
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Team</h2>
|
||||
<p>The people behind Wizdom Networks — technical depth, business vision, and proven execution.</p>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/sa-media-office.webp" class="img-fluid" alt="Essae B."></div>
|
||||
<div class="member-info">
|
||||
<h4>Essae B.</h4>
|
||||
<span>Founder & Principal Consultant</span>
|
||||
<p>20+ years leading IT strategy, infrastructure, and operations for business and public sector clients.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Team Member -->
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/sb-linkedin2.webp" class="img-fluid" alt="Operations Lead"></div>
|
||||
<div class="member-info">
|
||||
<h4>Jodie R.</h4>
|
||||
<span>Chief Operations Officer</span>
|
||||
<p>Drives delivery timelines, project planning, and stakeholder communications across key initiatives.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Team Member -->
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="300">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/person-m-6.webp" class="img-fluid" alt="Senior Network Engineer"></div>
|
||||
<div class="member-info">
|
||||
<h4>Martin C.</h4>
|
||||
<span>Senior Network Engineer</span>
|
||||
<p>Expert in firewalls, routing, structured cabling, and end-to-end IT systems architecture.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Team Member -->
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="400">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/person-f-4.webp" class="img-fluid" alt="Client Success Lead"></div>
|
||||
<div class="member-info">
|
||||
<h4>Tina R.</h4>
|
||||
<span>Client Success Lead</span>
|
||||
<p>Helps clients define priorities, align services, and ensure satisfaction across all engagements.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- End Team Member -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End Team Section -->
|
||||
<!-- ======= Testimonials Section ======= -->
|
||||
<section id="testimonials" class="testimonials section">
|
||||
|
||||
<!-- Section Title -->
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Testimonials</h2>
|
||||
<p>Feedback from our clients and collaborators.</p>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
|
||||
<div class="swiper init-swiper">
|
||||
<script type="application/json" class="swiper-config">
|
||||
{
|
||||
"loop": true,
|
||||
"speed": 600,
|
||||
"autoplay": {
|
||||
"delay": 5000
|
||||
},
|
||||
"slidesPerView": "auto",
|
||||
"pagination": {
|
||||
"el": ".swiper-pagination",
|
||||
"type": "bullets",
|
||||
"clickable": true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="swiper-wrapper">
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="assets/img/person/person-m-9.webp" class="testimonial-img" alt="">
|
||||
<h3>Saul Goodman</h3>
|
||||
<h4>CEO & Founder</h4>
|
||||
<div class="stars">
|
||||
<i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<p>
|
||||
<i class="bi bi-quote quote-icon-left"></i>
|
||||
<span>Wizdom Networks delivered exactly what we needed—on time, on budget, and with serious technical insight.</span>
|
||||
<i class="bi bi-quote quote-icon-right"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- End testimonial item -->
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="assets/img/person/person-f-5.webp" class="testimonial-img" alt="">
|
||||
<h3>Sara Wilsson</h3>
|
||||
<h4>Designer</h4>
|
||||
<div class="stars">
|
||||
<i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<p>
|
||||
<i class="bi bi-quote quote-icon-left"></i>
|
||||
<span>Their attention to detail and responsiveness made this one of the smoothest projects I’ve ever been part of.</span>
|
||||
<i class="bi bi-quote quote-icon-right"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- End testimonial item -->
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="assets/img/person/person-f-12.webp" class="testimonial-img" alt="">
|
||||
<h3>Jena Karlis</h3>
|
||||
<h4>Store Owner</h4>
|
||||
<div class="stars">
|
||||
<i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<p>
|
||||
<i class="bi bi-quote quote-icon-left"></i>
|
||||
<span>When you want it done right the first time, these are the people you call. Incredible service.</span>
|
||||
<i class="bi bi-quote quote-icon-right"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- End testimonial item -->
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="assets/img/person/person-m-12.webp" class="testimonial-img" alt="">
|
||||
<h3>Matt Brandon</h3>
|
||||
<h4>Freelancer</h4>
|
||||
<div class="stars">
|
||||
<i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<p>
|
||||
<i class="bi bi-quote quote-icon-left"></i>
|
||||
<span>Reliable, honest, and technically brilliant. I’d recommend Wizdom to any business that depends on IT.</span>
|
||||
<i class="bi bi-quote quote-icon-right"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- End testimonial item -->
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="assets/img/person/person-m-13.webp" class="testimonial-img" alt="">
|
||||
<h3>John Larson</h3>
|
||||
<h4>Entrepreneur</h4>
|
||||
<div class="stars">
|
||||
<i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i><i class="bi bi-star-fill"></i>
|
||||
</div>
|
||||
<p>
|
||||
<i class="bi bi-quote quote-icon-left"></i>
|
||||
<span>We worked with Wizdom Networks on three separate engagements. All of them exceeded expectations.</span>
|
||||
<i class="bi bi-quote quote-icon-right"></i>
|
||||
</p>
|
||||
</div>
|
||||
</div><!-- End testimonial item -->
|
||||
|
||||
</div>
|
||||
<div class="swiper-pagination"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End Testimonials Section -->
|
||||
<!-- ======= Pricing Section ======= -->
|
||||
<section id="pricing" class="pricing section">
|
||||
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>HelpDesk+ Plans</h2>
|
||||
<p>Reliable remote support for your growing business — no surprises, just dependable IT coverage.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="row gy-4" data-aos="fade-up" data-aos-delay="100">
|
||||
|
||||
<!-- Starter Plan -->
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="pricing-item">
|
||||
<h3>Starter</h3>
|
||||
<h4><sup>$</sup>299<span>/mo</span></h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-check"></i> Up to 5 devices</li>
|
||||
<li><i class="bi bi-check"></i> Email + remote desktop support</li>
|
||||
<li><i class="bi bi-check"></i> Business hours coverage</li>
|
||||
<li class="na"><i class="bi bi-x"></i> <span>24/7 emergency support</span></li>
|
||||
<li class="na"><i class="bi bi-x"></i> <span>Onboarding assistance</span></li>
|
||||
</ul>
|
||||
<a href="#contact" class="buy-btn">Get Started</a>
|
||||
</div>
|
||||
</div><!-- End Pricing Item -->
|
||||
|
||||
<!-- Business Plan -->
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="pricing-item featured">
|
||||
<h3>Business</h3>
|
||||
<h4><sup>$</sup>499<span>/mo</span></h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-check"></i> Up to 15 devices</li>
|
||||
<li><i class="bi bi-check"></i> All Starter features</li>
|
||||
<li><i class="bi bi-check"></i> 24/7 emergency support</li>
|
||||
<li><i class="bi bi-check"></i> Device monitoring & patching</li>
|
||||
<li><i class="bi bi-check"></i> Onboarding support</li>
|
||||
</ul>
|
||||
<a href="#contact" class="buy-btn">Choose Plan</a>
|
||||
</div>
|
||||
</div><!-- End Pricing Item -->
|
||||
|
||||
<!-- Enterprise Plan -->
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="pricing-item">
|
||||
<h3>Enterprise</h3>
|
||||
<h4><sup>$</sup>899<span>/mo</span></h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-check"></i> Unlimited users</li>
|
||||
<li><i class="bi bi-check"></i> Dedicated support lead</li>
|
||||
<li><i class="bi bi-check"></i> Quarterly strategy review</li>
|
||||
<li><i class="bi bi-check"></i> Full reporting & auditing</li>
|
||||
<li><i class="bi bi-check"></i> SLA guarantees</li>
|
||||
</ul>
|
||||
<a href="#contact" class="buy-btn">Contact Sales</a>
|
||||
</div>
|
||||
</div><!-- End Pricing Item -->
|
||||
|
||||
<!-- Retail Variant -->
|
||||
<div class="col-lg-3 col-md-6">
|
||||
<div class="pricing-item">
|
||||
<h3>Retail/Franchise</h3>
|
||||
<h4><sup>$</sup>249<span>/store/mo</span></h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-check"></i> Optimized for retail turnover</li>
|
||||
<li><i class="bi bi-check"></i> POS + Wi-Fi + camera support</li>
|
||||
<li><i class="bi bi-check"></i> Rapid seasonal onboarding</li>
|
||||
<li><i class="bi bi-check"></i> Custom site inventory</li>
|
||||
<li><i class="bi bi-check"></i> Multi-location reporting</li>
|
||||
</ul>
|
||||
<a href="#contact" class="buy-btn">Talk to Us</a>
|
||||
</div>
|
||||
</div><!-- End Pricing Item -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End Pricing Section -->
|
||||
<!-- ======= Contact Section ======= -->
|
||||
<section id="contact" class="contact section">
|
||||
|
||||
<!-- Section Title -->
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Contact</h2>
|
||||
<p>Let’s connect. Whether it’s a project, support request, or general inquiry — we’re ready to help.</p>
|
||||
</div><!-- End Section Title -->
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-5">
|
||||
<div class="info-wrap">
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="200">
|
||||
<i class="bi bi-geo-alt flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Address</h3>
|
||||
<p>Mississauga, ON, Canada</p>
|
||||
</div>
|
||||
</div><!-- End Info Item -->
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="300">
|
||||
<i class="bi bi-telephone flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Call Us</h3>
|
||||
<p>+1 (416) 873-9473</p>
|
||||
<p>+1 (416) USE-WISE</p>
|
||||
</div>
|
||||
</div><!-- End Info Item -->
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="400">
|
||||
<i class="bi bi-envelope flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Email Us</h3>
|
||||
<p>concierge@wizdom.ca</p>
|
||||
</div>
|
||||
</div><!-- End Info Item -->
|
||||
|
||||
<iframe src="https://maps.google.com/maps?q=Mississauga,%20ON&t=&z=13&ie=UTF8&iwloc=&output=embed" frameborder="0" style="border:0; width: 100%; height: 270px;" allowfullscreen="" loading="lazy"></iframe>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-7">
|
||||
|
||||
|
||||
|
||||
<?php
|
||||
|
||||
|
||||
$hasSuccess = $_SESSION['contact_success'] ?? false;
|
||||
$hasError = $_SESSION['contact_error'] ?? null;
|
||||
|
||||
unset($_SESSION['contact_success']);
|
||||
unset($_SESSION['contact_error']);
|
||||
|
||||
if ($hasSuccess): ?>
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
✅ Thank you! Your message has been received. We'll be in touch shortly.
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php elseif (!empty($hasError)): ?>
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
⚠️ <?= htmlspecialchars($hasError) ?>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="/contact" method="POST" class="needs-validation" novalidate>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<label for="first_name" class="pb-2">First Name</label>
|
||||
<input type="text" name="first_name" id="first_name" class="form-control" required>
|
||||
<div class="invalid-feedback">First name is required.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="last_name" class="pb-2">Last Name</label>
|
||||
<input type="text" name="last_name" id="last_name" class="form-control" required>
|
||||
<div class="invalid-feedback">Last name is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="pb-2">Your Email</label>
|
||||
<input type="email" name="email" id="email" class="form-control" required>
|
||||
<div class="invalid-feedback">Please enter a valid email address.</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="pb-2">Phone Number <small class="text-muted">(required to qualify submissions)</small></label>
|
||||
<input type="tel" name="phone" id="phone" class="form-control" required>
|
||||
<div class="invalid-feedback">Phone number is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<label for="subject" class="pb-2">Subject</label>
|
||||
<input type="text" name="subject" id="subject" class="form-control" required placeholder="Subject of your inquiry">
|
||||
<div class="invalid-feedback">Subject is required.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<label for="message" class="pb-2">Message</label>
|
||||
<textarea name="message" id="message" rows="5" class="form-control" required></textarea>
|
||||
<div class="invalid-feedback">Please enter your message.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<button type="submit" class="btn btn-primary">Send Message</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</div><!-- End Contact Form -->
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End Contact Section -->
|
||||
<!-- ======= FAQ Section ======= -->
|
||||
<section id="faq" class="why-us section light-background">
|
||||
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<p>Answers to common questions about our services, pricing, and process.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="faq-container">
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="100">
|
||||
<h3><span>1.</span> What industries do you work with?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We work across sectors including healthcare, nonprofit, retail, cannabis, government, and professional services. Our team adapts quickly to unique industry needs.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="200">
|
||||
<h3><span>2.</span> How do HelpDesk+ support plans work?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Our support plans include unlimited remote assistance, proactive patching, monitoring, and device onboarding — all tailored to your plan tier.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="300">
|
||||
<h3><span>3.</span> Can I get a one-time Stealthascope audit?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Yes. Our Basic and Pro plans are one-time engagements, and Enterprise audits can include recurring follow-ups if needed.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="400">
|
||||
<h3><span>4.</span> Do you provide onsite service?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We primarily offer remote support across North America. Onsite visits are available in the GTA or by special arrangement elsewhere.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="500">
|
||||
<h3><span>5.</span> How fast is your response time?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Our average first response is under 1 hour during business hours. Emergency support (24/7) is included in Business+ plans and above.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End FAQ Section -->
|
||||
<!-- ======= Footer ======= -->
|
||||
<footer id="footer" class="footer">
|
||||
|
||||
<!-- Newsletter -->
|
||||
<div class="footer-newsletter">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center text-center">
|
||||
<div class="col-lg-6">
|
||||
<h4>Join Our Newsletter</h4>
|
||||
<p>Get updates on new services, insights, and best practices from the Wizdom Networks team.</p>
|
||||
<form action="forms/newsletter.php" method="post" class="php-email-form">
|
||||
<div class="newsletter-form">
|
||||
<input type="email" name="email" placeholder="Your Email">
|
||||
<input type="submit" value="Subscribe">
|
||||
</div>
|
||||
<div class="loading">Loading</div>
|
||||
<div class="error-message"></div>
|
||||
<div class="sent-message">Subscription successful. Thank you!</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer Links & Contact -->
|
||||
<div class="container footer-top">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-4 col-md-6 footer-about">
|
||||
<a href="/" class="d-flex align-items-center">
|
||||
<span class="sitename">Wizdom Networks</span>
|
||||
</a>
|
||||
<div class="footer-contact pt-3">
|
||||
<p>Mississauga, ON, Canada</p>
|
||||
<p><strong>Phone:</strong> <span>416-USE-WISE</span></p>
|
||||
<p><strong>Email:</strong> <span>concierge@wizdom.ca</span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Quick Links</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#hero">Home</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#about">About</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Services</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-2 col-md-3 footer-links">
|
||||
<h4>Services</h4>
|
||||
<ul>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">IT Strategy & Alignment</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">Cloud Strategy</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#services">HelpDesk+</a></li>
|
||||
<li><i class="bi bi-chevron-right"></i> <a href="#stealthascope-pricing">Stealthascope</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4 col-md-12">
|
||||
<h4>Follow Us</h4>
|
||||
<p>Stay connected with Wizdom Networks on social.</p>
|
||||
<div class="social-links d-flex">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-facebook"></i></a>
|
||||
<a href="#"><i class="bi bi-instagram"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container copyright text-center mt-4">
|
||||
<p>© <span>Copyright</span> <strong class="px-1 sitename">Wizdom Networks</strong>. <span>All Rights Reserved</span></p>
|
||||
</div>
|
||||
|
||||
</footer><!-- End Footer -->
|
||||
|
||||
<script>
|
||||
(() => {
|
||||
'use strict'
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<section class="newsletter-action section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-envelope-x text-warning" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">Unsubscribe from “Words of Wizdom”</h2>
|
||||
<p class="lead">We’re sorry to see you go. If you’d like to stop receiving our emails, confirm below.</p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/newsletter-thanks.webp" alt="Unsubscribe" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<form action="/unsubscribe" method="post" class="row justify-content-center mt-3" style="max-width: 600px; margin: 0 auto;">
|
||||
<input type="hidden" name="email" value="<?= htmlspecialchars($email) ?>">
|
||||
|
||||
<div class="col-12 mb-3">
|
||||
<textarea name="unsubscribe_reason" class="form-control" rows="3" placeholder="Optional: Let us know why you’re leaving"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-danger">Unsubscribe Me</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="/" class="btn btn-outline-secondary">Never mind — take me back</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<section class="newsletter-action section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-x-circle text-danger" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">Unsubscribe Failed</h2>
|
||||
<p class="lead"><?= htmlspecialchars($reason) ?></p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/newsletter-thanks.webp" alt="Error" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="/" class="btn btn-outline-primary">Return to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<section class="newsletter-action section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-check-circle text-success" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">You’ve Been Unsubscribed</h2>
|
||||
<p class="lead">
|
||||
We’ve removed <strong><?= htmlspecialchars($email) ?></strong> from our mailing list.
|
||||
<?php if (!empty($alreadyUnsubscribed)): ?>
|
||||
(You were already unsubscribed.)
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/newsletter-thanks.webp" alt="Unsubscribed" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="/" class="btn btn-primary">Return to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
/**
|
||||
* File: verify_failed.php
|
||||
* Version: 1.1
|
||||
* Path: /resources/views/pages/verify_failed.php
|
||||
* Purpose: Displays failure message and allows resend of verification links.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
?>
|
||||
<section class="verify-section section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-x-circle text-danger" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">Verification Failed</h2>
|
||||
<p class="lead">
|
||||
<?= htmlspecialchars($reason ?? 'Something went wrong. Please try again later.') ?>
|
||||
</p>
|
||||
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/newsletter-thanks.webp" alt="Error" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<!-- Resend Verification Form -->
|
||||
<div class="mt-4">
|
||||
<h5>Resend Verification</h5>
|
||||
<p class="mb-2">If you entered the correct email and didn't receive your verification link, you can request a new one below.</p>
|
||||
<form action="/resend-verification" method="post" class="row justify-content-center" style="max-width: 500px; margin: 0 auto;">
|
||||
<div class="col-12 mb-2">
|
||||
<input type="email" name="email" class="form-control" placeholder="Enter your email" required>
|
||||
</div>
|
||||
<div class="col-12 mb-2">
|
||||
<select name="type" class="form-select" required>
|
||||
<option value="">Select type...</option>
|
||||
<option value="newsletter">Newsletter</option>
|
||||
<option value="contact">Contact Form</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-primary">Resend Link</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<a href="/" class="btn btn-outline-primary">Return to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
// File: verify_success.php
|
||||
// Version: 1.2
|
||||
// Purpose: Displays confirmation message after email verification and optional personalization update.
|
||||
?>
|
||||
<section class="verify-section section pt-5">
|
||||
<div class="container text-center">
|
||||
<div class="icon mb-4">
|
||||
<i class="bi bi-check-circle text-success" style="font-size: 4rem;"></i>
|
||||
</div>
|
||||
<h2 class="mb-3">Email Verified</h2>
|
||||
<p class="lead">
|
||||
Your <?= htmlspecialchars($type) ?> email has been successfully verified.<br>
|
||||
Thank you for confirming your email address.
|
||||
</p>
|
||||
|
||||
<!-- Optional Image -->
|
||||
<div class="my-4">
|
||||
<img src="/assets/img/personalize-email-verified.webp" alt="Welcome!" class="img-fluid rounded shadow" style="max-width: 400px;">
|
||||
</div>
|
||||
|
||||
<!-- Alert Message -->
|
||||
<?php if (!empty($message)): ?>
|
||||
<div class="alert alert-success mt-4"><?= htmlspecialchars($message) ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($type === 'newsletter' && empty($message)): ?>
|
||||
<!-- Personalization Form -->
|
||||
<div class="mt-4">
|
||||
<p class="mb-3">Would you like us to personalize future emails with your name?</p>
|
||||
<form action="/subscriber/update" method="post" class="row justify-content-center" style="max-width: 600px; margin: 0 auto;">
|
||||
<input type="hidden" name="email" value="<?= htmlspecialchars($_GET['email'] ?? '') ?>">
|
||||
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="text" name="first_name" class="form-control" placeholder="First Name">
|
||||
</div>
|
||||
<div class="col-md-6 mb-2">
|
||||
<input type="text" name="last_name" class="form-control" placeholder="Last Name">
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
<button type="submit" class="btn btn-secondary">Update My Info</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Return Button -->
|
||||
<div class="mt-5">
|
||||
<a href="/" class="btn btn-outline-primary">Return to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<!-- ======= About Section ======= -->
|
||||
<section id="about" class="about section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>About Us</h2>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
<div class="col-lg-6 content" data-aos="fade-up" data-aos-delay="100">
|
||||
<p>
|
||||
We’re not your average IT consultancy — and that’s the point.
|
||||
At Wizdom, we blend sharp technical expertise with grounded business sense.
|
||||
For over 20 years, we've helped clients design smarter infrastructure, manage complex projects, and adapt to change without missing a beat.
|
||||
</p>
|
||||
<ul>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>Infrastructure strategy meets execution — without the buzzwords.</span></li>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>We don’t just work with tech — we translate it into results.</span></li>
|
||||
<li><i class="bi bi-check2-circle"></i> <span>Collaborative, transparent, and battle-tested since 2002.</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="200">
|
||||
<p>
|
||||
Whether you need an architect, a troubleshooter, or a translator between tech and executive — we've been that partner.
|
||||
Our engagements go beyond checklists — we help build what's next.
|
||||
</p>
|
||||
<a href="#services" class="read-more"><span>Explore Our Services</span><i class="bi bi-arrow-right"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End About Section -->
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<!-- ======= Call To Action Section ======= -->
|
||||
<section id="call-to-action" class="call-to-action section dark-background">
|
||||
|
||||
<img src="assets/img/bg/bg-8.webp" alt="Call to Action Background">
|
||||
|
||||
<div class="container">
|
||||
<div class="row" data-aos="zoom-in" data-aos-delay="100">
|
||||
<div class="col-xl-9 text-center text-xl-start">
|
||||
<h3>Let’s Make Tech Make Sense</h3>
|
||||
<p>Whether you're solving today’s fires or planning for tomorrow’s growth, we’d love to hear your story — and help write the next chapter.</p>
|
||||
</div>
|
||||
<div class="col-xl-3 cta-btn-container text-center">
|
||||
<a class="cta-btn align-middle" href="#contact">Get In Touch</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</section><!-- End Call To Action Section -->
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<!-- ======= Clients Section ======= -->
|
||||
<section id="clients" class="clients section light-background">
|
||||
<div class="container" data-aos="zoom-in">
|
||||
|
||||
<div class="swiper init-swiper clients-slider">
|
||||
<script type="application/json" class="swiper-config">
|
||||
{
|
||||
"loop": true,
|
||||
"speed": 600,
|
||||
"autoplay": {
|
||||
"delay": 5000,
|
||||
"disableOnInteraction": false
|
||||
},
|
||||
"slidesPerView": "auto",
|
||||
"breakpoints": {
|
||||
"320": { "slidesPerView": 2, "spaceBetween": 40 },
|
||||
"480": { "slidesPerView": 3, "spaceBetween": 60 },
|
||||
"640": { "slidesPerView": 4, "spaceBetween": 80 },
|
||||
"992": { "slidesPerView": 6, "spaceBetween": 120 }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="swiper-wrapper align-items-center">
|
||||
<div class="swiper-slide"><img src="assets/img/clients/bbbst-logo.webp" class="img-fluid" alt="Big Brothers & Big Sisters of Toronto"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/bellmedia-logo.webp" class="img-fluid" alt="Bell Media"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ibm-logo.webp" class="img-fluid" alt="IBM"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/Microsoft-logo.webp" class="img-fluid" alt="Microsoft"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/nissan-logo.webp" class="img-fluid" alt="Nissan"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ontario-logo.webp" class="img-fluid" alt="Government of Ontario"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/celestica-logo.webp" class="img-fluid" alt="Celestica"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/smartcentres-logo.webp" class="img-fluid" alt="SMART CENTRES REIT"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/cabip-logo.webp" class="img-fluid" alt="Canadian Association of Black Insurance Professionals"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/TZLogo-4K.webp" class="img-fluid" alt="Toronto Zoo"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/von-logo.webp" class="img-fluid" alt="Victorian Order of Nurses"></div>
|
||||
<div class="swiper-slide"><img src="assets/img/clients/ccah-logo.webp" class="img-fluid" alt="Canadian Caribbean Association of Halton"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section><!-- End Clients Section -->
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
/**
|
||||
* File: contact.php
|
||||
* Version: 1.3
|
||||
* Path: /resources/views/partials/contact.php
|
||||
* Purpose: Renders the contact form with inline error feedback and optional newsletter signup.
|
||||
* Project: Wizdom Networks Website
|
||||
*/
|
||||
?>
|
||||
|
||||
<!-- ======= Contact Section ======= -->
|
||||
<section id="contact" class="contact section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Contact</h2>
|
||||
<p>Let’s connect. Reach out with questions, ideas, or project inquiries — we’ll respond quickly.</p>
|
||||
</div>
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="row gy-4">
|
||||
|
||||
<!-- Contact Info -->
|
||||
<div class="col-lg-5">
|
||||
<div class="info-wrap">
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="200">
|
||||
<i class="bi bi-geo-alt flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Address</h3>
|
||||
<p>Mississauga, Ontario<br>Canada</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="300">
|
||||
<i class="bi bi-telephone flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Call Us</h3>
|
||||
<p>416-USE-WISE<br>(416-873-9473)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-item d-flex" data-aos="fade-up" data-aos-delay="400">
|
||||
<i class="bi bi-envelope flex-shrink-0"></i>
|
||||
<div>
|
||||
<h3>Email Us</h3>
|
||||
<p>concierge@wizdom.ca</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<iframe src="https://maps.google.com/maps?q=Mississauga&t=&z=13&ie=UTF8&iwloc=&output=embed"
|
||||
frameborder="0"
|
||||
style="border:0; width: 100%; height: 270px;"
|
||||
allowfullscreen=""
|
||||
loading="lazy"
|
||||
referrerpolicy="no-referrer-when-downgrade"></iframe>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Contact Form -->
|
||||
<div class="col-lg-7">
|
||||
<form action="/contact" method="post" class="php-email-form" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="row gy-4">
|
||||
|
||||
<!-- Inline error message -->
|
||||
<?php if (!empty($_SESSION['contact_error'])): ?>
|
||||
<div class="col-12">
|
||||
<div class="alert alert-danger">
|
||||
<?= htmlspecialchars($_SESSION['contact_error']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php unset($_SESSION['contact_error']); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="first-name" class="pb-2">First Name</label>
|
||||
<input type="text" name="first_name" id="first-name" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="last-name" class="pb-2">Last Name</label>
|
||||
<input type="text" name="last_name" id="last-name" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="email" class="pb-2">Email</label>
|
||||
<input type="email" name="email" id="email" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label for="phone" class="pb-2">Phone</label>
|
||||
<input type="tel" name="phone" id="phone" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="subject" class="pb-2">Subject</label>
|
||||
<input type="text" name="subject" id="subject" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<label for="message" class="pb-2">Message</label>
|
||||
<textarea name="message" id="message" rows="10" class="form-control" required></textarea>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 form-check mt-3">
|
||||
<input class="form-check-input" type="checkbox" name="subscribe_newsletter" id="subscribe-newsletter">
|
||||
<label class="form-check-label" for="subscribe-newsletter">
|
||||
Sign me up for the Wizdom Networks newsletter
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="col-md-12 text-center">
|
||||
<div class="loading">Loading</div>
|
||||
<div class="error-message"></div>
|
||||
<div class="sent-message">Your message has been sent. Thank you!</div>
|
||||
<button type="submit">Send Message</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div><!-- End Form Column -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Contact Section -->
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<!-- ======= FAQ Section ======= -->
|
||||
<section id="faq" class="why-us section light-background">
|
||||
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<p>Answers to common questions about our services, pricing, and process.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="faq-container">
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="100">
|
||||
<h3><span>1.</span> What industries do you work with?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We work across sectors including healthcare, nonprofit, retail, cannabis, government, and professional services. Our team adapts quickly to unique industry needs.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="200">
|
||||
<h3><span>2.</span> How do HelpDesk+ support plans work?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Our support plans include unlimited remote assistance, proactive patching, monitoring, and device onboarding — all tailored to your plan tier.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="300">
|
||||
<h3><span>3.</span> Can I get a one-time Stealthascope audit?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Yes. Our Basic and Pro plans are one-time engagements, and Enterprise audits can include recurring follow-ups if needed.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="400">
|
||||
<h3><span>4.</span> Do you provide onsite service?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We primarily offer remote support across North America. Onsite visits are available in the GTA or by special arrangement elsewhere.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
<div class="faq-item" data-aos="fade-up" data-aos-delay="500">
|
||||
<h3><span>5.</span> How fast is your response time?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Our average first response is under 1 hour during business hours. Emergency support (24/7) is included in Business+ plans and above.</p>
|
||||
</div>
|
||||
<div class="faq-toggle"><i class="bi bi-chevron-down"></i></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</section><!-- End FAQ Section -->
|
||||
|
|
@ -1,40 +1,25 @@
|
|||
<?php
|
||||
<!-- ======= Hero Section ======= -->
|
||||
<section id="hero" class="hero section dark-background">
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
|
||||
/**
|
||||
* Hero Partial (v2)
|
||||
*
|
||||
* This partial dynamically loads a hero section based on the provided configuration.
|
||||
* It allows pages to enable or disable the hero and define custom styles.
|
||||
*
|
||||
* ## Configuration Options:
|
||||
* - `enabled` (bool) - Determines whether the hero is displayed (default: false)
|
||||
* - `title` (string) - Hero title text
|
||||
* - `description` (string) - Hero subtitle or description text
|
||||
* - `image` (string) - Background image URL (default: 'wizdom-about-definitions.jpg')
|
||||
* - `height` (string) - Height of the hero section (default: '50vh')
|
||||
* - `text_align` (string) - Text alignment ('left', 'center', 'right') (default: 'center')
|
||||
* - `overlay` (bool) - Apply a dark overlay to improve text readability (default: true)
|
||||
*/
|
||||
<!-- Text Content -->
|
||||
<div class="col-lg-6 order-2 order-lg-1 d-flex flex-column justify-content-center" data-aos="zoom-out">
|
||||
<h1>Tech Strategy Meets Business Reality</h1>
|
||||
<p>Since 2002, Wizdom has helped organizations align IT with what actually matters: results.</p>
|
||||
<div class="d-flex">
|
||||
<a href="#contact" class="btn-get-started">Get in Touch</a>
|
||||
<a href="https://www.youtube.com/watch?v=Y7f98aduVJ8" class="glightbox btn-watch-video d-flex align-items-center">
|
||||
<i class="bi bi-play-circle"></i><span>Watch Overview</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// Ensure hero visibility is explicitly enabled
|
||||
if (!isset($heroConfig) || empty($heroConfig['enabled'])) {
|
||||
return;
|
||||
}
|
||||
<!-- Hero Image -->
|
||||
<div class="col-lg-6 order-1 order-lg-2 hero-img" data-aos="zoom-out" data-aos-delay="200">
|
||||
<img src="assets/img/hero-img.png" class="img-fluid animated" alt="Wizdom Networks Hero Image">
|
||||
</div>
|
||||
|
||||
$heroTitle = htmlspecialchars($heroConfig['title'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$heroDescription = htmlspecialchars($heroConfig['description'] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
$heroImage = htmlspecialchars($heroConfig['image'] ?? '/assets/images/wizdom-about-definitions.jpg', ENT_QUOTES, 'UTF-8');
|
||||
$heroHeight = htmlspecialchars($heroConfig['height'] ?? '50vh', ENT_QUOTES, 'UTF-8');
|
||||
$textAlign = htmlspecialchars($heroConfig['text_align'] ?? 'center', ENT_QUOTES, 'UTF-8');
|
||||
$overlay = $heroConfig['overlay'] ?? false;
|
||||
?>
|
||||
|
||||
<section class="hero-section" style="background-image: url('<?php echo $heroImage; ?>'); height: <?php echo $heroHeight; ?>;">
|
||||
<?php if ($overlay) : ?>
|
||||
<div class="hero-overlay"></div>
|
||||
<?php endif; ?>
|
||||
<div class="hero-content" style="text-align: <?php echo $textAlign; ?>;">
|
||||
<h1><?php echo $heroTitle; ?></h1>
|
||||
<p><?php echo $heroDescription; ?></p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</section><!-- End Hero Section -->
|
||||
|
|
|
|||
|
|
@ -1,16 +1,13 @@
|
|||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">Wizdom Networks</a>
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNav">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item"><a class="nav-link" href="/">Home</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/services">Services</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/about">About</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="/contact">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- ======= Header ======= -->
|
||||
<nav id="navbar" class="navmenu">
|
||||
<ul>
|
||||
<li><a class="nav-link scrollto active" href="#hero">Home</a></li>
|
||||
<li><a class="nav-link scrollto" href="#about">About</a></li>
|
||||
<li><a class="nav-link scrollto" href="#services">Services</a></li>
|
||||
<!-- <li><a class="nav-link scrollto" href="#portfolio">Portfolio</a></li> -->
|
||||
<li><a class="nav-link scrollto" href="#team">Team</a></li>
|
||||
<li><a class="nav-link scrollto" href="#contact">Contact</a></li>
|
||||
</ul>
|
||||
<i class="bi bi-list mobile-nav-toggle"></i>
|
||||
</nav>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,61 @@
|
|||
<!-- ======= Services Section ======= -->
|
||||
<section id="services" class="services section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Services</h2>
|
||||
<p>Discover how Wizdom Networks transforms businesses through modern IT consulting and powerful digital infrastructure solutions.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-5">
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="200">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-briefcase"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('IT Strategy & Alignment')" >IT Strategy & Alignment</a></h4>
|
||||
<p>Align your technology with your business roadmap — not the other way around.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="300">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-card-checklist"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('Project & Implementation Support')" >Project & Implementation Support</a></h4>
|
||||
<p>From kickoff to go-live, we keep your projects moving and your outcomes clear.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="400">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-bar-chart"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('Managed IT Services')" >Managed IT Services</a></h4>
|
||||
<p>Full-service support for infrastructure, endpoints, cloud, and more.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="500">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-binoculars"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('Network & Infrastructure Design')" >Network & Infrastructure Design</a></h4>
|
||||
<p>Firewalls, cabling, wireless, data centers — optimized and future-ready.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="600">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-brightness-high"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('Training & Documentation')" >Training & Documentation</a></h4>
|
||||
<p>Executive briefings. End-user workshops. Tailored sessions that stick.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xl-4 col-md-6" data-aos="zoom-in" data-aos-delay="700">
|
||||
<div class="service-item">
|
||||
<div class="icon"><i class="bi bi-calendar4-week"></i></div>
|
||||
<h4><a href="#contact" class="stretched-link" onclick="prefillService('Branding & Web Consulting')" >Branding & Web Consulting</a></h4>
|
||||
<p>From domain to delivery — we help you tell your story and back it up with solid tech.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Services Section -->
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<!-- ======= Skills Section ======= -->
|
||||
<section id="skills" class="skills section">
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6 d-flex align-items-center">
|
||||
<img src="assets/img/illustration/illustration-10.webp" class="img-fluid" alt="">
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6 pt-4 pt-lg-0 content">
|
||||
<h3>Expertise Across Core IT Domains</h3>
|
||||
<p class="fst-italic">
|
||||
Our team is fluent in the systems, protocols, and technologies that keep your organization productive and secure.
|
||||
</p>
|
||||
|
||||
<div class="skills-content skills-animation">
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Active Directory & Exchange</span> <i class="val">100%</i></span>
|
||||
<div class="progress-bar-wrap"><div class="progress-bar" role="progressbar" aria-valuenow="100"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Virtualization & Infrastructure</span> <i class="val">95%</i></span>
|
||||
<div class="progress-bar-wrap"><div class="progress-bar" role="progressbar" aria-valuenow="95"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Network Design & Security</span> <i class="val">90%</i></span>
|
||||
<div class="progress-bar-wrap"><div class="progress-bar" role="progressbar" aria-valuenow="90"></div></div>
|
||||
</div>
|
||||
|
||||
<div class="progress">
|
||||
<span class="skill"><span>Cloud Strategy & M365</span> <i class="val">85%</i></span>
|
||||
<div class="progress-bar-wrap"><div class="progress-bar" role="progressbar" aria-valuenow="85"></div></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section><!-- End Skills Section -->
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<!-- ======= Team Section ======= -->
|
||||
<section id="team" class="team section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Team</h2>
|
||||
<p>The people behind Wizdom Networks — technical depth, business vision, and proven execution.</p>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/sa-media-office.webp" class="img-fluid" alt="Essae B."></div>
|
||||
<div class="member-info">
|
||||
<h4>Essae B.</h4>
|
||||
<span>Founder & Principal Consultant</span>
|
||||
<p>20+ years leading IT strategy, infrastructure, and operations for business and public sector clients.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/sb-linkedin2.webp" class="img-fluid" alt="Jodie R."></div>
|
||||
<div class="member-info">
|
||||
<h4>Jodie R.</h4>
|
||||
<span>Chief Operations Officer</span>
|
||||
<p>Drives delivery timelines, project planning, and stakeholder communications across key initiatives.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-6" data-aos="fade-up" data-aos-delay="300">
|
||||
<div class="team-member d-flex align-items-start">
|
||||
<div class="pic"><img src="assets/img/person/person-m-6.webp" class="img-fluid" alt="Martin C."></div>
|
||||
<div class="member-info">
|
||||
<h4>Martin C.</h4>
|
||||
<span>Senior Network Engineer</span>
|
||||
<p>Expert in firewalls, routing, structured cabling, and end-to-end IT systems architecture.</p>
|
||||
<div class="social">
|
||||
<a href="#"><i class="bi bi-twitter-x"></i></a>
|
||||
<a href="#"><i class="bi bi-linkedin"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add additional team members here if needed -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Team Section -->
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<!-- ======= Testimonials Section ======= -->
|
||||
<section id="testimonials" class="testimonials section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Testimonials</h2>
|
||||
<p>What our clients say about working with Wizdom Networks.</p>
|
||||
</div>
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="swiper init-swiper testimonials-slider">
|
||||
|
||||
<!-- Swiper Configuration JSON -->
|
||||
<script type="application/json" class="swiper-config">
|
||||
{
|
||||
"loop": true,
|
||||
"speed": 600,
|
||||
"autoplay": {
|
||||
"delay": 5000,
|
||||
"disableOnInteraction": false
|
||||
},
|
||||
"pagination": {
|
||||
"el": ".swiper-pagination",
|
||||
"clickable": true
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="swiper-wrapper">
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="/assets/img/testimonials/dionne-bowers.webp" class="testimonial-img" alt="Dionne B.">
|
||||
<p>"Wizdom delivered exactly what we needed — on time and without surprises. The team was professional, communicative, and solutions-driven."</p>
|
||||
<h3>Dionne B.</h3>
|
||||
<h4>President of the Board, CABIP</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="/assets/img/testimonials/sheldon-williams.webp" class="testimonial-img" alt="Sheldon W.">
|
||||
<p>"They didn’t just implement our tech — they helped us rethink our processes. Their strategic input was invaluable."</p>
|
||||
<h3>Sheldon W.</h3>
|
||||
<h4>President of the Board, CCAH</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="swiper-slide">
|
||||
<div class="testimonial-item">
|
||||
<img src="/assets/img/testimonials/richard-bailey.webp" class="testimonial-img" alt="Richard B.">
|
||||
<p>"Wizdom has been instrumental in keeping our cannabis store running smoothly — especially during compliance and tech transitions."</p>
|
||||
<h3>Richard B.</h3>
|
||||
<h4>Retail Store Owner, Cannabis Industry</h4>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="swiper-pagination"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Testimonials Section -->
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<!-- ======= Why Us Section ======= -->
|
||||
<section id="why-us" class="section why-us light-background" data-builder="section">
|
||||
<div class="container-fluid">
|
||||
<div class="row gy-4">
|
||||
|
||||
<div class="col-lg-7 d-flex flex-column justify-content-center order-2 order-lg-1">
|
||||
|
||||
<div class="content px-xl-5" data-aos="fade-up" data-aos-delay="100">
|
||||
<h3><span>Why Organizations Choose </span><strong>Wizdom Networks</strong></h3>
|
||||
<p>
|
||||
There’s no shortage of IT consultants out there. But few bring the same mix of creativity, pragmatism, and proven experience. We do.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="faq-container px-xl-5" data-aos="fade-up" data-aos-delay="200">
|
||||
|
||||
<div class="faq-item faq-active">
|
||||
<h3><span>01</span> What makes Wizdom different?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We combine deep technical chops with strategic insight and hands-on execution. No fluff. No shortcuts.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
<div class="faq-item">
|
||||
<h3><span>02</span> Who do you typically work with?</h3>
|
||||
<div class="faq-content">
|
||||
<p>We support SMBs and mid-market orgs across retail, public sector, professional services, and non-profits — but we scale easily into enterprise territory.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
<div class="faq-item">
|
||||
<h3><span>03</span> Do you offer flexible or project-based engagements?</h3>
|
||||
<div class="faq-content">
|
||||
<p>Absolutely. From one-off projects to long-term partnerships, we adapt to your business needs and growth stage.</p>
|
||||
</div>
|
||||
<i class="faq-toggle bi bi-chevron-right"></i>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-lg-5 order-1 order-lg-2 why-us-img">
|
||||
<img src="assets/img/why-us.png" class="img-fluid" alt="" data-aos="zoom-in" data-aos-delay="100">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Why Us Section -->
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<!-- ======= Work Process Section ======= -->
|
||||
<section id="work-process" class="work-process section">
|
||||
<div class="container section-title" data-aos="fade-up">
|
||||
<h2>Work Process</h2>
|
||||
<p>Our proven approach helps organizations move faster, stay secure, and grow smarter.</p>
|
||||
</div>
|
||||
|
||||
<div class="container" data-aos="fade-up" data-aos-delay="100">
|
||||
<div class="row gy-5">
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="200">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-1.webp" alt="Step 1" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">01</div>
|
||||
<h3>Discovery & Assessment</h3>
|
||||
<p>We begin with a collaborative review of your systems, needs, risks, and goals to chart the best path forward.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Infrastructure Audits</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Cloud Readiness</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Security Gaps</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="300">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-2.webp" alt="Step 2" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">02</div>
|
||||
<h3>Planning & Design</h3>
|
||||
<p>We design robust solutions aligned with your budget, timelines, and business objectives — built for scale and longevity.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>System Architecture</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Migration Planning</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Compliance Considerations</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4" data-aos="fade-up" data-aos-delay="400">
|
||||
<div class="steps-item">
|
||||
<div class="steps-image">
|
||||
<img src="assets/img/steps/steps-3.webp" alt="Step 3" class="img-fluid" loading="lazy">
|
||||
</div>
|
||||
<div class="steps-content">
|
||||
<div class="steps-number">03</div>
|
||||
<h3>Implementation & Support</h3>
|
||||
<p>From deployment to post-project monitoring, Wizdom Networks ensures success with hands-on support every step of the way.</p>
|
||||
<div class="steps-features">
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Project Execution</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>Training & Onboarding</span></div>
|
||||
<div class="feature-item"><i class="bi bi-check-circle"></i><span>HelpDesk+ Ongoing Support</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section><!-- End Work Process Section -->
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
|
||||
# File: convert-to-webp.sh
|
||||
# Version: 1.1
|
||||
# Purpose: Convert JPG/PNG to square WebP (centered or top-aligned)
|
||||
# Usage:
|
||||
# ./convert-to-webp.sh image1.jpg image2.png ...
|
||||
# ./convert-to-webp.sh --top image1.jpg image2.png ...
|
||||
|
||||
set -e
|
||||
|
||||
# Default to center crop
|
||||
CROP_MODE="center"
|
||||
|
||||
# Check for --top flag
|
||||
if [[ "$1" == "--top" ]]; then
|
||||
CROP_MODE="top"
|
||||
shift
|
||||
fi
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "Usage:"
|
||||
echo " $0 image1.jpg image2.png ..."
|
||||
echo " $0 --top image1.jpg image2.png ..."
|
||||
echo
|
||||
echo "Use --top to crop from bottom up (better for portraits)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for input in "$@"; do
|
||||
if [[ ! -f "$input" ]]; then
|
||||
echo "Skipping '$input': not a file"
|
||||
continue
|
||||
fi
|
||||
|
||||
ext="${input##*.}"
|
||||
ext_lc="$(echo "$ext" | tr '[:upper:]' '[:lower:]')"
|
||||
if [[ "$ext_lc" != "jpg" && "$ext_lc" != "jpeg" && "$ext_lc" != "png" ]]; then
|
||||
echo "Skipping '$input': unsupported format"
|
||||
continue
|
||||
fi
|
||||
|
||||
base_name="$(basename "$input" .${ext})"
|
||||
output="${base_name}.webp"
|
||||
|
||||
dims=$(identify -format "%w %h" "$input")
|
||||
width=$(echo "$dims" | cut -d' ' -f1)
|
||||
height=$(echo "$dims" | cut -d' ' -f2)
|
||||
|
||||
if [ "$width" -gt "$height" ]; then
|
||||
# Landscape
|
||||
offset_x=$(( (width - height) / 2 ))
|
||||
offset_y=0
|
||||
crop="${height}x${height}+${offset_x}+${offset_y}"
|
||||
else
|
||||
# Portrait or square
|
||||
offset_x=0
|
||||
if [[ "$CROP_MODE" == "top" ]]; then
|
||||
offset_y=0
|
||||
else
|
||||
offset_y=$(( (height - width) / 2 ))
|
||||
fi
|
||||
crop="${width}x${width}+${offset_x}+${offset_y}"
|
||||
fi
|
||||
|
||||
echo "Converting '$input' to '$output' (crop: $crop, mode: $CROP_MODE)..."
|
||||
convert "$input" -crop "$crop" +repage -quality 90 "$output"
|
||||
done
|
||||
|
||||
echo "All done."
|
||||