From 7ebc2f64cff34b3a83f7457ee221f6fe7c86c684 Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Tue, 16 Dec 2025 22:40:24 +0200 Subject: [PATCH] Implementation of Notification System --- .htaccess | 2 +- .htaccess copy | 215 ------------------ assets/css/notifications.css | 73 ++++++ assets/js/notifications_panel.js | 103 +++++++++ .../006b_create_notifications_table.sql | 13 ++ header.php | 16 +- progress.log | 3 + src/api/ikhokha_webhook.php | 9 + src/api/notifications.php | 41 ++++ src/config/connection.php | 11 +- src/helpers/notification_helper.php | 151 ++++++++++++ src/logs/db_errors.log | 2 +- src/pages/memberships/renew_membership.php | 13 ++ src/processors/process_application.php | 8 + src/processors/process_booking.php | 9 + src/processors/process_course_booking.php | 12 +- src/processors/process_trip_booking.php | 11 + validate_login.php | 41 +++- 18 files changed, 501 insertions(+), 232 deletions(-) delete mode 100644 .htaccess copy create mode 100644 assets/css/notifications.css create mode 100644 assets/js/notifications_panel.js create mode 100644 docs/migrations/006b_create_notifications_table.sql create mode 100644 src/api/notifications.php create mode 100644 src/helpers/notification_helper.php diff --git a/.htaccess b/.htaccess index a32cebe2..92ef24d7 100644 --- a/.htaccess +++ b/.htaccess @@ -161,7 +161,7 @@ RewriteRule ^autosave$ src/processors/blog/autosave.php [L] -php_flag display_errors Off +php_flag display_errors On # php_value error_reporting -1 RedirectMatch 403 ^/\.well-known Options -Indexes diff --git a/.htaccess copy b/.htaccess copy deleted file mode 100644 index 46c835d3..00000000 --- a/.htaccess copy +++ /dev/null @@ -1,215 +0,0 @@ -# URL Rewrite Rules - Maps old URLs to new directory structure during migration - -RewriteEngine On -RewriteBase / - -# Don't rewrite existing files or directories -RewriteCond %{REQUEST_FILENAME} !-f -RewriteCond %{REQUEST_FILENAME} !-d - -# === STRIP .PHP EXTENSION === -# Redirect /page.php to /page (301 permanent redirect) -RewriteCond %{REQUEST_FILENAME} !-d -RewriteRule ^(.+)\.php$ /$1 [R=301,L] -# Internally rewrite /page to /page.php if page.php exists -RewriteCond %{REQUEST_FILENAME}\.php -f -RewriteRule ^(.+)$ $1.php [L] - -# === AUTH PAGES === -RewriteRule ^login$ src/pages/auth/login.php [L] -RewriteRule ^register$ src/pages/auth/register.php [L] -RewriteRule ^forgot_password$ src/pages/auth/forgot_password.php [L] -RewriteRule ^reset_password$ src/pages/auth/reset_password.php [L] -RewriteRule ^verify$ src/pages/auth/verify.php [L] -RewriteRule ^resend_verification$ src/pages/auth/resend_verification.php [L] -RewriteRule ^change_password$ src/pages/auth/change_password.php [L] -RewriteRule ^update_password$ src/pages/auth/update_password.php [L] - -# === MEMBERSHIP PAGES === -RewriteRule ^membership$ src/pages/memberships/membership.php [L] -RewriteRule ^membership_details$ src/pages/memberships/membership_details.php [L] -RewriteRule ^membership_application$ src/pages/memberships/membership_application.php [L] -RewriteRule ^membership_payment$ src/pages/memberships/membership_payment.php [L] -RewriteRule ^renew_membership$ src/pages/memberships/renew_membership.php [L] -RewriteRule ^member_info$ src/pages/memberships/member_info.php [L] - -# === BOOKING PAGES === -RewriteRule ^bookings$ src/pages/bookings/bookings.php [L] -RewriteRule ^campsites$ src/pages/bookings/campsites.php [L] -RewriteRule ^campsite_booking$ src/pages/bookings/campsite_booking.php [L] -RewriteRule ^add_campsite$ src/pages/add_campsite.php [L] -RewriteRule ^trips$ src/pages/bookings/trips.php [L] -RewriteRule ^trip-details$ src/pages/bookings/trip-details.php [L] -RewriteRule ^course_details$ src/pages/bookings/course_details.php [L] -RewriteRule ^driver_training$ src/pages/bookings/driver_training.php [L] - -# === SHOP PAGES === -RewriteRule ^view_cart$ src/pages/shop/view_cart.php [L] -RewriteRule ^add_to_cart$ src/pages/shop/add_to_cart.php [L] -RewriteRule ^bar_tabs$ src/pages/shop/bar_tabs.php [L] -RewriteRule ^payment_confirmation$ src/pages/shop/payment_confirmation.php [L] -RewriteRule ^confirm$ src/pages/shop/confirm.php [L] -RewriteRule ^confirm2$ src/pages/shop/confirm2.php [L] - -# === GALLERY PAGES === -RewriteRule ^gallery$ src/pages/gallery/gallery.php [L] -RewriteRule ^create_album$ src/pages/gallery/create_album.php [L] -RewriteRule ^edit_album$ src/pages/gallery/create_album.php [L] -RewriteRule ^view_album$ src/pages/gallery/view_album.php [L] - -# === EVENTS & BLOG PAGES === -RewriteRule ^events$ src/pages/events/events.php [L] -RewriteRule ^blog$ src/pages/blog/blog.php [L] -RewriteRule ^blog_details$ src/pages/blog/blog_details.php [L] -RewriteRule ^best_of_the_eastern_cape_2024$ src/pages/events/best_of_the_eastern_cape_2024.php [L] -RewriteRule ^2025_agm_minutes$ src/pages/events/2025_agm_minutes.php [L] -RewriteRule ^agm_content$ src/pages/events/agm_content.php [L] -RewriteRule ^instapage$ src/pages/events/instapage.php [L] - -# === OTHER PAGES === -RewriteRule ^about$ src/pages/other/about.php [L] -RewriteRule ^contact$ src/pages/other/contact.php [L] -RewriteRule ^privacy_policy$ src/pages/other/privacy_policy.php [L] -RewriteRule ^track-map$ src/pages/track-map.php [L] -RewriteRule ^404$ src/pages/other/404.php [L] -RewriteRule ^account_settings$ src/pages/other/account_settings.php [L] -RewriteRule ^rescue_recovery$ src/pages/other/rescue_recovery.php [L] -RewriteRule ^bush_mechanics$ src/pages/other/bush_mechanics.php [L] -RewriteRule ^indemnity$ src/pages/other/indemnity.php [L] -RewriteRule ^indemnity_waiver$ src/pages/other/indemnity_waiver.php [L] -RewriteRule ^basic_indemnity$ src/pages/other/basic_indemnity.php [L] -RewriteRule ^view_indemnity$ src/pages/other/view_indemnity.php [L] - -# === ADMIN PAGES === -RewriteRule ^admin_members$ src/admin/admin_members.php [L] -RewriteRule ^admin_payments$ src/admin/admin_payments.php [L] -RewriteRule ^admin_web_users$ src/admin/admin_web_users.php [L] -RewriteRule ^admin_events$ src/admin/admin_events.php [L] -RewriteRule ^admin_course_bookings$ src/admin/admin_course_bookings.php [L] -RewriteRule ^admin_camp_bookings$ src/admin/admin_camp_bookings.php [L] -RewriteRule ^admin_trip_bookings$ src/admin/admin_trip_bookings.php [L] -RewriteRule ^admin_visitors$ src/admin/admin_visitors.php [L] -RewriteRule ^admin_efts$ src/admin/admin_efts.php [L] -RewriteRule ^admin_trips$ src/admin/admin_trips.php [L] -RewriteRule ^manage_events$ src/admin/manage_events.php [L] -RewriteRule ^manage_trips$ src/admin/manage_trips.php [L] - -# === API/AJAX ENDPOINTS === -RewriteRule ^fetch_users$ src/api/fetch_users.php [L] -RewriteRule ^fetch_drinks$ src/api/fetch_drinks.php [L] -RewriteRule ^fetch_bar_tabs$ src/api/fetch_bar_tabs.php [L] -RewriteRule ^get_campsites$ src/api/get_campsites.php [L] -RewriteRule ^get_tab_total$ src/api/get_tab_total.php [L] -RewriteRule ^google_validate_login$ src/api/google_validate_login.php [L] - -# === PROCESSORS === -RewriteRule ^validate_login$ src/processors/validate_login.php [L] -RewriteRule ^register_user$ src/processors/register_user.php [L] -RewriteRule ^process_application$ src/processors/process_application.php [L] -RewriteRule ^process_booking$ src/processors/process_booking.php [L] -RewriteRule ^process_camp_booking$ src/processors/process_camp_booking.php [L] -RewriteRule ^process_course_booking$ src/processors/process_course_booking.php [L] -RewriteRule ^process_trip_booking$ src/processors/process_trip_booking.php [L] -RewriteRule ^process_membership_payment$ src/processors/process_membership_payment.php [L] -RewriteRule ^process_payments$ src/processors/process_payments.php [L] -RewriteRule ^process_eft$ src/processors/process_eft.php [L] -RewriteRule ^submit_order$ src/processors/submit_order.php [L] -RewriteRule ^submit_pop$ src/processors/submit_pop.php [L] -RewriteRule ^process_signature$ src/processors/process_signature.php [L] -RewriteRule ^create_bar_tab$ src/processors/create_bar_tab.php [L] -RewriteRule ^update_application$ src/processors/update_application.php [L] -RewriteRule ^update_user$ src/processors/update_user.php [L] -RewriteRule ^upload_profile_picture$ src/processors/upload_profile_picture.php [L] -RewriteRule ^send_reset_link$ src/processors/send_reset_link.php [L] -RewriteRule ^logout$ src/processors/logout.php [L] -RewriteRule ^process_trip$ src/processors/process_trip.php [L] -RewriteRule ^process_event$ src/processors/process_event.php [L] -RewriteRule ^toggle_trip_published$ src/processors/toggle_trip_published.php [L] -RewriteRule ^toggle_event_published$ src/processors/toggle_event_published.php [L] -RewriteRule ^delete_trip$ src/processors/delete_trip.php [L] -RewriteRule ^delete_event$ src/processors/delete_event.php [L] -RewriteRule ^save_album$ src/processors/save_album.php [L] -RewriteRule ^update_album$ src/processors/update_album.php [L] -RewriteRule ^delete_album$ src/processors/delete_album.php [L] -RewriteRule ^delete_photo$ src/processors/delete_photo.php [L] -RewriteRule ^get_album_photos$ src/processors/get_album_photos.php [L] -RewriteRule ^link_membership_user$ src/processors/link_membership_user.php [L] -RewriteRule ^unlink_membership_user$ src/processors/unlink_membership_user.php [L] - -# Blog routes -RewriteRule ^admin_blogs$ src/pages/blog/admin_blogs.php [L] -RewriteRule ^user_blogs$ src/pages/blog/user_blogs.php [L] -RewriteRule ^blog_read$ src/pages/blog/blog_read.php [L] -RewriteRule ^blog_edit$ src/pages/blog/blog_edit.php [L] -RewriteRule ^blog_create$ src/processors/blog/blog_create.php [L] -RewriteRule ^blog_delete$ src/processors/blog/blog_delete.php [L] -RewriteRule ^publish_blog$ src/processors/blog/publish_blog.php [L] -RewriteRule ^blog_unpublish$ src/processors/blog/blog_unpublish.php [L] -RewriteRule ^submit_blog$ src/processors/blog/submit_blog.php [L] -RewriteRule ^upload_blog_image$ src/processors/blog/upload_blog_image.php [L] -RewriteRule ^autosave$ src/processors/blog/autosave.php [L] - - - -php_flag display_errors On -# php_value error_reporting -1 -RedirectMatch 403 ^/\.well-known -Options -Indexes - - - Require all denied - - -ErrorDocument 404 /404.php - - - Require all granted - Require not ip 4.222.252.98 - Require not ip 4.222.252.97 - - - - Order allow,deny - Deny from all - - - -# ALL CUSTOM ENTRIES SHOULD GO ABOVE THIS LINE -# BEGIN IWORX header -# This file was created by InterWorx-CP -# You may modify this file, but any changes made between -# BEGIN IWORX and END IWORX tags may be lost on future -# updates. Additionally, changes NOT made between these -# tags will not be recognized in the SiteWorx interface. -# END IWORX header - -# BEGIN IWORX accesscontrol -# END IWORX accesscontrol - -# BEGIN IWORX errordocs -# END IWORX errordocs - -# BEGIN IWORX mimetypes -# END IWORX mimetypes - -# BEGIN IWORX handlers -# END IWORX handlers - -# BEGIN IWORX charset -# END IWORX charset - -# BEGIN IWORX redirects -# END IWORX redirects - -# BEGIN IWORX phpvars -# END IWORX phpvars - -# BEGIN IWORX dirindex -# END IWORX dirindex - -# BEGIN IWORX hotlink -# END IWORX hotlink - -# BEGIN IWORX passwordprotection -# END IWORX passwordprotection - diff --git a/assets/css/notifications.css b/assets/css/notifications.css new file mode 100644 index 00000000..9003b190 --- /dev/null +++ b/assets/css/notifications.css @@ -0,0 +1,73 @@ +.notif-avatar-container { + position: relative; + display: inline-block; +} +.notif-badge { + position: absolute; + top: -6px; + right: -6px; + background: #e74c3c; + color: #fff; + border-radius: 50%; + min-width: 20px; + height: 20px; + padding: 0 6px; + font-size: 12px; + display: none; + line-height: 20px; + text-align: center; + box-sizing: border-box; + font-weight: 600; +} +.notif-panel { + position: absolute; + right: 10px; + top: 44px; + width: 320px; + background: #fff; + border: 1px solid #ddd; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.12); + z-index: 9999; + padding: 8px; + border-radius: 6px; +} +.notif-panel .notif-empty { + padding: 12px; + text-align: center; + color: #666; +} +.notif-item { + display: flex; + align-items: center; + padding: 8px; + border-bottom: 1px solid #f1f1f1; +} +.notif-item:last-child { + border-bottom: none; +} +.notif-item-avatar { + width: 36px; + height: 36px; + border-radius: 50%; + object-fit: cover; + margin-right: 8px; +} +.notif-item-body { + flex: 1; +} +.notif-item-title { + font-weight: 600; + font-size: 13px; +} +.notif-item-meta { + font-size: 11px; + color: #888; +} +.notif-close { + background: transparent; + border: 0; + color: #999; + font-weight: 700; + padding: 6px; + cursor: pointer; +} diff --git a/assets/js/notifications_panel.js b/assets/js/notifications_panel.js new file mode 100644 index 00000000..7d95ff1c --- /dev/null +++ b/assets/js/notifications_panel.js @@ -0,0 +1,103 @@ +/* notifications.js - small admin notification panel + Requires jQuery. */ +(function($){ + function timeAgo(ts){ + var seconds = Math.floor((Date.now() - (new Date(ts)).getTime())/1000); + if (seconds < 60) return seconds + 's ago'; + var minutes = Math.floor(seconds/60); + if (minutes < 60) return minutes + 'm ago'; + var hours = Math.floor(minutes/60); + if (hours < 24) return hours + 'h ago'; + var days = Math.floor(hours/24); + return days + 'd ago'; + } + + function renderNotifications(list){ + var $panel = $('#notif-panel'); + $panel.empty(); + if (!list || list.length === 0) { + $panel.append('
No notifications
'); + return; + } + list.forEach(function(n){ + var actorAvatar = (n.data && n.data.actor_avatar) ? n.data.actor_avatar : 'assets/images/icons/user.png'; + // Prefer the notification payload title (n.data.title). Do NOT fall back to the event string. + var title = (n.data && n.data.title) ? n.data.title : 'Notification'; + var time = n.time_created || new Date().toISOString(); + var read = (n.read_by && Array.isArray(n.read_by) && n.read_by.length>0); + var $item = $('
'); + $item.append('avatar'); + var $body = $('
'); + $body.append('
'+escapeHtml(title)+'
'); + $body.append('
'+timeAgo(time)+'
'); + $item.append($body); + $item.append(''); + $panel.append($item); + }); + } + + function escapeHtml(str) { + if (!str) return ''; + return String(str).replace(/[&<>"'`]/g, function(s){ return {'&':'&','<':'<','>':'>','"':'"',"'":"'",'`':'`'}[s]; }); + } + + function fetchAndRender(adminId){ + $.getJSON('/src/api/notifications.php', { action: 'fetch' }, function(resp){ + if (resp && resp.success) { + renderNotifications(resp.notifications); + if (resp.unread_count && resp.unread_count > 0) { + $('#notif-badge').text(resp.unread_count).show(); + } else { + $('#notif-badge').hide(); + } + } + }); + } + + // Fetch only unread count (used on page load so badge shows without opening panel) + function fetchUnreadCount(){ + $.getJSON('/src/api/notifications.php', { action: 'fetch' }, function(resp){ + if (resp && resp.success) { + if (resp.unread_count && resp.unread_count > 0) { + $('#notif-badge').text(resp.unread_count).show(); + } else { + $('#notif-badge').hide(); + } + } + }); + } + + $(function(){ + var $container = $('.notif-avatar-container'); + if (!$container.length) return; + var adminId = $container.data('admin-id'); + // ensure badge is populated on page load + fetchUnreadCount(); + $container.on('click', function(e){ + e.preventDefault(); + $('#notif-panel').toggle(); + if ($('#notif-panel').is(':visible')) fetchAndRender(adminId); + }); + + $(document).on('click', '.notif-close', function(e){ + e.stopPropagation(); + var $it = $(this).closest('.notif-item'); + var id = $it.data('id'); + if (!id) return; + $.post('/src/api/notifications.php', { action: 'mark_read', id: id }, function(resp){ + if (resp && resp.success) { + $it.remove(); + // refresh count + fetchAndRender(adminId); + } + }, 'json'); + }); + + // click outside to close + $(document).on('click', function(e){ + if (!$(e.target).closest('#notif-panel, .notif-avatar-container').length) { + $('#notif-panel').hide(); + } + }); + }); +})(jQuery); diff --git a/docs/migrations/006b_create_notifications_table.sql b/docs/migrations/006b_create_notifications_table.sql new file mode 100644 index 00000000..49c21df0 --- /dev/null +++ b/docs/migrations/006b_create_notifications_table.sql @@ -0,0 +1,13 @@ +-- Migration: create notifications table (corrected: `read_by` nullable) +CREATE TABLE IF NOT EXISTS `notifications` ( + `id` BIGINT PRIMARY KEY AUTO_INCREMENT, + `user_id` INT DEFAULT NULL, + `event` VARCHAR(100) NOT NULL, + `sub_feed` VARCHAR(100) DEFAULT NULL, + `data` TEXT DEFAULT NULL, + `target_url` VARCHAR(1024) DEFAULT NULL, + `read_by` TEXT DEFAULT NULL, + `time_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX (`user_id`), + INDEX (`sub_feed`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/header.php b/header.php index d2f04ee0..27229561 100644 --- a/header.php +++ b/header.php @@ -25,6 +25,7 @@ require_once($rootDir . "/src/config/env.php"); require_once($rootDir . "/src/config/session.php"); require_once($rootDir . "/src/config/connection.php"); require_once($rootDir . "/src/config/functions.php"); +require_once($rootDir . "/src/helpers/notification_helper.php"); $is_logged_in = isset($_SESSION['user_id']); if (isset($_SESSION['user_id'])) { @@ -348,11 +349,17 @@ if ($headerStyle === 'light') {
Welcome, - - Profile Picture - +
+ + Profile Picture + + +
+
+ + Log In @@ -372,6 +379,9 @@ if ($headerStyle === 'light') { const profileInfo = document.querySelector('.profile-info'); if (profileInfo) { profileInfo.addEventListener('click', function(event) { + // Ignore clicks on the notifications avatar so the notif panel + // can handle its own toggle without also toggling the profile dropdown. + if (event.target.closest && event.target.closest('.notif-avatar-container')) return; const dropdownMenu = document.querySelector('.dropdown-menu2'); if (dropdownMenu) { dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block'; diff --git a/progress.log b/progress.log index fa15d3bd..692ad5f6 100644 --- a/progress.log +++ b/progress.log @@ -120,3 +120,6 @@ [2025-12-15 15:48:43] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID [2025-12-15 15:48:43] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign [2025-12-15 15:48:43] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature +[2025-12-16 22:38:31] IKFLESZTKFM4HWWS76131L8HK9BYF96P | CONTEXT: IKHOKHA App ID +[2025-12-16 22:38:31] /public-api/v1/api/payments/history | CONTEXT: IKHOKHA Payload to Sign +[2025-12-16 22:38:31] b3b592829090d2cfd0912ccbdec73db18742088d01a2d2bee9f0eacdf37a6b26 | CONTEXT: IKHOKHA Signature diff --git a/src/api/ikhokha_webhook.php b/src/api/ikhokha_webhook.php index a4157953..2b9ce1b4 100644 --- a/src/api/ikhokha_webhook.php +++ b/src/api/ikhokha_webhook.php @@ -4,6 +4,7 @@ $rootPath = dirname(dirname(__DIR__)); require_once($rootPath . "/src/config/env.php"); require_once($rootPath . "/src/config/connection.php"); require_once($rootPath . "/src/config/functions.php"); +require_once($rootPath . "/src/helpers/notification_helper.php"); /** * ========================================================== @@ -272,6 +273,14 @@ if (in_array($normalized, ['PAID', 'SUCCESS', 'COMPLETED', 'SETTLED'], true)) { nl2br($message) ); sendAdminNotification($subject, nl2br($message)); + $event = 'new_payment_received'; + $sub_feed = 'payments'; + $data = [ + 'actor_id' => $_SESSION['user_id'] ?? null, + 'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar + 'title' => "New Payment Received for Payment ID: {$localPaymentId}" + ]; + addNotification(null, $event, $sub_feed, $data, null); } /** diff --git a/src/api/notifications.php b/src/api/notifications.php new file mode 100644 index 00000000..cf2435df --- /dev/null +++ b/src/api/notifications.php @@ -0,0 +1,41 @@ + true, 'notifications' => $notes, 'unread_count' => getUnreadCount($admin_id, $subs)]); + exit; +} + +if ($action === 'mark_read') { + if (!$admin_id) { echo json_encode(['success' => false, 'error' => 'unauthenticated']); exit; } + $id = isset($_POST['id']) ? intval($_POST['id']) : 0; + if (!$id) { echo json_encode(['success' => false, 'error' => 'missing_id']); exit; } + $ok = markNotificationRead($id, $admin_id); + echo json_encode(['success' => (bool)$ok]); + exit; +} + +if ($action === 'add') { + // internal use: create a notification + $target = isset($_POST['user_id']) ? intval($_POST['user_id']) : null; + $event = $_POST['event'] ?? ''; + $sub_feed = $_POST['sub_feed'] ?? null; + $data = isset($_POST['data']) ? json_decode($_POST['data'], true) : []; + $target_url = $_POST['target_url'] ?? null; + $id = addNotification($target, $event, $sub_feed, $data, $target_url); + echo json_encode(['success' => (bool)$id, 'id' => $id]); + exit; +} + +echo json_encode(['success' => false, 'error' => 'invalid_action']); diff --git a/src/config/connection.php b/src/config/connection.php index 3e4becbc..5df48748 100644 --- a/src/config/connection.php +++ b/src/config/connection.php @@ -3,11 +3,12 @@ // Disable mysqli exceptions so we can handle connection errors gracefully mysqli_report(MYSQLI_REPORT_OFF); -$dbhost = $_ENV['DB_HOST']; -$dbuser = $_ENV['DB_USER']; -$dbpass = $_ENV['DB_PASS']; -$dbname = $_ENV['DB_NAME']; -$salt = $_ENV['SALT']; +// Read from environment or fallback to getenv; keep empty string if not set to avoid PHP warnings +$dbhost = $_ENV['DB_HOST'] ?? getenv('DB_HOST') ?? ''; +$dbuser = $_ENV['DB_USER'] ?? getenv('DB_USER') ?? ''; +$dbpass = $_ENV['DB_PASS'] ?? getenv('DB_PASS') ?? ''; +$dbname = $_ENV['DB_NAME'] ?? getenv('DB_NAME') ?? ''; +$salt = $_ENV['SALT'] ?? getenv('SALT') ?? ''; // echo "hello. ". $dbhost; diff --git a/src/helpers/notification_helper.php b/src/helpers/notification_helper.php new file mode 100644 index 00000000..935a0ebb --- /dev/null +++ b/src/helpers/notification_helper.php @@ -0,0 +1,151 @@ +insert($query, [$target_user_id, $event, $sub_feed, $data_json, $target_url, $read_by_json], "isssss"); +} + +function fetchNotifications($admin_user_id = null, $subscriptions = [], $limit = 50) { + global $db, $conn; + if (!isset($db) && isset($conn)) { + $db = new DatabaseService($conn); + } + $ds = $db; + if (!$ds) { + if (isset($conn) && $conn) { + $ds = new DatabaseService($conn); + } else { + // No DB available — return empty list to avoid fatal error + return []; + } + } + $params = []; + $types = ""; + $sql = "SELECT * FROM notifications"; + $where = []; + // Admin-only: fetch notifications targeted to admins (user_id IS NULL) or global, or specifically to an admin + if ($admin_user_id) { + $where[] = "(user_id IS NULL OR user_id = ?)"; + $params[] = $admin_user_id; + $types .= "i"; + } + if (!empty($subscriptions)) { + // build IN (...) list safely by placeholders + $placeholders = implode(',', array_fill(0, count($subscriptions), '?')); + $where[] = "(sub_feed IN ($placeholders))"; + foreach ($subscriptions as $s) { $params[] = $s; $types .= "s"; } + } + if (!empty($where)) { + $sql .= " WHERE " . implode(' AND ', $where); + } + $sql .= " ORDER BY time_created DESC LIMIT ?"; + $params[] = $limit; $types .= "i"; + + $results = $ds->select($sql, $params, $types); + if ($results === false) { + // Query error - return empty list so UI doesn't break + return []; + } + // decode data JSON and include read_by as array + $filtered = []; + foreach ($results as $r) { + $r['data'] = $r['data'] ? json_decode($r['data'], true) : null; + $r['read_by'] = $r['read_by'] ? json_decode($r['read_by'], true) : []; + if (!is_array($r['read_by'])) $r['read_by'] = []; + // If admin_user_id is provided, skip notifications this admin already read + if ($admin_user_id && in_array((int)$admin_user_id, $r['read_by'])) { + continue; + } + $filtered[] = $r; + } + return $filtered; +} + +function markNotificationRead($id, $admin_user_id) { + global $conn; + if (!$id || !$admin_user_id) return false; + if (!isset($conn) || !$conn) return false; + $stmt = $conn->prepare("SELECT read_by FROM notifications WHERE id = ? LIMIT 1"); + $stmt->bind_param("i", $id); + $stmt->execute(); + $stmt->bind_result($read_by_json); + $found = $stmt->fetch(); + $stmt->close(); + if (!$found) return false; + $read_by = $read_by_json ? json_decode($read_by_json, true) : []; + if (!is_array($read_by)) $read_by = []; + if (!in_array($admin_user_id, $read_by)) { + $read_by[] = (int)$admin_user_id; + $new_json = json_encode(array_values($read_by)); + $u = $conn->prepare("UPDATE notifications SET read_by = ? WHERE id = ?"); + $u->bind_param("si", $new_json, $id); + $res = $u->execute(); + $u->close(); + return $res; + } + return true; +} + +function getUnreadCount($admin_user_id, $subscriptions = []) { + global $conn; + if (!isset($conn) || !$conn) return 0; + $sql = "SELECT id, read_by, sub_feed FROM notifications"; + $where = []; + $params = []; + $types = ""; + if ($admin_user_id) { + $where[] = "(user_id IS NULL OR user_id = ?)"; $params[] = $admin_user_id; $types .= "i"; + } + if (!empty($subscriptions)) { + $placeholders = implode(',', array_fill(0, count($subscriptions), '?')); + $where[] = "(sub_feed IN ($placeholders))"; + foreach ($subscriptions as $s) { $params[] = $s; $types .= "s"; } + } + if (!empty($where)) $sql .= " WHERE " . implode(' AND ', $where); + $sql .= " ORDER BY time_created DESC"; + $stmt = $conn->prepare($sql); + if ($types) { + // bind params dynamically + $refs = []; + $refs[] = &$types; + foreach ($params as $k => $v) { $refs[] = &$params[$k]; } + call_user_func_array([$stmt, 'bind_param'], $refs); + } + $stmt->execute(); + $res = $stmt->get_result(); + $count = 0; + while ($row = $res->fetch_assoc()) { + $read_by = $row['read_by'] ? json_decode($row['read_by'], true) : []; + if (!is_array($read_by)) $read_by = []; + if (!in_array((int)$admin_user_id, $read_by)) $count++; + } + $stmt->close(); + return $count; +} + +function getAdminSubscriptions($admin_user_id) { + // Placeholder: by default return empty array (all sub_feeds). Implement subscription table later. + return []; +} diff --git a/src/logs/db_errors.log b/src/logs/db_errors.log index effc7895..f7cf8e36 100644 --- a/src/logs/db_errors.log +++ b/src/logs/db_errors.log @@ -1 +1 @@ -Database Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directory \ No newline at end of file +Database Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: php_network_getaddresses: getaddrinfo for mysql-db failed: Name or service not knownDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directoryDatabase Connection Error: No such file or directory \ No newline at end of file diff --git a/src/pages/memberships/renew_membership.php b/src/pages/memberships/renew_membership.php index 2c99b72c..8b4bc5ab 100644 --- a/src/pages/memberships/renew_membership.php +++ b/src/pages/memberships/renew_membership.php @@ -6,11 +6,14 @@ include_once($rootPath . '/header.php'); $is_logged_in = isset($_SESSION['user_id']); if (isset($_SESSION['user_id'])) { $user_id = isset($_SESSION['user_id']) ? $_SESSION['user_id'] : null; + } else { header('Location: login.php'); exit(); // Stop further script execution } +$full_name = getFullName($user_id); + //if membership_fees payment_status is PENDING RENEWAL, redirect to membership_details.php $stmt = $conn->prepare("SELECT payment_status FROM membership_fees WHERE user_id = ? LIMIT 1"); $stmt->bind_param("i", $user_id); @@ -46,6 +49,16 @@ if ($stmt->execute()) { auditLog($user_id, 'MEMBERSHIP_RENEWAL_INITIATED', 'membership_fees', null, ['payment_id' => $payment_id, 'amount' => $payment_amount]); } + // Send Notification + $event = 'membership_renewal_initiated'; + $sub_feed = 'membership_renewal'; + $data = [ + 'actor_id' => $_SESSION['user_id'], + 'actor_avatar' => $_SESSION['profile_pic'], // used by UI to show avatar + 'title' => "Membership Renewal Initiated by {$full_name}" + ]; + addNotification(null, $event, $sub_feed, $data, null); + $checkP = $conn->prepare("SELECT COUNT(*) AS cnt FROM payments WHERE payment_id = ? LIMIT 1"); if ($checkP) { $checkP->bind_param('s', $payment_id); diff --git a/src/processors/process_application.php b/src/processors/process_application.php index 866c3a35..8e073398 100644 --- a/src/processors/process_application.php +++ b/src/processors/process_application.php @@ -224,6 +224,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Audit: membership application submitted if (function_exists('auditLog')) { auditLog($user_id, 'MEMBERSHIP_APPLICATION_SUBMITTED', 'membership_application', null, ['payment_id' => $payment_id, 'amount' => $payment_amount ?? null]); + $event = 'new_application_submitted'; + $sub_feed = 'membership_applications'; + $data = [ + 'actor_id' => $_SESSION['user_id'], + 'actor_avatar' => $_SESSION['profile_pic'], // used by UI to show avatar + 'title' => "New Membership Application from {$first_name} {$last_name}" + ]; + addNotification(null, $event, $sub_feed, $data, null); } header("Location: indemnity"); // Success message diff --git a/src/processors/process_booking.php b/src/processors/process_booking.php index c5fc251b..3ed8bc41 100644 --- a/src/processors/process_booking.php +++ b/src/processors/process_booking.php @@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__)); require_once($rootPath . "/src/config/env.php"); require_once($rootPath . "/src/config/connection.php"); require_once($rootPath . "/src/config/functions.php"); +require_once($rootPath . "/src/helpers/notification_helper.php"); // Start session to retrieve the logged-in user's ID session_start(); @@ -84,6 +85,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (function_exists('auditLog')) { auditLog($user_id, 'BOOKING_CREATED', 'bookings', $booking_id, ['total_amount' => $total_amount, 'from' => $from_date, 'to' => $to_date]); } + $event = 'new_booking_created'; + $sub_feed = 'bookings'; + $data = [ + 'actor_id' => $_SESSION['user_id'] ?? null, + 'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar + 'title' => "New Booking Created with Booking ID: {$booking_id}" + ]; + addNotification(null, $event, $sub_feed, $data, null); // Redirect to success page or display success message echo ""; } else { diff --git a/src/processors/process_course_booking.php b/src/processors/process_course_booking.php index 16fd7404..cc256514 100644 --- a/src/processors/process_course_booking.php +++ b/src/processors/process_course_booking.php @@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__)); require_once($rootPath . "/src/config/env.php"); require_once($rootPath . "/src/config/connection.php"); require_once($rootPath . "/src/config/functions.php"); +require_once($rootPath . "/src/helpers/notification_helper.php"); session_start(); @@ -122,6 +123,14 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (function_exists('auditLog')) { auditLog($user_id, 'COURSE_BOOKING_CREATED', 'bookings', $booking_id, ['course_id' => $course_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]); } + $event = 'new_course_booking_created'; + $sub_feed = 'bookings'; + $data = [ + 'actor_id' => $_SESSION['user_id'] ?? null, + 'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar + 'title' => "New Course Booking Created : {$payment_id}" + ]; + addNotification(null, $event, $sub_feed, $data, null); if ($payment_amount < 1) { if (processZeroPayment($payment_id, $payment_amount, $description)) { @@ -142,8 +151,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Create iKhokha payment link $resp = createIkhokhaPayment($payment_id, $payment_amount, $description, $publicRef); - // Send invoice and admin notification (keep for records) - // sendInvoice(getEmail($user_id), getFullName($user_id), $eft_id, formatCurrency($payment_amount), $description); + // Send invoice and admin notification (keep for records) sendAdminNotification('New Course Booking - '.getFullName($user_id), getFullName($user_id).' has booked for '.$description); // Redirect user to payment link if available diff --git a/src/processors/process_trip_booking.php b/src/processors/process_trip_booking.php index a71a958e..2471c308 100644 --- a/src/processors/process_trip_booking.php +++ b/src/processors/process_trip_booking.php @@ -3,6 +3,7 @@ $rootPath = dirname(dirname(__DIR__)); require_once($rootPath . "/src/config/env.php"); require_once($rootPath . "/src/config/connection.php"); require_once($rootPath . "/src/config/functions.php"); +require_once($rootPath . "/src/helpers/notification_helper.php"); session_start(); // Get the trip_id from the request (ensure it's sanitized) @@ -131,6 +132,16 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { auditLog($user_id, 'TRIP_BOOKING_CREATED', 'bookings', $booking_id, ['trip_id' => $trip_id, 'payment_id' => $payment_id, 'amount' => $payment_amount]); } + // Create notification for new booking + $event = 'new_trip_booking_created'; + $sub_feed = 'bookings'; + $data = [ + 'actor_id' => $_SESSION['user_id'] ?? null, + 'actor_avatar' => $_SESSION['profile_pic'] ?? null, // used by UI to show avatar + 'title' => "New Trip Booking Created: {$payment_id}" + ]; + addNotification(null, $event, $sub_feed, $data, null); + if ($payment_amount < 1) { if (processZeroPayment($payment_id, $payment_amount, $description)) { echo ""; diff --git a/validate_login.php b/validate_login.php index 59e3bdbd..555532b2 100644 --- a/validate_login.php +++ b/validate_login.php @@ -5,6 +5,8 @@ require_once($rootPath . "/src/config/session.php"); require_once($rootPath . "/src/config/connection.php"); require_once($rootPath . "/src/config/functions.php"); require_once($rootPath . '/google-client/vendor/autoload.php'); // Add this line for Google Client +require_once($rootPath . "/src/helpers/notification_helper.php"); + // Check if connection is established if (!$conn) { @@ -37,6 +39,8 @@ if (isset($_GET['code'])) { $last_name = $google_account_info->family_name; $picture = $google_account_info->picture; + + // Check if the user exists in the database $query = "SELECT * FROM users WHERE email = ?"; $stmt = $conn->prepare($query); @@ -53,12 +57,20 @@ if (isset($_GET['code'])) { $stmt->bind_param("sssssi", $email, $first_name, $last_name, $picture, $password, $is_verified); if ($stmt->execute()) { // User successfully registered, set session and redirect - sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just created an account using Google Login.'); $_SESSION['user_id'] = $conn->insert_id; $_SESSION['first_name'] = $first_name; $_SESSION['profile_pic'] = $picture; - processLegacyMembership($_SESSION['user_id']); - // echo json_encode(['status' => 'success', 'message' => 'Google login successful']); + + // Send Notification + $event = 'user_login'; + $sub_feed = 'logins'; + $data = [ + 'actor_id' => $conn->insert_id, + 'actor_avatar' => $picture, // used by UI to show avatar + 'title' => "User Login by {$first_name} {$last_name}" + ]; + addNotification(null, $event, $sub_feed, $data, null); + header("Location: index.php"); exit(); } else { @@ -72,8 +84,17 @@ if (isset($_GET['code'])) { $_SESSION['user_id'] = $row['user_id']; $_SESSION['first_name'] = $row['first_name']; $_SESSION['profile_pic'] = $row['profile_pic']; - sendEmail('chrispintoza@gmail.com', '4WDCSA: New User Login', $name.' has just logged in using Google Login.'); - // echo json_encode(['status' => 'success', 'message' => 'Google login successful']); + + // Send Notification + $event = 'user_login'; + $sub_feed = 'logins'; + $data = [ + 'actor_id' => $_SESSION['user_id'], + 'actor_avatar' => $_SESSION['profile_pic'], // used by UI to show avatar + 'title' => "User Login by {$first_name} {$last_name}" + ]; + addNotification(null, $event, $sub_feed, $data, null); + header("Location: index.php"); exit(); } @@ -181,6 +202,16 @@ if (isset($_POST['email']) && isset($_POST['password'])) { // Set session timeout (30 minutes) $_SESSION['login_time'] = time(); $_SESSION['session_timeout'] = 1800; // 30 minutes in seconds + + // Send Notification + $event = 'user_login'; + $sub_feed = 'logins'; + $data = [ + 'actor_id' => $_SESSION['user_id'], + 'actor_avatar' => $_SESSION['profile_pic'], // used by UI to show avatar + 'title' => "User Login by {$first_name} {$last_name}" + ]; + addNotification(null, $event, $sub_feed, $data, null); auditLog($row['user_id'], 'LOGIN_SUCCESS', 'users', $row['user_id']); echo json_encode(['status' => 'success', 'message' => 'Successful Login']);