<?php
|
namespace JVBase\managers;
|
|
use JVBase\JVB;
|
use WP_Error;
|
use Exception;
|
use WP_Post;
|
use WP_User;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
//TODO: Ensure this works with the constants setup
|
/**
|
* NotificationManager - Centralized notification system for edmonton.ink
|
*
|
* This refactored class handles notifications with a clearer separation of concerns
|
* between system notifications and content updates. It uses the new database structure
|
* to efficiently track and display notifications.
|
*/
|
class NotificationManager
|
{
|
protected object $cache;
|
protected string $campaign;
|
protected string $table = BASE.'notifications';
|
protected string $contentTable = BASE.'notifications_content';
|
protected string $seenTable = BASE.'notifications_user_seen';
|
protected string $preferencesTable = BASE.'notification_preferences';
|
protected array $notification_types = [
|
// System notifications
|
'new_favourite' => [
|
'icon' => 'heart',
|
'priority' => 'low',
|
'requires_action' => false,
|
'audience' => 'content_owner',
|
'email_digest' => true
|
],
|
'artist_approved' => [
|
'icon' => 'check',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'artist_joined' => [
|
'icon' => 'artist',
|
'priority' => 'low',
|
'requires_action' => false,
|
'email_digest' => false,
|
],
|
'artist_rejected' => [
|
'icon' => 'x',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'artist_invitation' => [
|
'icon' => 'invite',
|
'priority' => 'high',
|
'requires_action' => true,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'artist_request' => [
|
'icon' => 'artist',
|
'priority' => 'high',
|
'requires_action' => true,
|
'audience' => 'single',
|
'email_digest' => true,
|
],
|
'shop_accepted' => [
|
'icon' => 'shop',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true,
|
],
|
'shop_rejected' => [
|
'icon' => 'shop',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true,
|
],
|
'new_term' => [
|
'icon' => 'style',
|
'priority' => 'medium',
|
'requires_action' => true,
|
'audience' => 'artists',
|
'email_digest' => false
|
],
|
'term_approved' => [
|
'icon' => 'check',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'term_rejected' => [
|
'icon' => 'x',
|
'priority' => 'medium',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'list_shared' => [
|
'icon' => 'share',
|
'priority' => 'medium',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'list_share_accepted' => [
|
'icon' => 'check',
|
'priority' => 'low',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'list_share_revoked' => [
|
'icon' => 'close',
|
'priority' => 'medium',
|
'requires_action' => false,
|
'audience' => 'single',
|
'email_digest' => true
|
],
|
'system_message' => [
|
'icon' => 'info',
|
'priority' => 'high',
|
'requires_action' => false,
|
'audience' => 'all',
|
'email_digest' => false
|
]
|
];
|
|
/**
|
* Constructor
|
*/
|
public function __construct()
|
{
|
$this->cache = CacheManager::for('notifications', WEEK_IN_SECONDS);
|
|
// Add filter for bulk operation handling
|
add_filter(BASE . 'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3);
|
|
add_action(BASE . 'cleanup_notifications', [$this, 'cleanupOldNotifications']);
|
|
// Track content creation for notifications
|
add_action('save_post', [ $this, 'trackContentCreation' ], 10, 3);
|
add_action('saved_term', [ $this, 'track_shop_creation' ], 10, 3);
|
|
// Generate content summaries when users log in
|
add_action('wp_login', [ $this, 'generateUserContentNotifications' ], 10, 2);
|
|
// Register digest cron jobs
|
$this->registerCron();
|
}
|
|
/**
|
* Registers the digest cron jobs
|
* @return void
|
*/
|
protected function registerCron():void
|
{
|
add_action(BASE . 'notification_digest_daily', [ $this, 'runDailyDigests' ]);
|
add_action(BASE . 'notification_digest_weekly', [ $this, 'runWeeklyDigests' ]);
|
add_action(BASE . 'notification_digest_monthly', [ $this, 'runMonthlyDigests' ]);
|
}
|
|
/************************************************************
|
* Basic Notification Methods
|
************************************************************/
|
/**
|
* @return array
|
*/
|
public function getNotificationTypes():array
|
{
|
return $this->notification_types;
|
}
|
/**
|
* Add a new notification
|
*
|
* @param int|array $user_ids Recipient user ID(s)
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function addNotification(
|
mixed $user_ids,
|
string $type,
|
int|null $action_user_id = null,
|
string $message = '',
|
int|null $target_id = null,
|
string|null $target_type = null,
|
array|null $context = null
|
):bool|WP_Error {
|
// Validate notification type
|
if (!isset($this->notification_types[$type])) {
|
$this->logError("Invalid notification type: $type", [
|
'user_ids' => is_array($user_ids) ? implode(',', $user_ids) : $user_ids,
|
'message' => $message
|
], 'warning');
|
return new WP_Error('invalid_type', 'Invalid notification type');
|
}
|
|
// Handle single user or array of users
|
$user_ids = is_array($user_ids) ? $user_ids : [$user_ids];
|
|
if (empty($user_ids)) {
|
return false;
|
}
|
// Queue the bulk notification operation
|
$queue = JVB()->queue();
|
$queue->queueOperation(
|
'addNotification',
|
$action_user_id,
|
[
|
'user_ids' => $user_ids,
|
'type' => $type,
|
'action_user_id' => $action_user_id,
|
'message' => $message,
|
'target_id' => $target_id,
|
'target_type' => $target_type,
|
'context' => $context
|
],
|
[
|
'count' => count($user_ids),
|
'priority' => 'normal',
|
'chunk_key' => 'user_ids',
|
'chunk_size'=> 50,
|
'operation_id' => 'notifications' . uniqid()
|
]
|
);
|
|
return true;
|
}
|
|
/**
|
* Process notification operation
|
*
|
* @param int|array $user_ids Recipient user ID(s)
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function processNotification(
|
int|array $user_ids,
|
string $type,
|
int|null $action_user_id = null,
|
string $message = '',
|
int|null $target_id = null,
|
string|null $target_type = null,
|
array|null $context = null
|
):bool|WP_Error {
|
$config = $this->notification_types[$type];
|
$notifications = [];
|
$errors = [];
|
|
$user_ids = is_array($user_ids) ? $user_ids : [$user_ids];
|
|
global $wpdb;
|
foreach ($user_ids as $user_id) {
|
// Skip invalid users
|
if (!$user_id || $user_id <= 0) {
|
$errors[] = "Invalid user ID: $user_id";
|
continue;
|
}
|
|
// Skip sending notifications to self if action_user_id == user_id
|
if ($action_user_id && $action_user_id == $user_id && !($config['notify_self'] ?? false)) {
|
continue;
|
}
|
|
try {
|
// Prepare context data
|
$context_json = null;
|
if (!empty($context)) {
|
$context_json = json_encode($context);
|
}
|
|
// Insert new notification
|
$result = $wpdb->insert(
|
$wpdb->prefix . $this->table,
|
[
|
'owner_id' => $user_id,
|
'action_user_id' => $action_user_id,
|
'type' => $type,
|
'status' => 'unread',
|
'message' => $message,
|
'priority' => $config['priority'] ?? 'normal',
|
'target_id' => $target_id,
|
'target_type' => $target_type,
|
'context' => $context_json,
|
'requires_action' => !empty($config['requires_action']) ? 1 : 0,
|
'created_at' => current_time('mysql'),
|
'updated_at' => current_time('mysql')
|
]
|
);
|
|
if ($result) {
|
$notification_id = $wpdb->insert_id;
|
$notifications[] = $notification_id;
|
|
// Clear cache for this user
|
$this->clearNotificationCache($user_id);
|
} else {
|
$errors[] = "Database error for user $user_id: " . $wpdb->last_error;
|
$this->logError("Failed to insert notification", [
|
'user_id' => $user_id,
|
'type' => $type,
|
'db_error' => $wpdb->last_error
|
]);
|
}
|
} catch (Exception $e) {
|
$errors[] = "Exception for user $user_id: " . $e->getMessage();
|
$this->logError("Error adding notification", [
|
'user_id' => $user_id,
|
'type' => $type,
|
'message' => $message,
|
'error' => $e->getMessage()
|
]);
|
}
|
}
|
|
// Return results
|
if (!empty($notifications) && !empty($errors)) {
|
$this->logError("Some notifications failed", [
|
'successful' => count($notifications),
|
'errors' => $errors
|
], 'warning');
|
}
|
|
if (empty($notifications) && !empty($errors)) {
|
return new WP_Error('notification_failed', 'All notifications failed', [
|
'errors' => $errors
|
]);
|
}
|
|
return !empty($notifications);
|
}
|
|
/**
|
* Wrapper for notifying verified artists
|
*
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function notifyVerifiedArtists(string $type, int|null $action_user_id = null, string $message = '', int|null $target_id = null, string|null $target_type = null, array|null $context = null):bool|WP_Error
|
{
|
$artists = $this->getVerifiedArtists();
|
return $this->addNotification($artists, $type, $action_user_id, $message, $target_id, $target_type, $context);
|
}
|
/**
|
* Wrapper for notifying verified partners
|
*
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function notifyVerifiedPartners(string $type, int|null $action_user_id = null, string $message = '', int|null $target_id = null, string|null $target_type = null, array|null $context = null):bool|WP_Error
|
{
|
$artists = $this->getVerifiedPartners();
|
return $this->addNotification($artists, $type, $action_user_id, $message, $target_id, $target_type, $context);
|
}
|
/**
|
* Wrapper for notifying verified partners
|
*
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function notifyEnthusiasts(string $type, int|null $action_user_id = null, string $message = '', int|null $target_id = null, string|null $target_type = null, array|null $context = null):bool|WP_Error
|
{
|
$artists = $this->getEnthusiasts();
|
return $this->addNotification($artists, $type, $action_user_id, $message, $target_id, $target_type, $context);
|
}
|
/**
|
* Wrapper for notifying verified partners
|
*
|
* @param string $type Notification type
|
* @param int|null $action_user_id User who performed the action (optional)
|
* @param string $message Notification message (optional)
|
* @param int|null $target_id Related target ID (optional)
|
* @param string|null $target_type Related target type (optional)
|
* @param array|null $context Additional contextual data (optional)
|
*
|
* @return bool|WP_Error Success or error
|
*/
|
public function notifyEveryone(string $type, int|null $action_user_id = null, string $message = '', int|null $target_id = null, string|null $target_type = null, array|null $context = null):bool|WP_Error
|
{
|
$artists = $this->getEveryone();
|
return $this->addNotification($artists, $type, $action_user_id, $message, $target_id, $target_type, $context);
|
}
|
|
|
|
/************************************************************
|
* Content Notification Methods
|
************************************************************/
|
|
/**
|
* Track content creation or updates for notification purposes
|
*
|
* @param int $post_id Post ID
|
* @param WP_Post $post Post object
|
* @param bool $update Whether this is an update
|
* @return void
|
*/
|
public function trackContentCreation(int $post_id, WP_POST $post, bool $update):void
|
{
|
// Skip if not a published post
|
if ($post->post_status !== 'publish') {
|
return;
|
}
|
|
// Check if this is a relevant content type
|
$content_types = jvbBasedFeedContent();
|
if (!in_array($post->post_type, $content_types)) {
|
return;
|
}
|
|
// Check if the artist has any followers before tracking
|
$follower_count = $this->getFollowerCount($post->post_author);
|
if ($follower_count === 0) {
|
return; // No need to track if nobody follows this artist
|
}
|
|
// Get the clean content type for storing
|
$content_type = str_replace(BASE, '', $post->post_type);
|
|
// Update the artist's content notification records
|
$this->updateContentNotificationRecord($post->post_author, $content_type, $post_id, $update);
|
}
|
|
/**
|
* Update content notification record for an artist
|
*
|
* @param int $artist_id Artist user ID
|
* @param string $content_type Content type (tattoo, artwork, etc)
|
* @param int $content_id Content post ID
|
* @param bool $is_update Whether this is an update to existing content
|
*
|
* @return bool Success or failure
|
*/
|
protected function updateContentNotificationRecord(int $artist_id, string $content_type, int $content_id, bool $is_update = false):bool
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . $this->contentTable;
|
|
// Start transaction
|
$wpdb->query('START TRANSACTION');
|
$success = false;
|
|
try {
|
// Get today's date
|
$today = date('Y-m-d');
|
$frequency_updates = [];
|
|
// Update records for each frequency
|
foreach (['daily', 'weekly', 'monthly'] as $frequency) {
|
// Find or create a record for this artist, date and frequency
|
$record = $wpdb->get_row($wpdb->prepare(
|
"SELECT * FROM {$table}
|
WHERE user_id = %d AND date = %s AND frequency = %s",
|
$artist_id,
|
$today,
|
$frequency
|
));
|
|
if ($record) {
|
$frequency_updates[$frequency] = $this->updateExistingContentRecord(
|
$record,
|
$content_type,
|
$content_id,
|
$is_update
|
);
|
} else {
|
$frequency_updates[$frequency] = $this->createNewContentRecord(
|
$artist_id,
|
$frequency,
|
$content_type,
|
$content_id,
|
$is_update
|
);
|
}
|
}
|
|
// Check if all updates were successful
|
$success = !in_array(false, $frequency_updates, true);
|
|
if ($success) {
|
$wpdb->query('COMMIT');
|
return true;
|
} else {
|
// If any update failed, roll back
|
$wpdb->query('ROLLBACK');
|
$this->logError("Failed to update content notification records", [
|
'artist_id' => $artist_id,
|
'content_type' => $content_type,
|
'content_id' => $content_id,
|
'updates' => $frequency_updates
|
]);
|
return false;
|
}
|
} catch (Exception $e) {
|
$wpdb->query('ROLLBACK');
|
$this->logError("Exception in content notification update", [
|
'artist_id' => $artist_id,
|
'content_type' => $content_type,
|
'content_id' => $content_id,
|
'error' => $e->getMessage()
|
]);
|
return false;
|
}
|
}
|
|
/**
|
* Update an existing content notification record
|
*
|
* @param object $record Existing database record
|
* @param string $content_type Content type
|
* @param int $content_id Content ID
|
* @param bool $is_update Whether this is an update
|
*
|
* @return bool Success or failure
|
*/
|
protected function updateExistingContentRecord(object $record, string $content_type, int $content_id, bool $is_update):bool
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . $this->contentTable;
|
|
// Decode existing JSON data
|
$new_items = json_decode($record->new_items, true) ?: [];
|
$updated_items = json_decode($record->updated_items, true) ?: [];
|
|
// Initialize arrays if not present
|
if (!isset($new_items[BASE.$content_type])) {
|
$new_items[BASE.$content_type] = [];
|
}
|
if (!isset($updated_items[BASE.$content_type])) {
|
$updated_items[BASE.$content_type] = [];
|
}
|
|
// Add ID to appropriate array
|
if ($is_update) {
|
// Add to updated array if not already there and not in new items
|
if (!in_array($content_id, $updated_items[BASE.$content_type]) &&
|
!in_array($content_id, $new_items[BASE.$content_type])) {
|
$updated_items[BASE.$content_type][] = $content_id;
|
}
|
} else {
|
// Add to new array if not already there
|
if (!in_array($content_id, $new_items[BASE.$content_type])) {
|
$new_items[BASE.$content_type][] = $content_id;
|
}
|
}
|
|
// Prepare update data
|
$update_data = [
|
'new_items' => json_encode($new_items),
|
'updated_items' => json_encode($updated_items),
|
'updated_at' => current_time('mysql')
|
];
|
|
// Update appropriate count column
|
$count_column = "{$content_type}_count";
|
if (property_exists($record, $count_column)) {
|
$update_data[ $count_column ] = $record->$count_column + ( $is_update ? 0 : 1 );
|
}
|
|
// Update total count
|
$update_data['total_items'] = $record->total_items + ( $is_update ? 0 : 1 );
|
|
// Update the record
|
return $wpdb->update(
|
$table,
|
$update_data,
|
[ 'id' => $record->id ]
|
) !== false;
|
}
|
|
/**
|
* Create a new content notification record
|
*
|
* @param int $artist_id Artist user ID
|
* @param string $frequency Frequency (daily, weekly, monthly)
|
* @param string $content_type Content type
|
* @param int $content_id Content ID
|
* @param bool $is_update Whether this is an update
|
*
|
* @return bool Success or failure
|
*/
|
protected function createNewContentRecord(int $artist_id, string $frequency, string $content_type, int $content_id, bool $is_update):bool
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . $this->contentTable;
|
|
// Initialize arrays for new and updated items
|
$new_items = [];
|
$updated_items = [];
|
|
if ($is_update) {
|
$updated_items[BASE.$content_type] = [ $content_id ];
|
} else {
|
$new_items[BASE.$content_type] = [ $content_id ];
|
}
|
|
// Prepare insert data
|
$insert_data = [
|
'user_id' => $artist_id,
|
'date' => date('Y-m-d'),
|
'frequency' => $frequency,
|
'total_items' => 1,
|
'new_items' => json_encode($new_items),
|
'updated_items' => json_encode($updated_items),
|
'created_at' => current_time('mysql'),
|
'updated_at' => current_time('mysql')
|
];
|
|
// Set count column
|
$insert_data["{$content_type}_count"] = $is_update ? 0 : 1;
|
|
// Insert the record
|
return $wpdb->insert($table, $insert_data) !== false;
|
}
|
|
/**
|
* Generate content notifications for a user upon login
|
*
|
* @param string $username Username
|
* @param WP_User $user User object
|
* @return void
|
*/
|
public function generateUserContentNotifications(string $username, WP_User $user):void
|
{
|
$this->createContentSeenRecords($user->ID);
|
}
|
|
/**
|
* Create content seen records for a user's followed artists
|
*
|
* @param int $user_id User ID
|
*
|
* @return int Number of notifications created
|
*/
|
protected function createContentSeenRecords(int $user_id):int
|
{
|
global $wpdb;
|
|
try {
|
// Get followed artists
|
$followed_artists = $this->getFollowedArtists($user_id);
|
if (empty($followed_artists)) {
|
return 0;
|
}
|
|
// Get the last time notifications were checked
|
$last_check = get_user_meta($user_id, BASE . 'last_content_check', true);
|
$since_date = $last_check ? date('Y-m-d', strtotime($last_check)) : date('Y-m-d', strtotime('-7 days'));
|
|
// Create placeholders for SQL query
|
$placeholders = implode(',', array_fill(0, count($followed_artists), '%d'));
|
|
// Get content notifications since last check
|
$content_records = $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT * FROM {$wpdb->prefix}{$this->contentTable}
|
WHERE user_id IN ($placeholders)
|
AND date >= %s
|
AND total_items > 0",
|
array_merge($followed_artists, [ $since_date ])
|
)
|
);
|
|
if (empty($content_records)) {
|
return 0;
|
}
|
|
// Add user seen records for each content notification
|
$count = 0;
|
$user_seen_table = $wpdb->prefix . $this->seenTable;
|
|
foreach ($content_records as $record) {
|
// Check if record already exists
|
$exists = $wpdb->get_var(
|
$wpdb->prepare(
|
"SELECT id FROM {$user_seen_table}
|
WHERE user_id = %d AND content_notification_id = %d",
|
$user_id,
|
$record->id
|
)
|
);
|
|
if (!$exists) {
|
// Create new seen record
|
$wpdb->insert(
|
$user_seen_table,
|
[
|
'user_id' => $user_id,
|
'content_notification_id' => $record->id,
|
'status' => 'unread',
|
'created_at' => current_time('mysql')
|
]
|
);
|
$count ++;
|
}
|
}
|
|
// Update last check time
|
update_user_meta($user_id, BASE . 'last_content_check', current_time('mysql'));
|
|
// Clear cache
|
$this->clearNotificationCache($user_id);
|
|
return $count;
|
} catch (Exception $e) {
|
$this->logError("Error creating content seen records: " . $e->getMessage(), [
|
'user_id' => $user_id
|
]);
|
|
return 0;
|
}
|
}
|
|
|
/************************************************************
|
* Notification Digest Methods
|
************************************************************/
|
|
/**
|
* Process daily notification digests
|
*/
|
public function runDailyDigests():void
|
{
|
$this->campaign = 'daily_digest_' . date('Y-m-d');
|
$this->processDigest('daily');
|
}
|
|
/**
|
* Process weekly notification digests
|
*/
|
public function runWeeklyDigests():void
|
{
|
$this->campaign = 'weekly_digest_' . date('Y-m-d');
|
$this->processDigest('weekly');
|
}
|
|
/**
|
* Process monthly notification digests
|
*/
|
public function runMonthlyDigests():void
|
{
|
$this->campaign = 'monthly_digest_' . date('Y-m-d');
|
$this->processDigest('monthly');
|
}
|
|
/**
|
* Process digests for a specific frequency
|
*
|
* @param string $frequency Digest frequency (daily, weekly, monthly)
|
*
|
* @return bool Success or failure
|
*/
|
protected function processDigest(string $frequency):bool
|
{
|
// Get users with this digest frequency preference
|
$users = $this->getUsersForDigest($frequency);
|
|
if (empty($users)) {
|
return true; // No users to process
|
}
|
|
// Queue digest generation
|
$queue = JVB()->queue();
|
$queue->queueOperation(
|
'email_notification_digest',
|
0, // System user
|
[
|
'frequency' => $frequency,
|
'users' => $users
|
],
|
[
|
'count' => count($users),
|
'chunk_key' => 'users',
|
'chunk_size' => 20,
|
'priority' => 'normal',
|
'operation_id' => 'notification_digest_' . date('Y_m_d')
|
]
|
);
|
|
return true;
|
}
|
|
/**
|
* Get users who have subscribed to a specific digest frequency
|
*
|
* @param string $frequency Digest frequency
|
*
|
* @return array Array of user IDs
|
*/
|
protected function getUsersForDigest(string $frequency):array
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . $this->preferencesTable;
|
|
// Get users with this frequency setting
|
$users = $wpdb->get_col(
|
$wpdb->prepare(
|
"SELECT DISTINCT user_id FROM $table
|
WHERE frequency = %s",
|
$frequency
|
)
|
);
|
|
return $users ?: [];
|
}
|
|
/**
|
* Generate and send a digest email for a user
|
*
|
* @param int $user_id User ID
|
* @param string $frequency Digest frequency
|
*
|
* @return bool Success status
|
*/
|
public function generateUserDigest(int $user_id, string $frequency):bool
|
{
|
try {
|
$user = get_userdata($user_id);
|
if (!$user || !is_email($user->user_email)) {
|
return false;
|
}
|
|
// Get date range based on frequency
|
$today = date('Y-m-d');
|
$since_date = $this->getSinceDate($frequency, $today);
|
|
// Get regular notifications
|
$notifications = $this->getDigestNotifications($user_id);
|
|
// Get content updates from followed artists
|
$content_updates = $this->getContentUpdatesForDigest($user_id, $since_date);
|
|
if (empty($notifications) && empty($content_updates)) {
|
return true; // Nothing to send
|
}
|
|
// Generate and send email
|
$sent = $this->sendDigestEmail($user, $frequency, $notifications, $content_updates);
|
|
if ($sent) {
|
// Update preferences last_sent timestamp
|
$this->updateDigestTimestamps($user_id, $frequency);
|
|
return true;
|
}
|
|
return false;
|
} catch (Exception $e) {
|
$this->logError("Error generating digest for user $user_id: " . $e->getMessage());
|
|
return false;
|
}
|
}
|
|
/**
|
* Get notifications for a digest
|
*
|
* @param int $user_id User ID
|
*
|
* @return array Notification objects
|
*/
|
protected function getDigestNotifications(int $user_id):array
|
{
|
global $wpdb;
|
|
// Get unread, non-emailed notifications
|
return $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT * FROM {$wpdb->prefix}{$this->table}
|
WHERE user_id = %d
|
AND status = 'unread'
|
AND emailed_at IS NULL
|
ORDER BY priority DESC, created_at DESC",
|
$user_id
|
)
|
);
|
}
|
|
/**
|
* Get content updates for digest
|
*
|
* @param int $user_id User ID
|
* @param string $since_date Date string (YYYY-MM-DD)
|
*
|
* @return array Content update records
|
*/
|
protected function getContentUpdatesForDigest(int $user_id, string $since_date):array
|
{
|
// Get followed artists
|
$followed_artists = $this->getFollowedArtists($user_id);
|
if (empty($followed_artists)) {
|
return [];
|
}
|
|
global $wpdb;
|
|
// Create placeholders for SQL IN clause
|
$placeholders = implode(',', array_fill(0, count($followed_artists), '%d'));
|
|
// Get content records since the date
|
return $wpdb->get_results(
|
$wpdb->prepare(
|
"SELECT * FROM {$wpdb->prefix}{$this->contentTable}
|
WHERE user_id IN ($placeholders)
|
AND date >= %s
|
AND total_items > 0
|
ORDER BY date DESC",
|
array_merge($followed_artists, [ $since_date ])
|
)
|
);
|
}
|
|
/**
|
* Get since date for a frequency
|
*
|
* @param string $frequency Digest frequency
|
* @param string $today Today's date
|
*
|
* @return string Date string (YYYY-MM-DD)
|
*/
|
protected function getSinceDate(string $frequency, string $today):string
|
{
|
switch ($frequency) {
|
case 'daily':
|
return date('Y-m-d', strtotime('-1 day', strtotime($today)));
|
case 'weekly':
|
return date('Y-m-d', strtotime('-1 week', strtotime($today)));
|
case 'monthly':
|
return date('Y-m-d', strtotime('-1 month', strtotime($today)));
|
default:
|
return '';
|
}
|
}
|
|
/**
|
* Send a digest email
|
*
|
* @param WP_User $user User object
|
* @param string $frequency Digest frequency
|
* @param array $notifications Regular notifications
|
* @param array $content_updates Content update records
|
*
|
* @return bool Whether the email was sent
|
*/
|
protected function sendDigestEmail(WP_User $user, string $frequency, array $notifications, array $content_updates):bool
|
{
|
// Generate subject based on frequency
|
$subjects = [
|
'daily' => [
|
"[edmonton.ink] Your " . date('l') . " Ink Update ♡",
|
"[edmonton.ink] Fresh Ink Alert - Your " . date('l') . " Digest",
|
"[edmonton.ink] What You Missed in Edmonton's Tattoo Scene Today"
|
],
|
'weekly' => [
|
"[edmonton.ink] Your Weekly Roundup from edmonton.ink ♡",
|
"[edmonton.ink] This Week in Edmonton's Tattoo Scene",
|
"[edmonton.ink] Weekly Ink Update - Fresh From the Scene"
|
],
|
'monthly' => [
|
"[edmonton.ink] Monthly Ink Roundup ♡",
|
"[edmonton.ink] Your Monthly Scene Report: " . date('F') . " Edition",
|
"[edmonton.ink] " . date('F') . " in Edmonton Tattoos"
|
]
|
];
|
|
// Randomly select a subject for variety
|
$subject_options = $subjects[ $frequency ] ?? [ "Your edmonton.ink Update" ];
|
$subject = $subject_options[ array_rand($subject_options) ];
|
|
// Generate email content
|
$content = $this->generateDigestContent($user, $frequency, $notifications, $content_updates);
|
|
// Add tracking pixel
|
$tracking_code = sprintf(
|
'<img src="%s/track-email.php?uid=%s&digest=%s" width="1" height="1" alt="" />',
|
site_url(),
|
base64_encode($user->ID),
|
$frequency
|
);
|
$content .= $tracking_code;
|
|
// Set header based on frequency
|
$header = match ($frequency) {
|
'daily' => "TODAY'S INK DROP",
|
'weekly' => "THIS WEEK'S SCENE REPORT",
|
'monthly' => "MONTHLY INK ROUNDUP",
|
default => "YOUR INK UPDATES",
|
};
|
|
// Send the email
|
return jvbMail($user->user_email, $subject, $content, $header);
|
}
|
|
/**
|
* Generate HTML content for digest email
|
*
|
* @param WP_User $user User object
|
* @param string $frequency Digest frequency
|
* @param array $notifications Regular notifications
|
* @param array $content_updates Content update records
|
*
|
* @return string HTML email content
|
*/
|
protected function generateDigestContent(WP_User $user, string $frequency, array $notifications, array $content_updates):string
|
{
|
$content = sprintf('<p>Hey %s,</p>', $user->first_name ?: $user->display_name);
|
|
// Intro text based on frequency
|
switch ($frequency) {
|
case 'daily':
|
$content .= '<p>Here\'s what happened in Edmonton\'s tattoo scene today:</p>';
|
break;
|
case 'weekly':
|
$content .= '<p>Here\'s what you missed in Edmonton\'s tattoo scene this week:</p>';
|
break;
|
case 'monthly':
|
$content .= sprintf('<p>Here\'s your monthly roundup of what happened in %s in Edmonton\'s tattoo scene:</p>', date('F'));
|
break;
|
}
|
|
// Process artist content updates - the most visually interesting part
|
$content .= $this->generateContentUpdatesSection($content_updates);
|
|
// Process regular notifications
|
if (!empty($notifications)) {
|
$content .= $this->generateNotificationsSection($notifications);
|
}
|
|
// Add footer content
|
$content .= '<div class="divider"></div>';
|
$content .= sprintf(
|
'<p>You\'re receiving this %s digest because you follow artists on edmonton.ink. ' .
|
'You can <a href="%s" class="text-link">adjust your notification settings</a> at any time.</p>',
|
$frequency,
|
esc_url(add_query_arg([
|
'utm_source' => 'email',
|
'utm_medium' => 'digest',
|
'utm_campaign' => $this->campaign
|
], site_url('/dash/settings/')))
|
);
|
|
return $content;
|
}
|
|
/**
|
* Generate HTML section for content updates
|
*
|
* @param array $content_updates Content update records
|
*
|
* @return string HTML content
|
* TODO: This needs some work
|
*/
|
protected function generateContentUpdatesSection(array $content_updates):string
|
{
|
if (empty($content_updates)) {
|
return '';
|
}
|
|
$content = '';
|
$cache = CacheManager::for('digest_content', HOUR_IN_SECONDS * 6); // Cache for 6 hours
|
|
// Group updates by artist
|
$updates_by_artist = [];
|
foreach ($content_updates as $update) {
|
if (!isset($updates_by_artist[$update->user_id])) {
|
$updates_by_artist[ $update->user_id ] = [];
|
}
|
$updates_by_artist[ $update->user_id ][] = $update;
|
}
|
|
// Process each artist's updates
|
foreach ($updates_by_artist as $artist_id => $updates) {
|
$artist_data = $this->getArtistData($artist_id);
|
if (!$artist_data) {
|
continue;
|
}
|
|
// Combine all content updates from this artist
|
$combined_new_items = [];
|
|
foreach ($updates as $update) {
|
$new_items = json_decode($update->new_items, true) ?: [];
|
|
foreach ($new_items as $type => $ids) {
|
if (!isset($combined_new_items[ $type ])) {
|
$combined_new_items[ $type ] = [];
|
}
|
$combined_new_items[ $type ] = array_merge($combined_new_items[ $type ], $ids);
|
// Remove duplicates
|
$combined_new_items[ $type ] = array_unique($combined_new_items[ $type ]);
|
}
|
}
|
|
// Skip if no content to show
|
if (empty($combined_new_items)) {
|
continue;
|
}
|
|
// Add artist header
|
$content .= sprintf(
|
'<h3><a href="%s" class="text-link">%s</a></h3>',
|
esc_url(add_query_arg([
|
'utm_source' => 'email',
|
'utm_medium' => 'digest',
|
'utm_campaign' => $this->campaign
|
], $artist_data['url'])),
|
esc_html($artist_data['display_name'])
|
);
|
|
// Process each content type
|
foreach ($combined_new_items as $type => $ids) {
|
if (empty($ids)) {
|
continue;
|
}
|
|
$clean_type = str_replace(BASE, '', $type);
|
$type_label = $this->getContentTypeLabel($clean_type);
|
|
// Add subheading for content type
|
$content .= sprintf('<h4>%s</h4>', esc_html($type_label));
|
|
// Display up to 3 items in a row
|
$display_ids = array_slice($ids, 0, 3);
|
$content .= '<table><tr>';
|
|
foreach ($display_ids as $item_id) {
|
// Get cached item data or fetch it
|
$cache_key = "digest_{$type}_{$item_id}";
|
$item_data = $cache->get($cache_key);
|
|
if ($item_data === false) {
|
$item_data = $this->getContentDetails($type, $item_id, $artist_id);
|
if ($item_data) {
|
$cache->set($cache_key, $item_data);
|
}
|
}
|
|
if (!$item_data) {
|
continue;
|
}
|
|
// Add item to email
|
$content .= '<td style="padding: 10px; width: 33.3%; vertical-align: top; text-align: center;">';
|
$content .= '<div style="margin-bottom: 10px;">';
|
$content .= sprintf(
|
'<a href="%s"><img src="%s" alt="%s" style="width: 100%%; max-width: 150px; height: auto; border-radius: 4px;"></a>',
|
esc_url(add_query_arg([
|
'utm_source' => 'email',
|
'utm_medium' => 'digest',
|
'utm_campaign' => $this->campaign
|
], $item_data['url'])),
|
esc_url($item_data['image']),
|
esc_attr($item_data['title'])
|
);
|
$content .= '</div>';
|
$content .= sprintf('<p style="margin: 5px 0; font-weight: bold;">%s</p>', esc_html($item_data['title']));
|
$content .= '</td>';
|
}
|
|
// Fill empty cells if needed
|
for ($i = count($display_ids); $i < 3; $i++) {
|
$content .= '<td style="width: 33.3%;"></td>';
|
}
|
|
$content .= '</tr></table>';
|
|
// Add "See all" link if there are more items
|
if (count($ids) > 3) {
|
$content .= sprintf(
|
'<p style="text-align: right;"><a href="%s" class="text-link">See all %d %s →</a></p>',
|
esc_url(add_query_arg([
|
'utm_source' => 'email',
|
'utm_medium' => 'digest',
|
'utm_campaign' => $this->campaign
|
], site_url('/dash/feed/?type=' . $clean_type . '&artist=' . $artist_id))),
|
count($ids),
|
$this->pluralize($clean_type)
|
);
|
}
|
}
|
|
$content .= '<div class="divider"></div>';
|
}
|
|
return $content;
|
}
|
|
/**
|
* Generate HTML section for regular notifications
|
*
|
* @param array $notifications Notification objects
|
*
|
* @return string HTML content
|
*/
|
protected function generateNotificationsSection(array $notifications):string
|
{
|
if (empty($notifications)) {
|
return '';
|
}
|
|
$content = '<h3>Other Updates</h3>';
|
$content .= '<ul style="padding-left: 20px;">';
|
|
// Group notifications by type
|
$by_type = [];
|
foreach ($notifications as $notification) {
|
if (!isset($by_type[$notification->type])) {
|
$by_type[ $notification->type ] = [];
|
}
|
$by_type[ $notification->type ][] = $notification;
|
}
|
|
// Process each type
|
foreach ($by_type as $type => $type_notifications) {
|
$config = $this->notification_types[ $type ] ?? [];
|
$icon = $config['icon'] ?? 'info';
|
|
foreach ($type_notifications as $notification) {
|
$message = $notification->message;
|
if (empty($message)) {
|
$message = $this->generateNotificationMessage($notification);
|
}
|
|
if (!empty($message)) {
|
$content .= sprintf('<li>%s</li>', $message);
|
}
|
}
|
}
|
|
$content .= '</ul>';
|
$content .= '<div class="divider"></div>';
|
|
return $content;
|
}
|
|
/**
|
* Generate a message for a notification when none is provided
|
*
|
* @param object $notification Notification object
|
*
|
* @return string Formatted message
|
*/
|
protected function generateNotificationMessage(object $notification):string
|
{
|
switch ($notification->type) {
|
case 'new_favourite':
|
return 'Someone favourited your content';
|
|
case 'artist_approved':
|
return 'Your artist profile has been approved';
|
|
case 'artist_invitation':
|
return 'You have a new invitation to join a shop';
|
|
case 'new_term':
|
return 'New term suggestion requires your approval';
|
|
case 'term_approved':
|
return 'Your term suggestion was approved';
|
|
case 'term_rejected':
|
return 'Your term suggestion was not approved';
|
|
case 'list_shared':
|
return 'Someone shared a list with you';
|
|
default:
|
return 'You have a new notification';
|
}
|
}
|
|
/**
|
* Get content details for digest
|
*
|
* @param string $type Content type (with jvb_ prefix)
|
* @param int $content_id Content ID
|
* @param int $artist_id Artist ID
|
*
|
* @return array|false Content details or false if not found
|
*/
|
protected function getContentDetails(string $type, int $content_id, int $artist_id):array|false
|
{
|
// Build post type from content type
|
$post_type = $type; // Already has jvb_ prefix
|
|
// Get the post
|
$post = get_post($content_id);
|
if (!$post || $post->post_type !== $post_type || $post->post_status !== 'publish') {
|
return false;
|
}
|
|
// Get artist data
|
$artist_data = $this->getArtistData($artist_id);
|
if (!$artist_data) {
|
return false;
|
}
|
|
// Get featured image if available
|
$image_url = get_the_post_thumbnail_url($content_id, 'medium');
|
if (!$image_url) {
|
// Try meta fields for image
|
$meta_image_id = get_post_meta($content_id, BASE . 'image', true);
|
if ($meta_image_id !== '') {
|
$image_url = wp_get_attachment_image_url($meta_image_id, 'medium');
|
}
|
}
|
|
if (!$image_url) {
|
return false; // Skip items without images for digest
|
}
|
|
return [
|
'id' => $content_id,
|
'title' => $post->post_title,
|
'url' => get_permalink($content_id),
|
'image' => $image_url,
|
'date' => $post->post_date,
|
'artist_id' => $artist_id,
|
'artist_name' => $artist_data['display_name'],
|
'artist_url' => $artist_data['url'],
|
'type' => str_replace(BASE, '', $type)
|
];
|
}
|
|
/**
|
* Get human-readable label for content type
|
*
|
* @param string $type Content type (without jvb_ prefix)
|
*
|
* @return string Label
|
*/
|
protected function getContentTypeLabel(string $type):string
|
{
|
$labels = [
|
'tattoo' => 'New Tattoos',
|
'artwork' => 'New Artwork',
|
'piercing' => 'New Piercings',
|
'event' => 'Upcoming Events',
|
'news' => 'News & Updates',
|
'offer' => 'Special Offers'
|
];
|
|
return $labels[$type] ?? ucfirst($type . 's');
|
}
|
|
|
/**
|
* Update digest timestamps for notification preferences
|
*
|
* @param int $user_id User ID
|
* @param string $frequency Digest frequency
|
*
|
* @return bool Success or failure
|
*/
|
protected function updateDigestTimestamps(int $user_id, string $frequency):bool
|
{
|
global $wpdb;
|
$table = $wpdb->prefix . $this->preferencesTable;
|
|
$result = $wpdb->update(
|
$table,
|
[
|
'last_sent' => current_time('mysql')
|
],
|
[
|
'user_id' => $user_id,
|
'frequency' => $frequency
|
]
|
);
|
|
return $result !== false;
|
}
|
|
/************************************************************
|
* Bulk Operation Handlers
|
************************************************************/
|
|
/**
|
* Handle bulk operations for notifications
|
*
|
* @param WP_Error|array $result Default result
|
* @param object $operation Operation object
|
* @param array $data Current item
|
*
|
* @return WP_Error|array Operation result
|
*/
|
public function processOperation(WP_Error|array $result, object $operation, array $data):WP_Error|array
|
{
|
switch ($operation->type) {
|
case 'email_notification_digest':
|
return $this->handleDigestBatch($operation, $data);
|
|
case 'addNotification':
|
return $this->handleAddNotificationOperation($operation, $data);
|
default:
|
return $result;
|
}
|
}
|
|
/**
|
* Handle digest batch operation
|
*
|
* @param object $operation Operation object
|
* @param array $data
|
*
|
* @return WP_Error|array|bool Operation result
|
*/
|
protected function handleDigestBatch(object $operation, array $data):WP_Error|array|bool
|
{
|
try {
|
|
// Process one user at a time
|
$user_id = $data['users'][ $operation->progress_count ] ?? null;
|
|
if (!$user_id) {
|
return [
|
'success' => false,
|
'message' => 'Invalid user ID'
|
];
|
}
|
|
$result = $this->generateUserDigest($user_id, $data['frequency']);
|
|
return [
|
'success' => $result,
|
'result' => $result ? 'Digest sent successfully' : 'Failed to send digest',
|
];
|
} catch (Exception $e) {
|
$this->logError("Error processing digest batch: " . $e->getMessage(), [
|
'operation_id' => $operation->id
|
]);
|
|
return false;
|
}
|
}
|
|
/**
|
* @param object $operation
|
* @param array $data
|
*
|
* @return WP_Error|array|bool
|
*/
|
protected function handleAddNotificationOperation(object $operation, array $data):WP_Error|array|bool
|
{
|
try {
|
$user_ids = $data['user_ids'] ?? [];
|
$type = $data['type'] ?? '';
|
$message = $data['message'] ?? '';
|
$target_id = $data['target_id'] ?? null;
|
$target_type = $data['target_type'] ?? null;
|
|
if (empty($user_ids) || empty($type)) {
|
return new WP_Error('invalid_data', 'Missing required notification data');
|
}
|
|
foreach ($user_ids as $key => $user_id) {
|
if (!$this->checkUser($user_id)) {
|
unset($user_ids[$key]);
|
}
|
}
|
|
// Add notification for this user
|
$result = $this->processNotification($user_ids, $type, $message, $target_id, $target_type);
|
|
return [
|
'success' => !is_wp_error($result),
|
'result' => is_wp_error($result) ? $result->get_error_message() : $result,
|
];
|
} catch (Exception $e) {
|
return [
|
'success' => false,
|
'result' => $e->getMessage()
|
];
|
}
|
}
|
|
|
/**
|
* @param int $user_id
|
*
|
* @return array|false|mixed
|
*/
|
protected function getArtistData(int $user_id):array|false
|
{
|
// Try to get from cache
|
$cache_key = "artist_data_{$user_id}";
|
$cached = $this->cache->get($cache_key);
|
|
if ($cached !== false) {
|
return $cached;
|
}
|
|
// Get artist post ID from user meta
|
$artist_id = get_user_meta($user_id, BASE . 'link', true);
|
if (!$artist_id) {
|
return false;
|
}
|
|
// Get basic artist data
|
$artist_post = get_post($artist_id);
|
if (!$artist_post) {
|
return false;
|
}
|
|
$data = [
|
'id' => $artist_id,
|
'user_id' => $user_id,
|
'display_name' => get_the_title($artist_id),
|
'first_name' => get_post_meta($artist_id, BASE . 'first_name', true),
|
'url' => get_permalink($artist_id),
|
'image' => get_post_thumbnail_id($artist_id)
|
];
|
|
// Cache the result
|
$this->cache->set($cache_key, $data, null,'artists');
|
|
return $data;
|
}
|
|
/**
|
* @param int $user_id
|
*
|
* @return array
|
*/
|
protected function getFollowedArtists(int $user_id):array
|
{
|
global $wpdb;
|
$favourites_table = $wpdb->prefix . BASE . 'favourites';
|
|
// Get artists this user has favourited
|
return $wpdb->get_col($wpdb->prepare(
|
"SELECT f.target_id
|
FROM {$favourites_table} f
|
JOIN {$wpdb->posts} p ON f.target_id = p.ID
|
WHERE f.user_id = %d
|
AND f.type = 'artist'
|
AND p.post_status = 'publish'",
|
$user_id
|
));
|
}
|
|
/**
|
* @param int $artist_id
|
*
|
* @return int
|
*/
|
protected function getFollowerCount(int $artist_id):int
|
{
|
global $wpdb;
|
$favourites_table = $wpdb->prefix . BASE . 'favourites';
|
|
return $wpdb->get_var($wpdb->prepare(
|
"SELECT COUNT(DISTINCT user_id)
|
FROM {$favourites_table}
|
WHERE target_id = %d AND type = 'artist'",
|
$artist_id
|
));
|
}
|
|
/**
|
* @param string $word
|
*
|
* @return string
|
*/
|
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 (str_ends_with($word, 'y')) {
|
return substr($word, 0, -1) . 'ies';
|
}
|
|
return $word . 's';
|
}
|
|
/**
|
* @param int $user_id
|
*
|
* @return void
|
*/
|
protected function clearNotificationCache(int $user_id):void
|
{
|
|
$this->cache->delete("user_{$user_id}_notifications_", 'notifications_' . $user_id);
|
$this->cache->delete("user_{$user_id}_content_notifications_", 'notifications_' . $user_id);
|
}
|
|
/**
|
* Log errors with proper context using the error handler
|
*
|
* @param string $message Error message
|
* @param array $context Additional context data
|
* @param string $severity Error severity (debug, info, warning, error, critical)
|
* @return void
|
*/
|
protected function logError(string $message, array $context = [], string $severity = 'error'):void
|
{
|
try {
|
// Use the ErrorHandler through the JVB singleton
|
JVB()->error()->log(
|
'notifications', // component
|
$message,
|
$context,
|
$severity
|
);
|
} catch (Exception $e) {
|
// Fallback if error handler fails
|
error_log("NotificationManager Error: $message - " . json_encode($context));
|
}
|
}
|
|
/**
|
* @return void
|
*/
|
public function cleanupOldNotifications():void
|
{
|
global $wpdb;
|
$notifications_table = $wpdb->prefix . $this->table;
|
$seen_table = $wpdb->prefix . $this->seenTable;
|
|
// Keep track of current time
|
$current_time = current_time('mysql');
|
|
// Delete regular notifications older than 3 months
|
$wpdb->query($wpdb->prepare(
|
"DELETE FROM {$notifications_table}
|
WHERE created_at < DATE_SUB(%s, INTERVAL 3 MONTH)
|
AND status IN ('read', 'actioned', 'dismissed')",
|
$current_time
|
));
|
|
// Delete dismissed content notifications older than 1 month
|
$wpdb->query($wpdb->prepare(
|
"DELETE FROM {$seen_table}
|
WHERE status = 'dismissed'
|
AND created_at < DATE_SUB(%s, INTERVAL 1 MONTH)",
|
$current_time
|
));
|
|
// Delete read content notifications older than 3 months
|
$wpdb->query($wpdb->prepare(
|
"DELETE FROM {$seen_table}
|
WHERE status = 'read'
|
AND created_at < DATE_SUB(%s, INTERVAL 3 MONTH)",
|
$current_time
|
));
|
}
|
|
/**
|
* @return array
|
*/
|
protected function getVerifiedArtists():array
|
{
|
$artists = $this->cache->get('verified_artists');
|
if ($artists) {
|
return $artists;
|
}
|
|
$artists = get_users([
|
'role' => BASE.'artist',
|
'capability' => 'skip_moderation',
|
'fields' => 'ID'
|
]);
|
|
$this->cache->set('verified_artists', $artists);
|
return $artists;
|
}
|
|
/**
|
* @return array
|
*/
|
protected function getVerifiedPartners():array
|
{
|
$partners = $this->cache->get('verified_partners');
|
if ($partners) {
|
return $partners;
|
}
|
|
$partners = get_users([
|
'role' => BASE.'partner',
|
'capability' => 'skip_moderation',
|
'fields' => 'ID'
|
]);
|
|
$this->cache->set('verified_partners', $partners);
|
return $partners;
|
}
|
|
/**
|
* @return array
|
*/
|
protected function getEnthusiasts():array
|
{
|
$enthusiasts = $this->cache->get('enthusiasts');
|
if ($enthusiasts) {
|
return $enthusiasts;
|
}
|
|
$enthusiasts = get_users([
|
'role' => BASE.'enthusiast',
|
'fields' => 'ID'
|
]);
|
|
$this->cache->set('enthusiasts', $enthusiasts);
|
return $enthusiasts;
|
}
|
|
/**
|
* @return array
|
*/
|
protected function getEveryone():array
|
{
|
$users = $this->cache->get('users');
|
if ($users) {
|
return $users;
|
}
|
$users = get_users([
|
'role__in' => [BASE.'artist', BASE.'enthusiast', BASE.'partner'],
|
'fields' => 'ID'
|
]);
|
$this->cache->set('users', $users);
|
return $users;
|
}
|
|
/**
|
* @param int $userID
|
*
|
* @return bool|mixed
|
*/
|
protected function checkUser(int $userID):bool
|
{
|
$checked = $this->cache->get($userID, 'checked_users');
|
if ($checked) {
|
return $checked;
|
}
|
$test = (bool)get_userdata($userID);
|
|
$this->cache->set($userID, $test, null, 'checked_users');
|
return $test;
|
}
|
}
|