From 3baf3d2545ba6ece6b74a64c0def59bd0774cf54 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 10 Jun 2026 16:34:12 +0000
Subject: [PATCH] =Laid the groundwork for an improved DashboardManager.php setup. Have to put it aside so I can get the dang Northeh done though.

---
 inc/rest/routes/FavouritesRoutes.php |  588 ++++++++--------------------------------------------------
 1 files changed, 87 insertions(+), 501 deletions(-)

diff --git a/inc/rest/routes/FavouritesRoutes.php b/inc/rest/routes/FavouritesRoutes.php
index 9690a53..953e4db 100644
--- a/inc/rest/routes/FavouritesRoutes.php
+++ b/inc/rest/routes/FavouritesRoutes.php
@@ -3,6 +3,7 @@
 
 use JVBase\managers\Cache;
 use JVBase\managers\CustomTable;
+use JVBase\registrar\Registrar;
 use JVBase\rest\PermissionHandler;
 use JVBase\rest\Response;
 use JVBase\rest\Rest;
@@ -15,9 +16,6 @@
 	exit;
 }
 
-/**
- * TODO: Extract business logic into a Favourites.php manager class
- */
 class FavouritesRoutes extends Rest
 {
 	protected array $valid_types;
@@ -36,25 +34,18 @@
 		parent::__construct();
 
 		// Set up cache connections
-		$this->cache->connect('post')->connect('user')->connect('taxonomy');
-		$this->listsCache = Cache::for('lists')->connect('favourites', true);
-		$this->sharedListsCache = Cache::for('sharedLists')->connect('favourites', true);
-		$this->favouritesCache = Cache::for('allFavourites')->connect('favourites', true);
+		$this->cache->connect('post')->connect('user')->connect('taxonomy')->user();
+		$this->listsCache = Cache::for('lists')->connect('favourites', true)->user();
+		$this->sharedListsCache = Cache::for('sharedLists')->connect('favourites', true)->user();
+		$this->favouritesCache = Cache::for('allFavourites')->connect('favourites', true)->user();
 
-		$this->valid_types = array_keys(array_merge(JVB_CONTENT, JVB_TAXONOMY));
+		$this->valid_types = array_merge(Registrar::getRegistered('post'), Registrar::getRegistered('term'));
 
 		// Initialize CustomTable instances
 		$this->favourites = CustomTable::for('favourites');
 		$this->lists = CustomTable::for('favourites_lists');
 		$this->listItems = CustomTable::for('favourites_list_items');
 		$this->listShares = CustomTable::for('favourites_list_shares');
-
-		// Register hooks
-		add_filter(BASE.'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
-		add_action('before_delete_post', [$this, 'cleanupPostFavourites']);
-		add_action('delete_term', [$this, 'cleanupTermFavourites'], 10, 3);
-		add_action('jvbUserRegistered', [$this, 'maybeAcceptListInvite'], 10, 3);
-		add_action('jvb_cleanupOrphanedFavourites', [$this, 'cleanupOrphanedFavourites']);
 	}
 
 	public function registerRoutes(): void
@@ -72,7 +63,6 @@
 			->post([$this, 'handleFavourite'])
 			->args([
 				'user' => 'integer|required',
-				'id' => 'string|required',
 				'action' => 'string|required|enum:add,remove,toggle,batch,note',
 				'type' => 'string',
 				'target_id' => 'integer',
@@ -129,149 +119,50 @@
 		if ($cache_check) {
 			return $cache_check;
 		}
-
-		if (count($args) === 1 || ($request->get_param('include_all') === true)) {
-			$result = $this->getAllFavourites($user_id);
-		} else {
-			$result = $this->cache->remember($key, function() use ($args) {
-				return $this->getFilteredFavourites($args);
-			});
-		}
+		$result = JVB()->favourites()->getFavourites($args);
+		$result['items'] = $this->formatItems($result['items']);
 
 		return $this->addCacheHeaders(Response::success($result));
 	}
 
-	/**
-	 * Get filtered favourites using CustomTable fluent interface
-	 */
-	protected function getFilteredFavourites(array $args): array
-	{
-		try {
-			// Build base query
-			$query = $this->favourites->where(['user_id' => $args['user']]);
-
-			// Add type filter if specified
-			if (!empty($args['content']) && $args['content'] !== 'all') {
-				$query = $this->favourites->where([
-					'user_id' => $args['user'],
-					'type' => BASE . $args['content']
-				]);
-			}
-
-			// Apply ordering and pagination
-			$orderby = in_array($args['orderby'] ?? 'date_added', ['date_added', 'type'])
-				? $args['orderby']
-				: 'date_added';
-			$order = in_array(strtoupper($args['order'] ?? 'DESC'), ['ASC', 'DESC'])
-				? strtoupper($args['order'])
-				: 'DESC';
-
-			$favourites = $query
-				->orderBy($orderby, $order)
-				->limit(100, ($args['page'] - 1) * 100)
-				->getResults();
-
-			// Get total count
-			$count_query = $this->favourites->where(['user_id' => $args['user']]);
-			if (!empty($args['content']) && $args['content'] !== 'all') {
-				$count_query->where(['type' => BASE . $args['content']]);
-			}
-			$total_items = $count_query->countResults();
-
-			return [
-				'items' => $this->formatItems($favourites),
-				'has_more' => ($args['page'] * 100) < $total_items,
-				'total' => $total_items,
-				'success' => true,
-			];
-
-		} catch (Exception $e) {
-			$this->logError('getFilteredFavourites', [
-				'error' => $e->getMessage(),
-				'args' => $args
-			]);
-
-			return [
-				'success' => false,
-				'items' => [],
-				'total' => 0,
-				'has_more' => false
-			];
-		}
-	}
-
-	/**
-	 * Get all user's favourites organized by content type
-	 */
-	protected function getAllFavourites(int $user_id): array
-	{
-		return $this->cache->remember($user_id, function() use ($user_id) {
-			try {
-				$favourites = $this->favourites
-					->where(['user_id' => $user_id])
-					->getResults();
-
-				$by_type = [];
-				foreach ($favourites as $fav) {
-					$type = str_replace(BASE, '', $fav->type);
-					if (!isset($by_type[$type])) {
-						$by_type[$type] = [];
-					}
-					$by_type[$type][] = (int)$fav->target_id;
-				}
-
-				return [
-					'success' => true,
-					'items' => $by_type,
-					'has_more' => false,
-				];
-
-			} catch (Exception $e) {
-				$this->logError('getAllFavourites', [
-					'error' => $e->getMessage(),
-					'user_id' => $user_id
-				]);
-
-				return [
-					'success' => false,
-					'items' => [],
-				];
-			}
-		});
-	}
 
 	/**
 	 * Handle favourite operations
 	 */
 	public function handleFavourite(WP_REST_Request $request): WP_REST_Response
 	{
-		$user_id = absint($request->get_param('user'));
-		$operation_id = sanitize_text_field($request->get_param('id'));
-		$action = sanitize_text_field($request->get_param('action'));
+		$params = $request->get_params();
+		$user_id = absint($params['user']??0);
 
 		if (!$this->userCheck($user_id)) {
 			return $this->unauthorized();
 		}
+		$action = strtolower(sanitize_text_field($params['action']));
+		$action = in_array($action, ['add', 'remove']) ? $action : false;
+		if (!$action) {
+			return $this->error('Invalid favourite action');
+		}
+		$target_id = absint($params['target_id']??0);
+		if ($target_id === 0) {
+			return $this->error('Invalid target id');
+		}
 
-		$data = [
-			'action' => $action,
-			'type' => sanitize_text_field($request->get_param('type') ?? ''),
-			'target_id' => absint($request->get_param('target_id') ?? 0),
-			'items' => $request->get_param('items') ?? [],
-			'notes' => sanitize_textarea_field($request->get_param('notes') ?? ''),
-		];
+		$type = sanitize_text_field($params['type']??'');
+		if (empty($type)) {
+			return $this->error('No type provided');
+		}
 
-		JVB()->queue()->queueOperation(
-			'favourite_' . $action,
+		$result = JVB()->favourites()->toggleFavourite(
+			$action === 'add',
 			$user_id,
-			$data,
-			[
-				'operation_id' => $operation_id,
-				'priority' => 'high',
-			]
+			$target_id,
+			$type
 		);
 
-		return $this->queued($operation_id);
+		if ($result) {
+			return $this->success();
+		}
+		return $this->error('Something went wrong');
 	}
 
 	/**
@@ -279,18 +170,21 @@
 	 */
 	public function getLists(WP_REST_Request $request): WP_REST_Response
 	{
-		$user_id = absint($request->get_param('user'));
+		$params = $request->get_params();
+		$user_id = absint($params['user']);
 
 		if (!$this->userCheck($user_id)) {
 			return $this->unauthorized();
 		}
 
-		$params = ['user' => $user_id];
-		if ($request->get_param('id')) {
-			$params['list'] = sanitize_text_field($request->get_param('id'));
+		$args = $this->buildParams($request);
+		$args['per_page'] = 20;
+		$listId = $request->get_param('id');
+		if (!empty($listId)) {
+			$args['where']['id'] = sanitize_text_field($listId);
 		}
 
-		$key = $this->listsCache->generateKey($params);
+		$key = $this->listsCache->generateKey($args);
 
 		// Check cache headers
 		$cache_check = $this->checkHeaders($request, $key);
@@ -298,136 +192,30 @@
 			return $cache_check;
 		}
 
-		$list_id = $request->get_param('id');
-		$response = $list_id
-			? $this->getListDetails($list_id, $user_id)
-			: $this->getAvailableLists($user_id);
+		$includeShares = !empty($request->get_param('include_shares'));
+
+		$response = !empty($listId)
+			? JVB()->favourites()->getListDetails($listId, $user_id)
+			: JVB()->favourites()->getAvailableLists($args, $includeShares);
 
 		return $this->addCacheHeaders(Response::success($response));
 	}
 
-	/**
-	 * Get lists available to a user using CustomTable
-	 */
-	protected function getAvailableLists(int $user_id, bool $include_shared = true): array
-	{
-		if (!$this->checkUser($user_id)) {
-			return [];
-		}
-
-		$cache = $include_shared ? $this->sharedListsCache : $this->listsCache;
-
-		return $cache->remember($user_id, function() use ($user_id, $include_shared) {
-			try {
-				// Get owned lists
-				$owned = $this->lists
-					->where(['user_id' => $user_id])
-					->orderBy('created_at', 'DESC')
-					->getResults(ARRAY_A);
-
-				// Add item counts
-				foreach ($owned as &$list) {
-					$list['item_count'] = $this->listItems
-						->where(['list_id' => $list['id']])
-						->countResults();
-					$list['is_owner'] = true;
-					$list['is_shared'] = false;
-				}
-
-				if (!$include_shared) {
-					return [
-						'success' => true,
-						'lists' => $owned
-					];
-				}
-
-				// Get shared lists
-				$shares = $this->listShares
-					->where(['user_id' => $user_id, 'status' => 'accepted'])
-					->getResults();
-
-				$shared_lists = [];
-				foreach ($shares as $share) {
-					$list = $this->lists
-						->where(['id' => $share->list_id])
-						->first(ARRAY_A);
-
-					if ($list) {
-						$owner = get_userdata($list['user_id']);
-						$list['owner_name'] = $owner ? $owner->display_name : 'Unknown';
-						$list['item_count'] = $this->listItems
-							->where(['list_id' => $list['id']])
-							->countResults();
-						$list['permission_type'] = $share->permission_type;
-						$list['is_owner'] = false;
-						$list['is_shared'] = true;
-
-						$shared_lists[] = $list;
-					}
-				}
-
-				return [
-					'success' => true,
-					'lists' => [
-						'owned' => $owned,
-						'shared' => $shared_lists
-					]
-				];
-
-			} catch (Exception $e) {
-				$this->logError('getAvailableLists', [
-					'error' => $e->getMessage(),
-					'user_id' => $user_id
-				]);
-
-				return [];
-			}
-		});
-	}
 
 	/**
+	 * TODO: Done until here
 	 * Get favourite counts by type
 	 */
 	public function getFavouriteCounts(WP_REST_Request $request): WP_REST_Response
 	{
+
 		$user_id = absint($request->get_param('user'));
 
 		if (!$this->userCheck($user_id)) {
 			return $this->unauthorized();
 		}
 
-		$key = "counts_{$user_id}";
-
-		$counts = $this->cache->remember($key, function() use ($user_id) {
-			try {
-				// Get counts grouped by type using raw query
-				$results = $this->favourites->queryResults(
-					"SELECT type, COUNT(*) as count FROM {table} WHERE user_id = %d GROUP BY type",
-					[$user_id],
-					OBJECT_K
-				);
-
-				$all_counts = array_fill_keys(
-					array_map(fn($type) => str_replace(BASE, '', $type), array_keys($this->valid_types)),
-					0
-				);
-
-				foreach ($results as $type => $data) {
-					$type_key = str_replace(BASE, '', $type);
-					$all_counts[$type_key] = (int)$data->count;
-				}
-
-				return $all_counts;
-
-			} catch (Exception $e) {
-				$this->logError('getFavouriteCounts', [
-					'error' => $e->getMessage(),
-					'user_id' => $user_id
-				]);
-
-				return array_fill_keys(array_keys($this->valid_types), 0);
-			}
-		});
+		$counts = JVB()->favourites()->getFavouriteCounts($user_id);
 
 		return Response::success(['counts' => $counts]);
 	}
@@ -504,7 +292,7 @@
 			'target_id' => $target_id
 		]);
 
-		if ($result['created']) {
+		if ((bool)$result) {
 			$this->updateFavouriteCount($type, $target_id);
 			$this->maybeNotifyOwner($type, $target_id, $user_id);
 		}
@@ -581,13 +369,13 @@
 							'type' => $type,
 							'target_id' => $target_id
 						]);
-						if ($result['created']) $results['added']++;
+						if ((bool) $result) $results['added']++;
 					} else {
-						$deleted = $table->where([
+						$deleted = $table->delete([
 							'user_id' => $user_id,
 							'type' => $type,
 							'target_id' => $target_id
-						])->deleteResults();
+						]);
 						if ($deleted) $results['removed']++;
 					}
 
@@ -735,122 +523,27 @@
 	}
 
 	/**
-	 * Clean up favourites when a post is deleted
-	 */
-	public function cleanupPostFavourites(int $post_id): void
-	{
-		try {
-			$type = get_post_type($post_id);
-			if (!$type) return;
-
-			$type = BASE . $type;
-
-			// Delete using fluent interface
-			$this->favourites->where([
-				'type' => $type,
-				'target_id' => $post_id
-			])->deleteResults();
-
-			$this->listItems->where([
-				'item_type' => $type,
-				'item_id' => $post_id
-			])->deleteResults();
-
-		} catch (Exception $e) {
-			$this->logError('cleanupPostFavourites', [
-				'error' => $e->getMessage(),
-				'post_id' => $post_id
-			]);
-		}
-	}
-
-	/**
-	 * Clean up favourites when a term is deleted
-	 */
-	public function cleanupTermFavourites(int $term_id, int $tt_id, string $taxonomy): void
-	{
-		try {
-			if (!isset($this->valid_types[$taxonomy])) {
-				return;
-			}
-
-			// Delete using fluent interface
-			$this->favourites->where([
-				'type' => $taxonomy,
-				'target_id' => $term_id
-			])->deleteResults();
-
-			$this->listItems->where([
-				'item_type' => $taxonomy,
-				'item_id' => $term_id
-			])->deleteResults();
-
-		} catch (Exception $e) {
-			$this->logError('cleanupTermFavourites', [
-				'error' => $e->getMessage(),
-				'term_id' => $term_id,
-				'taxonomy' => $taxonomy
-			]);
-		}
-	}
-
-	/**
-	 * Cleanup orphaned favourites using CustomTable query method
-	 */
-	public function cleanupOrphanedFavourites(): bool
-	{
-		try {
-			// Delete favourites for non-existent users
-			$this->favourites->query(
-				"DELETE f FROM {table} f
-                 LEFT JOIN {$GLOBALS['wpdb']->users} u ON f.user_id = u.ID
-                 WHERE u.ID IS NULL"
-			);
-
-			// Delete favourites for non-existent posts
-			$post_types = array_filter(
-				array_keys($this->valid_types),
-				fn($type) => $this->valid_types[$type]['table'] === 'post'
-			);
-
-			foreach ($post_types as $type) {
-				$this->favourites->query(
-					"DELETE f FROM {table} f
-                     LEFT JOIN {$GLOBALS['wpdb']->posts} p ON f.target_id = p.ID
-                     WHERE f.type = %s AND p.ID IS NULL",
-					[$type]
-				);
-			}
-
-			return true;
-
-		} catch (Exception $e) {
-			$this->logError('cleanupOrphanedFavourites', [
-				'error' => $e->getMessage()
-			]);
-
-			return false;
-		}
-	}
-
-	/**
 	 * Helper methods
 	 */
 	protected function buildParams(WP_REST_Request $request): array
 	{
 		$data = $request->get_params();
-		$args = ['user' => absint($data['user'])];
 
-		if (!array_key_exists('page', $data)) {
-			return $args;
+		$where = ['user_id' => absint($data['user'])];
+		if (!empty($data['content']) && $data['content'] !== 'all') {
+			$where['type'] = BASE . $data['content'];
 		}
 
-		$args = array_merge($args, [
-			'page' => max(1, absint($data['page'] ?? 1)),
-			'content' => $this->checkContent($data['content'] ?? 'all')
-		]);
+		$page    = max(1, absint($data['page'] ?? 1));
+		$perPage = 250;
 
-		return $this->applyOrderFilters($args, $data);
+		return [
+			'where'		=> $where,
+			'orderby' 	=> sanitize_text_field($data['orderby'] ?? 'created_at'),
+			'order'		=> sanitize_text_field($data['order'] ?? 'DESC'),
+			'per_page'	=> $perPage,
+			'page'		=> $page
+		];
 	}
 
 
@@ -919,112 +612,10 @@
 		}
 	}
 
-	/**
-	 * Notify content owner of new favourite if configured
-	 *
-	 * @param string $type Content type
-	 * @param int $target_id Content ID
-	 * @param int $user_id User who favourited
-	 * @return void
-	 */
-	protected function maybeNotifyOwner(string $type, int $target_id, int $user_id): void
-	{
-		try {
-			$owner_id = $this->getContentOwner($type, $target_id);
-
-			if ($owner_id && $owner_id !== $user_id) {
-				JVB()->notification()->addNotification(
-					$owner_id,
-					'new_favourite',
-					[
-						'user_id' => $user_id,
-						'type' => $type,
-						'target_id' => $target_id
-					]
-				);
-			}
-		} catch (Exception $e) {
-			// Silent fail - notifications are non-critical
-		}
-	}
-
-	/**
-	 * Remove any existing notifications about a favorite action
-	 *
-	 * @param int $user_id User who removed the favorite
-	 * @param string $type Content type
-	 * @param int $target_id Content ID
-	 * @return void
-	 */
-	protected function removeRelatedNotifications(int $user_id, string $type, int $target_id):void
-	{
-		try {
-			// Get the content owner(s)
-			$owner_ids = $this->getContentOwner($type, $target_id);
-			if (!$owner_ids) {
-				return;
-			}
-
-			$owner_ids = (is_array($owner_ids)) ? $owner_ids : [$owner_ids];
-
-			foreach ($owner_ids as $owner_id) {
-				// Skip if owner is the same as the user who unfavorited
-				if ($owner_id === $user_id) {
-					continue;
-				}
-
-				global $wpdb;
-				$notifications_table = $wpdb->prefix . BASE . 'notifications';
-
-				// Find recent (within last 30 days) new_favourite notifications from this user for this content
-				$notifications = $wpdb->get_results($wpdb->prepare(
-					"SELECT id FROM {$notifications_table}
-                WHERE owner_id = %d
-                AND action_user_id = %d
-                AND type = 'new_favourite'
-                AND target_id = %d
-                AND target_type = %s
-                AND created_at > DATE_SUB(%s, INTERVAL 30 DAY)",
-					$owner_id,
-					$user_id,
-					$target_id,
-					$type,
-					current_time('mysql')
-				));
-
-				if (empty($notifications)) {
-					continue;
-				}
-
-				// Delete the notifications
-				foreach ($notifications as $notification) {
-					$wpdb->delete(
-						$notifications_table,
-						['id' => $notification->id],
-						['%d']
-					);
-				}
-
-				// Invalidate notification cache for this user
-//                if (method_exists(JVB()->notification(), 'clearNotificationCache')) {
-//                    JVB()->notification()->clearNotificationCache($owner_id);
-//                }
-			}
-		} catch (Exception $e) {
-			// Log but continue
-			JVB()->error()->log(
-				'favourites',
-				'Error removing related notifications: ' . $e->getMessage(),
-				['type' => $type, 'target_id' => $target_id, 'user_id' => $user_id],
-				'warning'
-			);
-		}
-	}
-
 	public function maybeAcceptListInvite(int $user_id, string $email, array $data):void
 	{
 		if (array_key_exists('list_token', $data) && !empty($data['list_token'])) {
-			$this->acceptListInvitation($data['list_token'], $email);
+			JVB()->favourites()->acceptListShare($data['list_token'], $user_id);
 		}
 	}
 
@@ -1033,40 +624,35 @@
 	 */
 	protected function getListDetails(int $list_id, int $user_id): array
 	{
+		// Check access - either owner or has share
+		$is_owner = JVB()->favourites()->userOwnsList($list_id, $user_id);
+		$is_shared = JVB()->favourites()->userCanViewList($list_id, $user_id);
+
+
+		if (!$is_owner && !$is_shared) {
+			return [
+				'success'	=> false,
+				'message'	=> 'You do not have access to this list.'
+			];
+		}
+
+		$list = JVB()->favourites()->getListDetails($list_id, $user_id);
+
+		if (empty($list)) {
+			return [
+				'success' => false,
+				'message' => 'List not found'
+			];
+		}
+
+
+
 		$key = "list_{$list_id}_user_{$user_id}";
 
 		return $this->listsCache->remember($key, function () use ($list_id, $user_id) {
 			try {
-				// Check access - either owner or has share
-				$is_owner = $this->lists->where([
-					'id' => $list_id,
-					'user_id' => $user_id
-				])->existsInQuery();
 
-				$share = null;
-				if (!$is_owner) {
-					$share = $this->listShares->where([
-						'list_id' => $list_id,
-						'user_id' => $user_id,
-						'status' => 'accepted'
-					])->first();
-				}
 
-				if (!$is_owner && !$share) {
-					return [
-						'success' => false,
-						'message' => 'You do not have access to this list'
-					];
-				}
-
-				// Get list details
-				$list = $this->lists->where(['id' => $list_id])->first(ARRAY_A);
-				if (!$list) {
-					return [
-						'success' => false,
-						'message' => 'List not found'
-					];
-				}
 
 				// Get list items
 				$items = $this->listItems

--
Gitblit v1.10.0