From f4be611c51473359e6d41780f0313c446079e9d3 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 09 Jun 2026 15:19:24 +0000
Subject: [PATCH] =Switched the /base/options.php to the same pattern as Site.php: a class based approached rather than a filter. Updated Meta.php to play along with the defined fields from there in Meta::forOptions. Had to change openingHoursSpecificationsTrait.php to not use the translater functions __('text','textdomain') for now, as we load before init.
---
inc/managers/FavouritesManager.php | 221 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 220 insertions(+), 1 deletions(-)
diff --git a/inc/managers/FavouritesManager.php b/inc/managers/FavouritesManager.php
index 563dde2..c2f2c05 100644
--- a/inc/managers/FavouritesManager.php
+++ b/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();
@@ -29,7 +38,7 @@
}
private function defineFavouriteTable():void
{
- $table = CustomTable::for('favourites');
+ $table = CustomTable::for('favourites', true);
$table->setColumns([
'id' => 'bigint(20) unsigned NOT NULL AUTO_INCREMENT',
@@ -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
+ ]);
+ }
+ }
}
--
Gitblit v1.10.0