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
This commit is contained in:
essae 2025-05-20 20:52:46 -04:00
parent a1c25d4885
commit fc4e8ae851
59 changed files with 2218 additions and 1309 deletions

View File

@ -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;
}
}
}

View File

@ -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.']);
}
}
}

View File

@ -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.']);
}
}
}

View File

@ -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.']);
}
}
}

View File

@ -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.']);
}
}
}

View File

@ -3,10 +3,9 @@
* ============================================
* File: Router.php
* Path: /app/Core/
* Purpose: Core router handling HTTP methodspecific route dispatching.
* Version: 1.1
* Purpose: Core router handling HTTP methodspecific 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";
}
}

View File

@ -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");
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}
}

View File

@ -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 "

View File

@ -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.");
}
}
}

View File

@ -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];
}
}
}

1
public/assets/images Symbolic link
View File

@ -0,0 +1 @@
/var/www/html/dev.wizdom.ca/public/assets/img

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

View File

@ -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}`;
}
};

View File

@ -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);

View File

@ -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>

View File

@ -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>&copy; <?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>

View File

@ -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;
// ============================================
// 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';">
<!-- 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">
<!-- Main Stylesheets -->
<link rel="stylesheet" href="/assets/css/main.css">
<link rel="stylesheet" href="/assets/css/utilities.css">
<!-- 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">
<!-- 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; ?>
<!-- Favicons -->
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
<link rel="apple-touch-icon" href="/assets/img/apple-touch-icon.png">
<!-- 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; ?>
<!-- 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">
<!-- Favicon -->
<link rel="icon" type="image/png" href="/assets/images/favicon.png">
<!-- 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>

View File

@ -1,3 +1,16 @@
<header class="bg-light py-3">
<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>

View File

@ -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">Weve 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>

View File

@ -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>

View File

@ -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>
Were not your average IT consultancy and thats 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 dont 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>
Theres 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>Lets Make Tech Make Sense</h3>
<p>Whether you're solving todays fires or planning for tomorrows growth, wed 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 Ive 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. Id 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>Lets connect. Whether its a project, support request, or general inquiry were 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>

View File

@ -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">Were sorry to see you go. If youd 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 youre 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>

View File

@ -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>

View File

@ -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">Youve Been Unsubscribed</h2>
<p class="lead">
Weve 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>

View File

@ -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>

View File

@ -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>

View File

@ -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>
Were not your average IT consultancy and thats 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 dont 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 -->

View File

@ -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>Lets Make Tech Make Sense</h3>
<p>Whether you're solving todays fires or planning for tomorrows growth, wed 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 -->

View File

@ -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 -->

View File

@ -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>Lets connect. Reach out with questions, ideas, or project inquiries well 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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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>

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 -->

View File

@ -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 didnt 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 -->

View File

@ -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>
Theres 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 -->

View File

@ -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 -->

View File

@ -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."