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