From ba1e1ccf869b818f7a7a897264dfea05563a7796 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 07 Jun 2026 20:10:20 +0000
Subject: [PATCH] =Major overhaul of Integrations. Playing around with adding fields to post types through Registrar from an integrations' class file.

---
 inc/managers/NotificationManager.php | 1840 ++--------------------------------------------------------
 1 files changed, 74 insertions(+), 1,766 deletions(-)

diff --git a/inc/managers/NotificationManager.php b/inc/managers/NotificationManager.php
index abac97a..99a771f 100644
--- a/inc/managers/NotificationManager.php
+++ b/inc/managers/NotificationManager.php
@@ -1,16 +1,14 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\JVB;
-use WP_Error;
-use Exception;
-use WP_Post;
-use WP_User;
+use JVBase\managers\Notifications\Content;
+use JVBase\managers\Notifications\EmailDigests;
+use JVBase\managers\Notifications\Notifications;
+use JVBase\managers\Notifications\Preferences;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
 }
-//TODO: Ensure this works with the constants setup
 /**
  * NotificationManager - Centralized notification system for edmonton.ink
  *
@@ -20,1772 +18,82 @@
  */
 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
-        ]
-    ];
+   protected Notifications $notifications;
+   protected Content $content;
+   protected EmailDigests $digest;
+   protected Preferences $preferences;
 
-    /**
-     * Constructor
-     */
-    public function __construct()
-    {
-        $this->cache = CacheManager::for('notifications', WEEK_IN_SECONDS);
+   public function __construct()
+   {
+	   $this->notifications = new Notifications();
+	   $this->content = new Content();
+	   $this->digest = new EmailDigests();
+	   $this->preferences = new Preferences();
+   }
 
-        // Add filter for bulk operation handling
-        add_filter(BASE . 'handle_bulk_operation', [ $this, 'processOperation' ], 10, 3);
+   /************************************************
+	* PREFERENCES
+   ************************************************/
+	public function getUsersByFrequency(string $frequency):array
+	{
+		return $this->preferences->getUsersByFrequency($frequency);
+	}
 
-        add_action(BASE . 'cleanup_notifications', [$this, 'cleanupOldNotifications']);
+	public function getUserSubscriptions(int $userID, string $frequency):array
+	{
+		return $this->preferences->getUserSubscriptions($userID, $frequency);
+	}
 
-        // Track content creation for notifications
-        add_action('save_post', [ $this, 'trackContentCreation' ], 10, 3);
-        add_action('saved_term', [ $this, 'track_shop_creation' ], 10, 3);
+	public function addUserPreference(int $userID, int $item_id, string $item_type, string $frequency):bool
+	{
+		return $this->preferences->addUserPreference($userID, $item_id, $item_type, $frequency);
+	}
 
-        // Generate content summaries when users log in
-        add_action('wp_login', [ $this, 'generateUserContentNotifications' ], 10, 2);
+	public function deleteUserPreference(int $userID, int $item_id, string $item_type):bool
+	{
+		return $this->preferences->deleteUserPreference($userID, $item_id, $item_type);
+	}
+   /************************************************
+	* NOTIFICATIONS
+   ************************************************/
+	public function notify(int|array $user_ids, string $notification_type, int $fromUser = 0, array $args = []):bool
+	{
+		return $this->notifications->notify($user_ids, $notification_type, $fromUser, $args);
+	}
+	public function unnotify(int|array $user_ids, string $notification_type, int $fromUser = 0, array $args = []):bool
+	{
+		return $this->notifications->unnotify($user_ids, $notification_type, $fromUser, $args);
+	}
+   public function getUserNotifications(int $user_id, array $args = []):array
+   {
+	   return $this->notifications->getUserNotifications($user_id, $args);
+   }
 
-        // Register digest cron jobs
-        $this->registerCron();
-    }
+   public function markRead(int $userID, int|array $notification_id):bool
+   {
+	   return $this->notifications->markRead($userID, $notification_id);
+   }
+   public function markDismissed(int $userID, int|array $notification_id):bool
+   {
+	   return $this->notifications->markDismissed($userID, $notification_id);
+   }
+   public function markActioned(int $userID, int|array $notification_id, array $result = []):bool
+   {
+	   return $this->notifications->markActioned($userID, $notification_id, $result);
+   }
 
-    /**
-     * 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' ]);
-    }
+   public function getNotificationTypes(bool $all = false):array
+   {
+		return $this->notifications->getNotificationTypes($all);
+   }
 
-    /************************************************************
-     * 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;
-    }
+	/************************************************
+	 * CONTENT NOTIFICATIONS
+	 * These are pooled notifications of new content for:
+	 * 		- a particular artist
+	 * 		- a particular term
+	 ************************************************/
+   /************************************************
+	* EMAIL DIGESTS
+	************************************************/
 }

--
Gitblit v1.10.0