Jake Vanderwerf
22 hours ago f4be611c51473359e6d41780f0313c446079e9d3
inc/rest/routes/FavouritesRoutes.php
@@ -46,13 +46,6 @@
      $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
@@ -215,44 +208,14 @@
    */
   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]);
   }
@@ -560,105 +523,6 @@
   }
   /**
    * 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
@@ -748,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);
      }
   }