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 |  110 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/inc/managers/FavouritesManager.php b/inc/managers/FavouritesManager.php
index 125c75a..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();
@@ -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
 			}
@@ -570,6 +581,59 @@
 					'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
 	 **************************************************************/
@@ -682,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