<?php
|
namespace JVBase\rest\routes;
|
|
use JVBase\JVB;
|
use JVBase\rest\RestRouteManager;
|
use WP_REST_Request;
|
use WP_REST_Response;
|
use WP_Error;
|
use Exception;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
/**
|
* Step 1: Build status/order/filter params
|
* Step 2: Get all regular notifications
|
* Step 3: Get all Content notifications
|
* Step 4: Get all Approval notifications
|
* Step 5: Merge in order of created date
|
* Step 6: Return result
|
*/
|
class NotificationsRoutes extends RestRouteManager
|
{
|
protected int $user_id;
|
protected array $notification_types = [];
|
protected object $manager;
|
protected array $typeMap = [
|
'favourite' => [
|
'new_favourite',
|
'list_shared',
|
],
|
'artist' => [
|
'new_artist',
|
'new_tattoo',
|
'new_piercing',
|
'new_event',
|
'new_update',
|
|
],
|
'partner' => [
|
'new_partner',
|
'new_offer',
|
],
|
'shop' => [
|
'new_shop',
|
'shop_update',
|
'shop_accepted',
|
'artist_request',
|
],
|
'event' => [
|
'new_event',
|
'event_reminder',
|
],
|
'news' => [
|
'new_update',
|
],
|
'system' => [
|
'system_message',
|
'artist_approved',
|
'artist_invitation',
|
'artist_request',
|
'shop_accepted',
|
'shop_rejected',
|
'new_term',
|
'term_approved',
|
'term_rejected',
|
],
|
];
|
protected array $notificationTableMap = [
|
// Regular notifications
|
'notifications' => [
|
'new_favourite',
|
'artist_approved',
|
'artist_rejected',
|
'artist_invitation',
|
'shop_invitation',
|
'artist_request',
|
'shop_accepted',
|
'shop_rejected',
|
'new_term',
|
'term_approved',
|
'term_rejected',
|
'list_shared',
|
'system_message'
|
],
|
|
// Content notifications (from artists the user follows)
|
'content_notifications' => [
|
'new_artist',
|
'new_tattoo',
|
'new_piercing',
|
'new_event',
|
'new_update',
|
'new_partner',
|
'new_offer',
|
'new_shop',
|
'shop_update',
|
'event_reminder'
|
]
|
];
|
|
public function __construct()
|
{
|
$this->cache_name = 'notifications';
|
parent::__construct();
|
|
$allTypes = [];
|
foreach ($this->typeMap as $key => $values) {
|
$allTypes = array_unique(array_merge($allTypes, $values));
|
}
|
|
$this->typeMap['all'] = $allTypes;
|
|
$this->user_id = get_current_user_id();
|
$this->action = 'notifications-';
|
|
add_action('init', [$this, 'init']);
|
|
add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
|
}
|
/**
|
* Format a notification for display
|
*
|
* @param object $notification Notification object
|
*
|
* @return array Formatted notification
|
*/
|
protected function formatNotification(object $notification):array
|
{
|
$config = $this->notification_types[$notification->type] ?? [];
|
$context = json_decode($notification->context ?? '{}', true);
|
|
// Get action user's name if available
|
$acting_user_name = null;
|
if ($notification->action_user_id) {
|
$acting_user_name = jvbShareName($notification->action_user_id);
|
}
|
|
return [
|
'id' => $notification->id,
|
'type' => $notification->type,
|
'message' => $notification->message,
|
'created_at' => $notification->created_at,
|
'status' => $notification->status,
|
'requires_action' => (bool)$notification->requires_action,
|
'action_taken' => (bool)$notification->action_taken,
|
'icon' => $config['icon'] ?? 'info',
|
'priority' => $notification->priority,
|
'target' => [
|
'id' => $notification->target_id,
|
'type' => $notification->target_type
|
],
|
'context' => $context,
|
'acting_user' => $notification->action_user_id ? [
|
'id' => $notification->action_user_id,
|
'name' => $acting_user_name
|
] : null
|
];
|
}
|
|
/**
|
* Set up required paramaters
|
* @return void
|
*/
|
public function init()
|
{
|
$this->manager = JVB()->notification();
|
$this->notification_types = $this->manager->getNotificationTypes();
|
}
|
|
/**
|
* Registers notification routes
|
* @return void
|
*/
|
public function registerRoutes():void
|
{
|
register_rest_route($this->namespace, '/notifications', [
|
[
|
'methods' => 'GET',
|
'callback' => [$this, 'getNotifications'],
|
'permission_callback' => [$this, 'checkPermission']
|
],
|
[
|
'methods' => 'POST',
|
'callback' => [$this, 'updateNotifications'],
|
'permission_callback' => [$this, 'checkPermission']
|
]
|
]);
|
}
|
|
|
/**
|
* @param int $ID
|
* @param string $content
|
*
|
* @return string
|
*/
|
protected function getItemLink(int $ID, string $content):string
|
{
|
error_log('Type: '.print_r($content, true));
|
switch ($content) {
|
case BASE.'artist':
|
case BASE.'artwork':
|
case BASE.'event':
|
case BASE.'news':
|
case BASE.'offer':
|
case BASE.'partner':
|
case BASE.'piercing':
|
case BASE.'tattoo':
|
return get_permalink($ID);
|
default:
|
return get_term_link($ID, BASE.$content);
|
}
|
}
|
|
/**
|
* Get notification actions
|
*
|
* @param string $type Notification type
|
* @param array $data Notification data
|
* @param object $notification Full notification object
|
*
|
* @return array Actions available for this notification
|
*/
|
protected function getNotificationActions(string $type, array $data, object $notification = null):array
|
{
|
error_log('Data for actions: '.print_r($data, true));
|
|
$actions = [];
|
|
switch ($type) {
|
case 'artist_approved':
|
case 'artist_rejected':
|
case 'shop_approved':
|
case 'shop_rejected':
|
case 'term_approved':
|
case 'term_rejected':
|
case 'system_message':
|
//No extra action needed
|
break;
|
case 'artist_invitation':
|
$actions[] = [
|
'icon' => 'upvote',
|
'label' => 'Approve',
|
'action' => 'acceptInvitation',
|
];
|
$actions[] = [
|
'icon' => 'downvote',
|
'label' => 'Reject',
|
'action' => 'reject_invitation',
|
];
|
break;
|
case 'artist_request':
|
$actions[] = [
|
'icon' => 'upvote',
|
'label' => 'Approve',
|
'action' => 'accept_to_shop',
|
];
|
$actions[] = [
|
'icon' => 'downvote',
|
'label' => 'Reject',
|
'action' => 'reject_to_shop',
|
];
|
break;
|
case 'artist_approval':
|
$actions[] = [
|
'icon' => 'upvote',
|
'label' => 'Approve',
|
'action' => 'accept_artist',
|
];
|
$actions[] = [
|
'icon' => 'downvote',
|
'label' => 'Reject',
|
'action' => 'reject_artist',
|
];
|
break;
|
|
case 'new_term':
|
$actions[] = [
|
'icon' => 'upvote',
|
'label' => 'Approve',
|
'action' => 'approve_term',
|
];
|
$actions[] = [
|
'icon' => 'downvote',
|
'label' => 'Reject',
|
'action' => 'reject_term',
|
];
|
break;
|
|
case 'list_shared':
|
if (!empty($data['list_id'])) {
|
$actions[] = [
|
'icon' => 'list-heart',
|
'label' => 'View List',
|
'url' => home_url("/dash/favourites/{$data['list_id']}"),
|
];
|
}
|
break;
|
default:
|
$actions[] = [
|
'icon' => 'link',
|
'label' => 'View',
|
'url' => $this->getItemLink($data['target_id'], $data['target_type']),
|
];
|
break;
|
}
|
|
$actions[] = [
|
'icon' => 'close',
|
'label' => 'Dismiss',
|
'action' => 'dismiss_notification'
|
];
|
// Allow customization via filter
|
return apply_filters('jvb_notification_actions', $actions, $type, $data, $notification);
|
}
|
|
/**
|
* @param int $user_id
|
* @param array $data
|
*
|
* @return array
|
*/
|
protected function getSanitizedData(int $user_id, array $data):array
|
{
|
$status = (array_key_exists('status', $data)) ? $data['status'] : 'unread';
|
$limit = (array_key_exists('limit', $data)) ? $data['limit'] : 20;
|
$offset = (array_key_exists('page', $data)) ? $data['page'] : 1;
|
$type = (array_key_exists('type', $data)) ? $data['type'] : 'all';
|
|
// Validate and sanitize status
|
$allowed_statuses = ['unread', 'read', 'actioned', 'dismissed', 'all'];
|
if (!in_array($status, $allowed_statuses)) {
|
$this->logError("Invalid notification status", [
|
'status' => $status,
|
'user' => $user_id
|
], 'warning');
|
$status = 'unread'; // Default to unread if invalid
|
}
|
|
if (!in_array($type, array_keys($this->typeMap))) {
|
$this->logError("Invalid notification type", [
|
'type' => $type,
|
'user' => $user_id
|
], 'warning');
|
$type = 'all';
|
}
|
|
// Validate and sanitize limit and offset
|
$limit = absint($limit);
|
if ($limit <= 0 || $limit > 100) {
|
$limit = 20; // Use reasonable default if invalid
|
}
|
|
$offset = absint($offset);
|
if ($offset < 0) {
|
$offset = 1;
|
}
|
|
$return = [
|
'status' => $status,
|
'limit' => $limit,
|
'page' => $offset,
|
'type' => $type
|
];
|
if (array_key_exists('grouped', $data)) {
|
$return['grouped'] = $data['grouped'];
|
}
|
return $return;
|
}
|
/**
|
* Get notifications for a user
|
*
|
* @param WP_REST_Request $request
|
* @return WP_REST_Response
|
*/
|
public function getNotifications(WP_REST_Request $request): WP_REST_Response
|
{
|
$data = $request->get_params();
|
$user_id = $data['user'];
|
if (!$this->userCheck($user_id)) {
|
$this->logError("Invalid user ID for notifications", [
|
'user' => $user_id
|
], 'warning');
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => 'User doesn\'t match. Are you a bot?'
|
]);
|
}
|
|
// Step 1: Build status/order/filter params
|
$params = $this->getSanitizedData($user_id, $data);
|
$status = $params['status'];
|
$limit = $params['limit'];
|
$offset = $params['page'];
|
$type = $params['type'];
|
|
// Try cache first with validated parameters
|
$cache_key = "user_{$user_id}_merged_notifications_{$status}_{$type}_{$limit}_{$offset}";
|
$cached = $this->cache->get($cache_key);
|
if ($cached) {
|
return new WP_REST_Response($cached);
|
}
|
|
try {
|
// Step 2: Get regular notifications
|
$regular_notifications = $this->getRegularNotifications($user_id, $params);
|
|
// Step 3: Get content notifications
|
$content_notifications = $this->getContentNotifications($user_id, $status, $limit, $offset);
|
|
// Step 4: Get approval notifications
|
$approval_notifications = $this->getApprovalNotifications($user_id, $status);
|
|
// Step 5: Merge in order of created date
|
$notifications = array_merge(
|
$regular_notifications,
|
$content_notifications,
|
$approval_notifications
|
);
|
|
usort($notifications, function ($a, $b) {
|
$date_a = strtotime($a['created_at'] ?? $a['date'] ?? date('Y-m-d H:i:s'));
|
$date_b = strtotime($b['created_at'] ?? $b['date'] ?? date('Y-m-d H:i:s'));
|
return $date_b - $date_a; // Sort from newest to oldest
|
});
|
|
// Apply pagination
|
$total_count = count($notifications);
|
$notifications = array_slice($notifications, 0, $limit);
|
|
// Step 6: Return result
|
$response = [
|
'notifications' => $notifications,
|
'pagination' => [
|
'total' => $total_count,
|
'page' => $offset,
|
'per_page' => $limit,
|
'pages' => ceil($total_count / $limit),
|
'has_more' => ($offset * $limit + count($notifications)) < $total_count
|
]
|
];
|
|
// Cache the result
|
$this->cache->set($cache_key, $response, 'notifications_' . $user_id);
|
return new WP_REST_Response($response);
|
} catch (Exception $e) {
|
$this->logError("Error retrieving notifications", [
|
'user_id' => $user_id,
|
'error' => $e->getMessage()
|
]);
|
|
return new WP_REST_Response([
|
'notifications' => [],
|
'pagination' => [
|
'total' => 0,
|
'page' => $offset,
|
'per_page' => $limit,
|
'pages' => 0,
|
'has_more' => false
|
]
|
]);
|
}
|
}
|
|
/**
|
* Get regular notifications from the notifications table
|
*
|
* @param int $user_id User ID
|
* @param array $params Filter parameters
|
* @return array Array of formatted notifications
|
*/
|
protected function getRegularNotifications(int $user_id, array $params): array
|
{
|
$status = $params['status'];
|
$limit = $params['limit'];
|
$offset = $params['page'];
|
$type = $params['type'];
|
|
// Try to get from cache first with validated parameters
|
$cache_key = "user_{$user_id}_regular_notifications_{$status}_{$type}_{$limit}_{$offset}";
|
$cached = $this->cache->get($cache_key);
|
if ($cached) {
|
return $cached;
|
}
|
|
global $wpdb;
|
$notifications_table = $wpdb->prefix . BASE . 'notifications';
|
|
// Build status condition
|
$status_condition = "1=1";
|
if ($status === 'unread') {
|
$status_condition = "status = 'unread'";
|
} elseif ($status === 'read') {
|
$status_condition = "status IN ('read', 'actioned')";
|
} elseif ($status !== 'all') {
|
$status_condition = $wpdb->prepare("status = %s", $status);
|
}
|
|
// Build type condition
|
$type_condition = "1=1";
|
if ($type !== 'all' && isset($this->typeMap[$type])) {
|
$types = $this->typeMap[$type];
|
if (!empty($types)) {
|
$placeholders = implode(',', array_fill(0, count($types), '%s'));
|
$type_condition = $wpdb->prepare("type IN ($placeholders)", $types);
|
}
|
}
|
|
// Get notifications
|
$notifications = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT * FROM {$notifications_table}
|
WHERE owner_id = %d AND {$status_condition} AND {$type_condition}
|
ORDER BY created_at DESC",
|
$user_id
|
)
|
);
|
|
// Format notifications
|
$formatted = [];
|
foreach ($notifications as $notification) {
|
$formatted[] = $this->formatNotification($notification);
|
}
|
|
// Cache the results
|
$this->cache->set($cache_key, $formatted, 'notifications_' . $user_id);
|
|
return $formatted;
|
}
|
|
/**
|
* Get approval notifications from the approval_requests table
|
*
|
* @param int $user_id User ID
|
* @param string $status Filter by status
|
* @return array Array of formatted approval notifications
|
*/
|
protected function getApprovalNotifications(int $user_id, string $status): array
|
{
|
// Try to get from cache first
|
$cache_key = "user_{$user_id}_approval_notifications_{$status}";
|
$cached = $this->cache->get($cache_key);
|
|
if ($cached) {
|
return $cached;
|
}
|
|
global $wpdb;
|
$formatted = [];
|
|
// Build status condition
|
$status_condition = "1=1";
|
if ($status === 'unread') {
|
$status_condition = "a.status = 'pending'";
|
} elseif ($status === 'read') {
|
$status_condition = "a.status IN ('approved', 'rejected')";
|
} elseif ($status !== 'all') {
|
$status_condition = $wpdb->prepare("a.status = %s", $status);
|
}
|
|
if ($this->isVerifiedUser($user_id)) {
|
$approvals = jvbApprovalTypes();
|
foreach ($approvals as $type => $config) {
|
$table = $wpdb->prefix.BASE.'approval_'.$type.'requests';
|
$votes = $wpdb->prefix.BASE.'approval_'.$type.'votes';
|
|
$approvals = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT a.*,
|
COALESCE(v.vote, 'none') as user_vote
|
FROM {$table} a
|
LEFT JOIN {$votes} v ON a.id = v.request_id AND v.user_id = %d
|
WHERE a.user_id != %d
|
AND {$status_condition}
|
ORDER BY a.created_at DESC",
|
$user_id,
|
$user_id
|
)
|
);
|
// Now filter out requests created by the current user
|
foreach ($approvals as $approval) {
|
$requested_by = json_decode($approval->requested_by, true);
|
|
// Skip if the current user is the requester
|
if (is_array($requested_by) && in_array($user_id, $requested_by)) {
|
continue;
|
}
|
|
$formatted[] = $this->formatApprovalNotification($approval);
|
}
|
}
|
}
|
|
// Cache the results
|
$this->cache->set($cache_key, $formatted, 'approvals');
|
|
return $formatted;
|
}
|
|
/**
|
* Format an approval request as a notification
|
*
|
* @param object $approval Approval request object
|
* @return array Formatted notification
|
*/
|
protected function formatApprovalNotification(object $approval): array
|
{
|
$data = json_decode($approval->data ?? '{}', true);
|
$type_labels = [
|
'artist_approval' => 'Artist Verification',
|
'term_suggestion' => 'Term Suggestion'
|
];
|
|
$status_labels = [
|
'pending' => 'Pending',
|
'approved' => 'Approved',
|
'rejected' => 'Rejected',
|
'expired' => 'Expired'
|
];
|
|
$icon = ($approval->type === 'artist_approval') ? 'artist' : 'style';
|
|
$message = '';
|
if ($approval->type === 'artist_approval') {
|
if ($approval->requested_by == $approval->target_id) {
|
$message = "Your artist verification is {$status_labels[$approval->status]}";
|
} else {
|
$name = isset($data['display_name']) ? $data['display_name'] : 'An artist';
|
$message = "{$name} is requesting verification";
|
}
|
} elseif ($approval->type === 'term_suggestion') {
|
$term_name = $data['term_name'] ?? 'A term';
|
$taxonomy = $data['taxonomy'] ?? '';
|
$taxonomy_name = str_replace(BASE, '', $taxonomy);
|
|
if ($approval->requested_by == get_current_user_id()) {
|
$message = "Your {$taxonomy_name} suggestion '{$term_name}' is {$status_labels[$approval->status]}";
|
} else {
|
$message = "New {$taxonomy_name} suggestion: '{$term_name}'";
|
}
|
}
|
|
return [
|
'id' => 'approval_' . $approval->id,
|
'type' => $approval->type,
|
'message' => $message,
|
'created_at' => $approval->created_at,
|
'status' => $approval->status,
|
'requires_action' => ($approval->status === 'pending' && $approval->requested_by != get_current_user_id()),
|
'action_taken' => !empty($approval->user_vote) && $approval->user_vote !== 'none',
|
'icon' => $icon,
|
'priority' => 'high',
|
'target' => [
|
'id' => $approval->target_id,
|
'type' => $approval->target_type
|
],
|
'context' => $data,
|
'approval_data' => [
|
'required_approvals' => $approval->required_approvals,
|
'current_approvals' => $approval->current_approvals,
|
'expires_at' => $approval->expires_at
|
]
|
];
|
}
|
|
/**
|
* Determine which table a notification type belongs to
|
*
|
* @param string $notificationType The notification type
|
* @return string The table name ('notifications' or 'content_notifications')
|
*/
|
protected function getTableForNotificationType(string $notificationType): string
|
{
|
foreach ($this->notificationTableMap as $table => $types) {
|
if (in_array($notificationType, $types)) {
|
return $table;
|
}
|
}
|
// Default to the main notifications table if type is unknown
|
return 'notifications';
|
}
|
|
/**
|
* Get grouped notifications for a user
|
* @param int $user_id User ID
|
* @param string $status notification status
|
* @param int $limit number of notifications to fetch
|
* @param int $offset page to fetch
|
* @param string $type notification type to fetch
|
* @return array Grouped notifications with pagination info
|
*/
|
public function getGroupedNotifications(int $user_id, string $status, int $limit, int $offset, string $type):array
|
{
|
$cache_key = "user_{$user_id}_grouped_notifications_{$status}_{$type}_{$limit}_{$offset}";
|
$cached = $this->cache->get($cache_key);
|
if ($cached !== false) {
|
return $cached;
|
}
|
|
global $wpdb;
|
|
// Build status condition
|
$status_condition = "1=1";
|
if ($status === 'unread') {
|
$status_condition = "status = 'unread'";
|
} elseif ($status === 'read') {
|
$status_condition = "status IN ('read', 'actioned')";
|
} elseif ($status !== 'all') {
|
$status_condition = $wpdb->prepare("status = %s", $status);
|
}
|
|
// Build type condition
|
$type_condition = "1=1";
|
if ($type !== 'all' && isset($this->typeMap[$type])) {
|
$types = $this->typeMap[$type];
|
if (!empty($types)) {
|
$placeholders = implode(',', array_fill(0, count($types), '%s'));
|
$type_condition = $wpdb->prepare("type IN ($placeholders)", $types);
|
}
|
}
|
try {
|
// Time window for grouping (e.g., last 24 hours)
|
$time_window = '24 HOUR';
|
|
$table = $wpdb->prefix.BASE.'notifications';
|
// Count notifications by action_user_id and type
|
$grouped_counts = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT
|
action_user_id,
|
type,
|
COUNT(*) as count,
|
MAX(created_at) as latest_time,
|
MIN(id) as first_id,
|
GROUP_CONCAT(target_type) as target_types
|
FROM {$table}
|
WHERE owner_id = %d
|
AND action_user_id IS NOT NULL
|
AND {$status_condition}
|
AND {$type_condition}
|
AND created_at > DATE_SUB(NOW(), INTERVAL {$time_window})
|
GROUP BY action_user_id, type
|
ORDER BY latest_time DESC
|
LIMIT %d OFFSET %d",
|
$user_id,
|
$limit,
|
$offset
|
)
|
);
|
|
|
// Get total count for pagination
|
$total_count = $wpdb->get_var(
|
$wpdb->prepare(
|
"SELECT COUNT(DISTINCT CONCAT(action_user_id, '_', type))
|
FROM {$table}
|
WHERE owner_id = %d
|
AND action_user_id IS NOT NULL
|
AND {$status_condition}
|
AND {$type_condition}
|
AND created_at > DATE_SUB(NOW(), INTERVAL {$time_window})",
|
$user_id
|
)
|
);
|
|
// Format the grouped notifications
|
$formatted = [];
|
foreach ($grouped_counts as $group) {
|
// Get acting user name
|
$acting_user_name = jvbGetUsername($group->action_user_id);
|
|
if ($group->count > 1) {
|
// Get unique target types for better message formatting
|
$target_types = array_unique(explode(',', $group->target_types));
|
|
// Build a grouped notification
|
$message = $this->buildGroupedMessage(
|
$acting_user_name,
|
$group->type,
|
$group->count,
|
$target_types
|
);
|
|
$formatted[] = [
|
'id' => 'group_' . $group->first_id,
|
'type' => $group->type,
|
'message' => $message,
|
'created_at' => $group->latest_time,
|
'timestamp' => strtotime($group->latest_time),
|
'status' => $status,
|
'icon' => $this->notification_types[$group->type]['icon'] ?? 'info',
|
'priority' => $this->notification_types[$group->type]['priority'] ?? 'normal',
|
'is_grouped' => true,
|
'group_count' => $group->count,
|
'acting_user' => [
|
'id' => $group->action_user_id,
|
'name' => $acting_user_name
|
],
|
'target_types' => $target_types
|
];
|
} else {
|
// Get the single notification details
|
$table = $wpdb->prefix.BASE.'notifications';
|
$notification = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE owner_id = %d
|
AND action_user_id = %d
|
AND type = %s
|
ORDER BY created_at DESC
|
LIMIT 1",
|
$user_id,
|
$group->action_user_id,
|
$group->type
|
)
|
);
|
|
if ($notification) {
|
$formatted[] = $this->formatNotification($notification);
|
}
|
}
|
}
|
|
// Prepare response with pagination info
|
$response = [
|
'notifications' => $formatted,
|
'pagination' => [
|
'total' => (int)$total_count,
|
'page' => $offset,
|
'per_page' => $limit,
|
'pages' => ceil($total_count / $limit),
|
'has_more' => ($offset + $limit) < $total_count
|
]
|
];
|
|
// Cache the results
|
$this->cache->set($cache_key, $response, 'notifications_' . $user_id);
|
|
return $response;
|
} catch (Exception $e) {
|
$this->logError("Error retrieving grouped notifications", [
|
'user_id' => $user_id,
|
'status' => $status,
|
'error' => $e->getMessage()
|
]);
|
|
return [
|
'notifications' => [],
|
'pagination' => [
|
'total' => 0,
|
'page' => $offset,
|
'per_page' => $limit,
|
'pages' => 0,
|
'has_more' => false
|
]
|
];
|
}
|
}
|
|
/**
|
* Build a message for grouped notifications
|
*
|
* @param string $user_name Acting user's name
|
* @param string $type Notification type
|
* @param int $count Number of grouped notifications
|
* @param array $target_types Types of targets involved
|
* @return string Formatted message
|
*/
|
protected function buildGroupedMessage(string $user_name, string $type, int $count, array $target_types = []):string
|
{
|
switch ($type) {
|
case 'new_favourite':
|
// If we have a single target type
|
if (count($target_types) === 1) {
|
$content_type = $this->manager->getContentTypeLabel($target_types[0]);
|
return "{$user_name} favourited {$count} of your {$content_type}";
|
}
|
return "{$user_name} favourited {$count} of your items";
|
|
case 'artist_request':
|
return "{$user_name} wants to join your shop";
|
|
// Add more cases for other notification types
|
default:
|
return "{$user_name} has {$count} notifications for you";
|
}
|
}
|
|
|
/**
|
* Build Notification request
|
*
|
* @param WP_REST_Request $request
|
* @return array Sanitized parameters for checking the cache
|
*/
|
protected function buildParams(WP_REST_Request $request):array
|
{
|
$request = $request->get_params();
|
return [
|
'status' => (array_key_exists('status', $request) && in_array($request['status'], ['all', 'unread', 'expired'])) ? $request['status'] : 'unread',
|
'user_id' => (array_key_exists('user', $request) && is_int($request['user'])) ? $request['user'] : get_current_user_id(),
|
'page' => (array_key_exists('page', $request) && is_numeric($request['page'])) ? $request['page'] : 1,
|
'type' => (array_key_exists('type', $request) && in_array($request['type'], array_keys($this->manager->notification_types))) ? $request['type'] : 'all',
|
];
|
}
|
|
/**
|
* @param WP_REST_Request $request
|
*
|
* @return WP_REST_Response
|
*/
|
public function updateNotifications(WP_REST_Request $request):WP_REST_Response
|
{
|
$data = $request->get_params();
|
$action = $request->get_param('action');
|
$notificationID = (array_key_exists('notification', $data) && is_int($data['notification'])) ? $data['notification'] : false;
|
$args = $this->buildParams($request);
|
$data = [];
|
$error = '';
|
switch ($action) {
|
case 'mark_as_read':
|
if (is_null($notificationID)) {
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => 'Notification ID is required'
|
]);
|
}
|
$data = [
|
'user_id' => $args['user_id'],
|
'notification_id' => $notificationID,
|
];
|
break;
|
case 'mark_all_as_read':
|
$data = [
|
'user_id' => $args['user_id'],
|
];
|
if ($request->get_param('notification_ids')) {
|
//To bulk mark all ids
|
$data['notification_ids'] = array_map('intval', $request->get_param('notification_ids'));
|
}
|
if ($request->get_param('type')) {
|
//To bulk select all items by type
|
$data['type'] = $request->get_param('type');
|
}
|
break;
|
case 'approve_artist':
|
case 'reject_artist':
|
//TODO: hook into the approval routes already set up
|
$handler = JVB()->routes('approvals');
|
$ID = $handler->getVerificationDetails($args['user_id']);
|
$notes = isset($request['notes']) ? sanitize_text_field($request['notes']) : '';
|
$handler->voteForArtist($args['user_id'], $ID['request'], $action==='approve_artist', $notes);
|
|
$this->markActioned($notificationID, $args['user_id']);
|
break;
|
case 'acceptInvitation':
|
case 'reject_invitation':
|
//TODO: hook into the shop routes already set up
|
$handler = JVB()->routes('shop');
|
$notification_data = $this->getNotification($notificationID);
|
|
if ($action === 'acceptInvitation') {
|
$result = $handler->acceptShopInvitation($args['user_id'], $args['shop_id']);
|
} else {
|
$result = $handler->declineShopInvitation($args['user_id'], $args['shop_id']);
|
}
|
|
$this->markActioned($notificationID, $args['user_id']);
|
break;
|
case 'accept_to_shop':
|
case 'reject_to_shop':
|
$handler = JVB()->routes('shop');
|
|
if ($action === 'accept_to_shop') {
|
$result = $handler->addArtistToShop($args['user_id'], $args['shop_id']);
|
} else {
|
//TODO: notify requester that their request has been denied
|
}
|
|
$this->markActioned($notificationID, $args['user_id']);
|
break;
|
case 'approve_term':
|
case 'reject_term':
|
$handler = JVB()->routes('approvals');
|
$notification_data = $this->getNotification($notificationID);
|
$notes = isset($request['notes']) ? sanitize_text_field($request['notes']) : '';
|
|
if ($action === 'approve_term') {
|
$result = $handler->approveTerm($args['user_id'], $notification_data, $notes);
|
} else {
|
$result = $handler->rejectTerm($args['user_id'], $notification_data, $notes);
|
}
|
|
$this->markActioned($notificationID, $args['user_id']);
|
break;
|
case 'dismiss_notification':
|
$data = [
|
'user_id' => $args['user_id'],
|
'notification_id' => $notificationID,
|
];
|
break;
|
|
default:
|
$error = 'Invalid action';
|
|
}
|
|
if (!empty($data)) {
|
JVB()->queue()->queueOperation(
|
'notification_'.$action,
|
$args['user_id'],
|
$data
|
);
|
return new WP_REST_Response([
|
'success' => true,
|
'message' => __('Notification queued for processing', 'jvb')
|
]);
|
} else {
|
return new WP_REST_Response([
|
'success' => false,
|
'message' => __('Error: '.$error, 'jvb')
|
]);
|
}
|
}
|
|
/**
|
* Get notification data by ID
|
* @param int $notification_id Notification ID
|
* @return array|false Notification data or false if not found
|
*/
|
protected function getNotification(int $notification_id):array|false
|
{
|
global $wpdb;
|
$table = $wpdb->prefix.BASE.'notifications';
|
$notification = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table} WHERE id = %d",
|
$notification_id
|
),
|
ARRAY_A
|
);
|
|
if (!$notification) {
|
return false;
|
}
|
|
// Parse context data
|
$context = !empty($notification['context'])
|
? json_decode($notification['context'], true)
|
: [];
|
|
return array_merge($notification, $context);
|
}
|
|
/**
|
* @param WP_Error|array $result
|
* @param object $operation
|
* @param array $data
|
*
|
* @return int|mixed
|
*/
|
public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
|
{
|
switch ($operation->type) {
|
case 'notification_mark_as_read':
|
$result = $this->markRead($data['notification_id'], $operation->user_id);
|
break;
|
case 'notification_mark_all_as_read':
|
$result = $this->markAllRead($data);
|
break;
|
case 'notification_dismiss_notification':
|
$result = $this->markDismissed($data['notification_id'], $operation->user_id);
|
break;
|
}
|
return $result;
|
}
|
|
/**
|
* Mark all notifications as read for a user
|
*
|
* @param array $data Data containing user_id and optional filters
|
*
|
* @return array Number of notifications marked as read
|
*/
|
public function markAllRead(array $data):array
|
{
|
$user_id = $data['user_id'];
|
$type = $data['type'] ?? null;
|
$notification_ids = $data['notification_ids'] ?? null;
|
|
global $wpdb;
|
$table = $wpdb->prefix . BASE . 'notifications';
|
|
$where = [
|
'owner_id = %d',
|
"status = 'unread'"
|
];
|
$params = [$user_id];
|
|
// Add type filter if specified
|
if ($type) {
|
$where[] = "type = %s";
|
$params[] = $type;
|
}
|
|
// Add specific IDs filter if provided
|
if ($notification_ids && is_array($notification_ids)) {
|
$id_placeholders = implode(',', array_fill(0, count($notification_ids), '%d'));
|
$where[] = "id IN ($id_placeholders)";
|
$params = array_merge($params, $notification_ids);
|
}
|
|
$where_clause = implode(' AND ', $where);
|
|
// Update all matching notifications
|
$updated = $wpdb->query($wpdb->prepare(
|
"UPDATE $table
|
SET status = 'read',
|
read_at = %s,
|
updated_at = %s
|
WHERE $where_clause",
|
array_merge(
|
[current_time('mysql'), current_time('mysql')],
|
$params
|
)
|
));
|
|
if ($updated) {
|
$this->trackNotificationMetrics(0, $user_id, 'batch_read', ['count' => $updated]);
|
$this->clearNotificationCache($user_id);
|
}
|
|
return [
|
'success' => true,
|
'result' => $updated
|
];
|
}
|
|
/**
|
* Mark a notification as read
|
*
|
* @param int $notification_id Notification ID
|
* @param int $user_id User ID making the request (for security)
|
*
|
* @return array Success or failure
|
*/
|
public function markRead(int $notification_id, int $user_id):array
|
{
|
if (!$this->checkUser($user_id)) {
|
return [
|
'success' => false,
|
'result' => 'Invalid user'
|
];
|
}
|
global $wpdb;
|
|
//TODO: We need to set up a system to check the main notification table, but also the content notification table
|
// Verify ownership
|
$table = $wpdb->prefix . BASE . 'notifications';
|
$notification = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE id = %d AND owner_id = %d",
|
$notification_id,
|
$user_id
|
)
|
);
|
|
if (!$notification) {
|
return [
|
'success' => false,
|
'result' => 'Invalid notification'
|
];
|
}
|
|
// Mark as read
|
$result = $wpdb->update(
|
$wpdb->prefix . BASE . 'notifications',
|
[
|
'status' => 'read',
|
'read_at' => current_time('mysql'),
|
'updated_at' => current_time('mysql')
|
],
|
['id' => $notification_id]
|
);
|
|
if ($result) {
|
$this->clearNotificationCache($user_id);
|
}
|
|
return [
|
'success' => $result !== false,
|
'result' => $result
|
];
|
}
|
|
/**
|
* Mark a notification as actioned
|
*
|
* @param int $notification_id Notification ID
|
* @param int $user_id User ID making the request
|
*
|
* @return array
|
*/
|
public function markActioned(int $notification_id, int $user_id):array
|
{
|
if (!$this->checkUser($user_id)) {
|
return [
|
'success' => false,
|
'message' => 'Invalid user'
|
];
|
}
|
global $wpdb;
|
|
//TODO: We need to set up a system to check the main notification table, but also the content notification table
|
// Start a transaction
|
$wpdb->query('START TRANSACTION');
|
|
try {
|
$table = $wpdb->prefix.BASE.'notifications';
|
// Verify ownership and that notification requires action
|
$notification = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE id = %d AND user_id = %d AND requires_action = 1
|
FOR UPDATE", // Lock the row
|
$notification_id,
|
$user_id
|
)
|
);
|
|
if (!$notification) {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Failed to action notification - not found or not owned", [
|
'notification_id' => $notification_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'No notification found, or invalid owner'
|
];
|
}
|
|
// Mark as actioned
|
$result = $wpdb->update(
|
$wpdb->prefix . BASE . 'notifications',
|
[
|
'status' => 'actioned',
|
'action_taken' => 1,
|
'actioned_at' => current_time('mysql'),
|
'updated_at' => current_time('mysql')
|
],
|
['id' => $notification_id]
|
);
|
|
if ($result !== false) {
|
$wpdb->query('COMMIT');
|
$this->clearNotificationCache($user_id);
|
return [
|
'success' => true,
|
'message' => 'Notification actioned'
|
];
|
} else {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Database error marking notification as actioned", [
|
'notification_id' => $notification_id,
|
'user_id' => $user_id,
|
'db_error' => $wpdb->last_error
|
]);
|
return [
|
'success' => false,
|
'message' => 'Error'
|
];
|
}
|
} catch (Exception $e) {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Exception marking notification as actioned", [
|
'notification_id' => $notification_id,
|
'user_id' => $user_id,
|
'error' => $e->getMessage()
|
]);
|
return [
|
'success' => false,
|
'message' => $e->getMessage(),
|
];
|
}
|
}
|
|
/**
|
* Dismiss a notification
|
*
|
* @param int $notification_id Notification ID
|
* @param int $user_id User ID making the request
|
*
|
* @return array Success or failure
|
*/
|
public function markDismissed(int $notification_id, int $user_id):array{
|
if (!$this->checkUser($user_id)) {
|
return [
|
'success' => false,
|
'result' => 'Invalid User',
|
];
|
}
|
global $wpdb;
|
|
// Verify ownership
|
$table = $wpdb->prefix.BASE.'notifications';
|
$notification = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE id = %d AND user_id = %d",
|
$notification_id,
|
$user_id
|
)
|
);
|
//TODO: We need to set up a system to check the main notification table, but also the content notification table
|
if (!$notification) {
|
return [
|
'success' => false,
|
'result' => 'Invalid notification'
|
];
|
}
|
|
// Mark as dismissed
|
$result = $wpdb->update(
|
$wpdb->prefix . BASE . 'notifications',
|
[
|
'status' => 'dismissed',
|
'updated_at' => current_time('mysql')
|
],
|
[ 'id' => $notification_id ]
|
);
|
|
if ($result) {
|
$this->clearNotificationCache($user_id);
|
}
|
|
return [
|
'success' => $result !== false,
|
'result' => $result
|
];
|
}
|
|
/***
|
* Content notifications (in a separate database))
|
*/
|
/**
|
* Get content notifications for a user
|
*
|
* @param int $user_id User ID
|
* @param string $status Status filter (unread, read, all)
|
* @param int $limit Maximum number of notifications to return
|
* @param int $offset Pagination offset
|
*
|
* @return array Content notifications
|
*/
|
public function getContentNotifications(
|
int $user_id,
|
string $status = 'unread',
|
int $limit = 20,
|
int $offset = 0
|
):array {
|
if (!$this->checkUser($user_id)) {
|
return [];
|
}
|
// Try cache first
|
$cache_key = "user_{$user_id}_content_notifications_{$status}_{$limit}_{$offset}";
|
$cached = $this->cache->get($cache_key);
|
|
if ($cached !== false) {
|
return $cached;
|
}
|
|
global $wpdb;
|
|
// Build status condition
|
$status_condition = "1=1";
|
if ($status === 'unread') {
|
$status_condition = "seen.status = 'unread'";
|
} elseif ($status === 'read') {
|
$status_condition = "seen.status = 'read'";
|
} elseif ($status !== 'all') {
|
$status_condition = $wpdb->prepare("seen.status = %s", $status);
|
}
|
|
// Get content notifications this user has seen records for
|
|
$notifications = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT seen.*, content.*
|
FROM {$wpdb->prefix}{$this->base}notifications_user_seen AS seen
|
JOIN {$wpdb->prefix}{$this->base}notifications_content AS content
|
ON seen.content_notification_id = content.id
|
WHERE seen.user_id = %d AND {$status_condition}
|
ORDER BY content.date DESC, content.created_at DESC
|
LIMIT %d OFFSET %d",
|
$user_id,
|
$limit,
|
$offset
|
)
|
);
|
|
// Format content notifications
|
$formatted = [];
|
foreach ($notifications as $notification) {
|
$formatted[] = $this->formatContentNotification($notification);
|
}
|
|
// Cache the results
|
$this->cache->set($cache_key, $formatted, 'notifications_' . $user_id);
|
|
return $formatted;
|
}
|
|
/**
|
* Format a content notification for display
|
*
|
* @param object $notification Content notification object
|
*
|
* @return array Formatted content notification
|
*/
|
protected function formatContentNotification(object $notification):array
|
{
|
// Get artist data
|
$artist_data = jvbContentFromUser($notification->user_id);
|
|
// Parse JSON data
|
$new_items = json_decode($notification->new_items, true) ?: [];
|
$updated_items = json_decode($notification->updated_items, true) ?: [];
|
|
// Count items by type
|
$counts_by_type = [];
|
$total_new = 0;
|
|
foreach ($new_items as $type => $ids) {
|
$clean_type = str_replace(BASE, '', $type);
|
$counts_by_type[ $clean_type ] = [
|
'new' => count($ids),
|
'updated' => 0
|
];
|
$total_new += count($ids);
|
}
|
|
foreach ($updated_items as $type => $ids) {
|
$clean_type = str_replace(BASE, '', $type);
|
if (!isset($counts_by_type[ $clean_type ])) {
|
$counts_by_type[ $clean_type ] = [
|
'new' => 0
|
];
|
}
|
$counts_by_type[ $clean_type ]['updated'] = count($ids);
|
}
|
|
// Build summary text
|
$summary = [];
|
foreach ($counts_by_type as $type => $counts) {
|
if ($counts['new'] > 0) {
|
$label = $counts['new'] === 1 ? $type : $this->pluralize($type);
|
$summary[] = "{$counts['new']} new {$label}";
|
}
|
if ($counts['updated'] > 0) {
|
$label = $counts['updated'] === 1 ? $type : $this->pluralize($type);
|
$summary[] = "{$counts['updated']} updated {$label}";
|
}
|
}
|
|
return [
|
'id' => $notification->id,
|
'seen_id' => $notification->content_notification_id,
|
'status' => $notification->status,
|
'date' => $notification->date,
|
'artist' => $artist_data,
|
'new_items' => $new_items,
|
'updated_items' => $updated_items,
|
'summary' => implode(', ', $summary),
|
'total_new' => $total_new,
|
'total_updated' => $notification->total_items - $total_new,
|
'counts_by_type' => $counts_by_type,
|
'has_profile_update' => (bool) $notification->has_profile_update,
|
'created_at' => $notification->created_at
|
];
|
}
|
|
/**
|
* Mark a content notification as read
|
*
|
* @param int $seen_id User seen record ID
|
* @param int $user_id User ID making the request
|
*
|
* @return array Success or failure
|
*/
|
public function markContentNotificationRead(int $seen_id, int $user_id):array
|
{
|
// Validate input parameters
|
if (!$this->checkUser($seen_id)) {
|
$this->logError("Invalid seen ID", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'Invalid User ID'
|
];
|
}
|
|
if (!$this->checkUser($user_id)) {
|
$this->logError("Invalid user ID", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'Invalid User ID'
|
];
|
}
|
|
global $wpdb;
|
$table = $wpdb->prefix . BASE . 'notifications_user_seen';
|
|
// Start transaction
|
$wpdb->query('START TRANSACTION');
|
|
try {
|
// Verify ownership with row locking
|
$seen_record = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE id = %d AND user_id = %d
|
FOR UPDATE",
|
$seen_id,
|
$user_id
|
)
|
);
|
|
if (!$seen_record) {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Seen record not found or not owned by user", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'Seen record not found or not owned by user'
|
];
|
}
|
|
// Mark as read
|
$result = $wpdb->update(
|
$table,
|
[
|
'status' => 'read',
|
'read_at' => current_time('mysql')
|
],
|
['id' => $seen_id]
|
);
|
|
if ($result !== false) {
|
$wpdb->query('COMMIT');
|
$this->clearNotificationCache($user_id);
|
return [
|
'success' => true,
|
'message' => 'Successfully marked as seen'
|
];
|
} else {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Database error marking content notification as read", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id,
|
'db_error' => $wpdb->last_error
|
]);
|
return [
|
'success' => false,
|
'message' => 'Something went wrong...'
|
];
|
}
|
} catch (Exception $e) {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Exception marking content notification as read", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id,
|
'error' => $e->getMessage()
|
]);
|
return [
|
'success' => false,
|
'message' => $e->getMessage()
|
];
|
}
|
}
|
|
|
/**
|
* Dismiss a content notification
|
*
|
* @param int $seen_id User seen record ID
|
* @param int $user_id User ID making the request
|
*
|
* @return array Success or failure
|
*/
|
public function dismissContentNotification( int $seen_id, int $user_id ):array
|
{
|
// Validate input parameters
|
if (!$this->checkUser($seen_id)) {
|
$this->logError("Invalid seen ID", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'Invalid User ID'
|
];
|
}
|
|
if (!$this->checkUser($user_id)) {
|
$this->logError("Invalid user ID", [
|
'seen_id' => $seen_id,
|
'user_id' => $user_id
|
], 'warning');
|
return [
|
'success' => false,
|
'message' => 'Invalid User ID'
|
];
|
}
|
global $wpdb;
|
|
// Verify ownership
|
$seen_record = $wpdb->get_row(
|
$wpdb->prepare(
|
"SELECT * FROM {$wpdb->prefix}{$this->base}notifications_user_seen
|
WHERE id = %d AND user_id = %d",
|
$seen_id,
|
$user_id
|
)
|
);
|
|
if (!$seen_record) {
|
return [
|
'success' => false,
|
'message' => 'No notification found'
|
];
|
}
|
|
// Mark as dismissed
|
$result = $wpdb->update(
|
$wpdb->prefix . BASE . 'notifications_user_seen',
|
[
|
'status' => 'dismissed'
|
],
|
[ 'id' => $seen_id ]
|
);
|
|
if ($result) {
|
$this->clearNotificationCache($user_id);
|
}
|
|
return [
|
'success' => $result !== false,
|
'message' => 'Operation completed',
|
];
|
}
|
|
/**
|
* Simple pluralization helper
|
*
|
* @param string $word Word to pluralize
|
* @return string Pluralized word
|
*/
|
protected function pluralize(string $word): string
|
{
|
$irregular = [
|
'tattoo' => 'tattoos',
|
'piercing' => 'piercings',
|
'artwork' => 'artwork',
|
'news' => 'news',
|
'offer' => 'offers',
|
'event' => 'events'
|
];
|
|
if (isset($irregular[$word])) {
|
return $irregular[$word];
|
}
|
|
// Simple pluralization rules
|
if (substr($word, -1) === 'y') {
|
return substr($word, 0, -1) . 'ies';
|
}
|
|
return $word . 's';
|
}
|
/**
|
* Track notification metrics for analytics
|
*
|
* @param int $notification_id Notification ID (0 for batch operations)
|
* @param int $user_id User ID
|
* @param string $action Action taken (read, dismiss, etc.)
|
* @param array $details Additional details
|
* @return bool Success or failure
|
*/
|
protected function trackNotificationMetrics(int $notification_id, int $user_id, string $action, array $details = []): bool
|
{
|
global $wpdb;
|
$metrics_table = $wpdb->prefix . BASE . 'notification_metrics';
|
|
try {
|
return $wpdb->insert(
|
$metrics_table,
|
[
|
'notification_id' => $notification_id,
|
'user_id' => $user_id,
|
'action' => $action,
|
'action_source' => 'web',
|
'action_details' => json_encode($details),
|
'created_at' => current_time('mysql')
|
]
|
) !== false;
|
} catch (Exception $e) {
|
$this->logError("Failed to track notification metrics", [
|
'user_id' => $user_id,
|
'action' => $action,
|
'error' => $e->getMessage()
|
]);
|
return false;
|
}
|
}
|
|
/**
|
* Clear notification cache for a user, for all notification types
|
*
|
* @param int $user_id User ID
|
* @return void
|
*/
|
protected function clearNotificationCache(int $user_id): void
|
{
|
// Clear regular notifications cache
|
$this->cache->invalidate("user_{$user_id}_notifications_");
|
$this->cache->invalidate("user_{$user_id}_regular_notifications_");
|
|
// Clear content notifications cache
|
$this->cache->invalidate("user_{$user_id}_content_notifications_");
|
|
// Clear approval notifications cache
|
$this->cache->invalidate("user_{$user_id}_approval_notifications_");
|
|
// Clear merged notifications cache
|
$this->cache->invalidate("user_{$user_id}_merged_notifications_");
|
}
|
}
|