Jake Vanderwerf
22 hours ago f4be611c51473359e6d41780f0313c446079e9d3
inc/managers/FavouritesManager.php
@@ -1,6 +1,7 @@
<?php
namespace JVBase\managers;
use Exception;
use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
@@ -18,8 +19,16 @@
   public function __construct()
   {
      $this->defineTables();
      $this->registerHooks();
   }
   protected function registerHooks():void
   {
      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']);
   }
   protected function defineTables():void
   {
      $this->defineFavouriteTable();
@@ -126,6 +135,7 @@
            'list_id'      => 'bigint(20) unsigned NOT NULL',
            'user_id'      => "{$table->getUserIDType()} NOT NULL",
            'email'        => 'varchar(255) NOT NULL',
            'invite_token' => 'varchar(255) NOT NULL',
            'permission'   => "ENUM('view', 'edit') NOT NULL DEFAULT 'view'",
            'status'    => "ENUM('pending', 'accepted', 'rejected', 'revoked') NOT NULL DEFAULT 'pending'",
            'created_at'   => 'datetime NOT NULL DEFAULT CURRENT_TIMESTAMP',
@@ -372,6 +382,7 @@
         } elseif ($newUser) {
            $email = sanitize_email($newUser['email']);
            $name = sanitize_text_field($newUser['name']);
            $args['invite_token'] = wp_generate_password(32, false);
            $args['email'] = $email;
            //TODO: Magic Link setup
         }
@@ -461,6 +472,168 @@
            'status' => $accept ? 'accepted' : 'rejected',
         ], $args);
      }
      public function getFavourites(array $args = []):array
      {
         return $this->favourites->getMany($args, false);
      }
      public function getLists(array $args = []):array
      {
         return $this->lists->getMany($args, false);
      }
         public function getAvailableLists(array $args = [], bool $include_shared = true):array
         {
            $lists = [];
            $owned = $this->lists->getMany($args, false);
            foreach ($owned['items'] as &$list) {
               $list['item_count'] = $this->listItems->count(['list_id' => $list['id']]);
            }
            $lists['owned'] = $owned;
            if ($include_shared) {
               $args['where']['status'] = 'accepted';
               $sharedLists = $this->listShares->getMany($args);
               $shared = [];
               foreach ($sharedLists as $share) {
                  $sharedList = $this->lists->get(['id' => $share->list_id]);
                  if ($sharedList) {
                     $sharedList['owner_name'] = jvbGetUsername($sharedList['user_id']);
                     $sharedList['item_count'] = $this->listItems->count(['list_id' => $sharedList['id']]);
                     $sharedList['permission_type'] = $sharedList->permission_type;
                     $shared[] = $sharedList;
                  }
               }
               $lists['shared'] = $shared;
            }
            return $lists;
         }
         public function userOwnsList(int $list_id, int $user_id):bool
         {
            return $this->lists->count(['id' => $list_id, 'user_id' => $user_id]) > 0;
         }
         public function getListDetails(int $list_id, int $user_id, int $page = 1):array
         {
            $list = JVB()->favourites()->getLists(['id' => $list_id, 'user_id' => $user_id]);
            if (!$list) {
               return [];
            }
            $items = $this->listItems->getMany([
               'where'  => ['list_id' => $list_id],
               'order_by'  => 'added_at',
               'order'     => 'DESC',
               'per_page'  => 25,
               'page'      => $page,
            ]);
            $formatted = [];
            foreach($items as $item) {
               //See if we can get the actual favourite record first
               if ($item->favourite_id) {
                  $fav = $this->favourites->get(['id' => $item->favourite_id]);
                  if ($fav) {
                     $formatted[] = $fav;
                     continue;
                  }
               }
               //Create a dummy favourite object otherwise
               $formatted[] = (object) [
                  'type'      => $item->item_type,
                  'target_id' => $item->item_id,
                  'created_at'=> $item->added_at
               ];
            }
            $sharedWith = [];
            $is_owner = $this->userOwnsList($list_id, $user_id);
            if ($is_owner) {
               $shares = $this->listShares->getMany([
                  'where' => ['list_id' => $list_id],
                  'order_by' => 'created_at',
                  'order'  => 'DESC'
               ]);
               foreach ($shares as $share_item) {
                  $user = [
                     'email'     => $share_item->email,
                     'status' => $share_item->status,
                     'date_added'=> $share_item->created_at,
                  ];
                  if ($share_item->status === 'accepted' && $share_item->user_id) {
                     $user['name'] = jvbGetUsername($share_item->user_id);
                     $user['permission_type'] = $share_item->permission_type;
                  }
                  $sharedWith[] = $user;
               }
            }
            return [
               'id'  => (int)$list['id'],
               'name'   => $list['name'],
               'description'  => $list['description']??'',
               'created_at'   => $list['created_at'],
               'is_owner'     => $is_owner,
               'items'        => $formatted,
               'shared_users' => $sharedWith
            ];
         }
   public function getFavouriteCounts(int $user_id): array
   {
      $results = $this->favourites->queryResults(
         "SELECT type, COUNT(*) as count FROM {table} WHERE user_id = %d GROUP BY type",
         [$user_id]
      );
      $counts = [];
      foreach ($results as $row) {
         $type = str_replace(BASE, '', $row->type);
         $counts[$type] = (int) $row->count;
      }
      $defaults = array_fill_keys(
         array_map(fn($t) => str_replace(BASE, '', $t), Registrar::withFeature('favouritable')),
         0
      );
      return array_merge($defaults, $counts);
   }
   public function cleanupOrphanedFavourites(): bool
   {
      global $wpdb;
      // Posts - no FK possible since target_id is generic
      $this->favourites->query(
         "DELETE f FROM {table} f
         LEFT JOIN {$wpdb->posts} p ON f.target_id = p.ID
         WHERE f.type IN (
             SELECT CONCAT('" . BASE . "', post_type)
             FROM {$wpdb->posts} GROUP BY post_type
         )
         AND p.ID IS NULL"
      );
      // Terms - same reason
      $this->favourites->query(
         "DELETE f FROM {table} f
         LEFT JOIN {$wpdb->term_taxonomy} tt ON f.target_id = tt.term_id
         AND f.type = CONCAT('" . BASE . "', tt.taxonomy)
         WHERE tt.term_id IS NULL
         AND f.type != CONCAT('" . BASE . "', 'user')"
      );
      $this->listItems->query(
         "DELETE li FROM {table} li
         LEFT JOIN {$wpdb->posts} p ON li.target_id = p.ID
         LEFT JOIN {$wpdb->term_taxonomy} tt ON li.target_id = tt.term_id
         WHERE p.ID IS NULL AND tt.term_id IS NULL"
      );
      return true;
   }
   /***************************************************************
    * UTILITY METHODS
    **************************************************************/
@@ -573,4 +746,50 @@
      unset($notes[$index]);
      return array_values($notes); //reindexed array
   }
   public function cleanupPostFavourites(int $post_id):void
   {
      try {
         $type = get_post_type($post_id);
         if (!$type) return;
         $this->favourites->where([
            'type'   => $type,
            'target_id' => $post_id
         ])->deleteResults();
         $this->listItems->where([
            'item_type' => $type,
            'item_id'   => $post_id
         ])->deleteResults();
      } catch (Exception $e) {
         JVB()->error()->log('cleanupPostFavourites', $e->getMessage(), [
            'post_id' => $post_id
         ]);
      }
   }
   public function cleanupTermFavourites(int $term_id, int $tt_id, string $taxonomy):void
   {
      try {
         $registrar = Registrar::getInstance($taxonomy);
         if (!$registrar || !$registrar->hasFeature('favouritable')) {
            return;
         }
         $this->favourites->where([
            'type'   => $taxonomy,
            'target_id' => $term_id,
         ])->deleteResults();
         $this->listItems->where([
            'item_type' => $taxonomy,
            'item_id'   => $term_id
         ])->deleteResults();
      } catch (Exception $e) {
         JVB()->error()->log('cleanupTermFavourites', $e->getMessage(), [
            'term_id' => $term_id,
            'taxonomy'=> $taxonomy
         ]);
      }
   }
}