Implementation of Notification System
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
41
src/api/notifications.php
Normal file
41
src/api/notifications.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
// Ensure environment is loaded before attempting DB connection
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/connection.php';
|
||||
// helper filename uses singular in this repo: notification_helper.php
|
||||
require_once __DIR__ . '/../helpers/notification_helper.php';
|
||||
session_start();
|
||||
|
||||
$admin_id = $_SESSION['user_id'] ?? null;
|
||||
$action = $_REQUEST['action'] ?? '';
|
||||
|
||||
if ($action === 'fetch') {
|
||||
$subs = getAdminSubscriptions($admin_id);
|
||||
$notes = fetchNotifications($admin_id, $subs, 50);
|
||||
echo json_encode(['success' => 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']);
|
||||
@@ -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;
|
||||
|
||||
|
||||
151
src/helpers/notification_helper.php
Normal file
151
src/helpers/notification_helper.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
// Notifications helper
|
||||
|
||||
require_once __DIR__ . '/../config/env.php';
|
||||
require_once __DIR__ . '/../config/connection.php';
|
||||
|
||||
require_once __DIR__ . '/../../classes/DatabaseService.php';
|
||||
|
||||
function addNotification($target_user_id, $event, $sub_feed = null, $data = [], $target_url = null) {
|
||||
global $db, $conn;
|
||||
if (!isset($db) && isset($conn)) {
|
||||
$db = new DatabaseService($conn);
|
||||
}
|
||||
$ds = $db;
|
||||
if (!$ds) {
|
||||
// Try to initialize DatabaseService if connection exists
|
||||
if (isset($conn) && $conn) {
|
||||
$ds = new DatabaseService($conn);
|
||||
} else {
|
||||
// No DB available
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$data_json = $data ? json_encode($data) : null;
|
||||
$read_by_json = json_encode([]);
|
||||
$query = "INSERT INTO notifications (user_id, event, sub_feed, data, target_url, read_by) VALUES (?,?,?,?,?,?)";
|
||||
return $ds->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 [];
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "<script>alert('Booking successfully created!'); window.location.href = 'booking.php';</script>";
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 "<script>alert('Booking successfully created!'); window.location.href = 'bookings.php';</script>";
|
||||
|
||||
Reference in New Issue
Block a user