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.
---
checks.php | 32 +-
base/_setup.php | 2
inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php | 12
assets/js/concise/FrontendFavourites.js | 72 ++++-
inc/meta/Meta.php | 7
base/Site.php | 4
jvb.php | 5
base/options.php | 138 +++++++++++
inc/rest/routes/FavouritesRoutes.php | 244 --------------------
inc/registrar/config/Integration.php | 11
inc/helpers/all.php | 3
inc/meta/Form.php | 9
inc/registrar/Fields.php | 2
inc/managers/FavouritesManager.php | 110 +++++++++
inc/registrar/Registrar.php | 12
15 files changed, 368 insertions(+), 295 deletions(-)
diff --git a/assets/js/concise/FrontendFavourites.js b/assets/js/concise/FrontendFavourites.js
index 47befbe..f986ff1 100644
--- a/assets/js/concise/FrontendFavourites.js
+++ b/assets/js/concise/FrontendFavourites.js
@@ -26,6 +26,11 @@
}
});
+ this.queue = {
+ add: new Map(),
+ remove: new Map(),
+ };
+
this.store = store.favourites;
// this.listStore = window.jvbStore.register(
@@ -71,39 +76,66 @@
// Update button icon
button.innerHTML = jvbSettings.icons[button.classList.contains('favourited') ? 'heart-filled' : 'heart'];
- window.debouncer.schedule(
- `favourite-${button.dataset.id}`,
- this.postFavourite({
- user: window.auth.getUser(),
- target_id: button.dataset.id,
- action: action,
- type: button.dataset.type
- }),
- 100
- );
+ this.checkQueue(action, {
+ target_id: button.dataset.id,
+ action: action,
+ type: button.dataset.type
+ });
+ window.debouncer.schedule('favourites', this.postFavourites.bind(this), 200);
+
}
- async postFavourite(args) {
- await window.auth.fetch(
+ checkQueue(action, item) {
+ switch (action) {
+ case 'add':
+ if (this.queue.remove.has(item.target_id)) {
+ this.queue.remove.delete(item.target_id);
+ }
+ this.queue.add.set(item.target_id, item);
+ break;
+ case 'remove':
+ if (this.queue.add.has(item.target_id)) {
+ this.queue.add.delete(item.target_id);
+ }
+ this.queue.remove.set(item.target_id, item);
+ break;
+ default:
+ return;
+ }
+ }
+ async postFavourites() {
+ console.log(this.queue,'Posting favourites');
+ const response = await window.auth.fetch(
`${jvbSettings.api}favourites`,
{
method: 'POST',
headers: {
'X-Action-Nonce': window.auth.getNonce('favourites')
},
- body: args
+ body: {
+ user: window.auth.getUser(),
+ ... this.queue
+ }
}
);
- if (args.action === 'add') {
- await this.store.setItem(button.dataset.id, {
- target_id: button.dataset.id,
- action: action,
- type: button.dataset.type,
- }).then(()=>{});
+ if (response.ok) {
+ console.log('Posted favourites - clearing queue');
+ //Add or remove from store
+ for (let item of this.queue.add.entries()) {
+ await this.store.setItem(item.target_id,
+ item);
+ }
+ for (let item of this.queue.remove.entries()) {
+ await this.store.delete(item.target_id);
+ }
+ //Clear the favourite queue
+ this.queue.add.clear();
+ this.queue.remove.clear();
} else {
- await this.store.delete(button.dataset.id).then(()=>{});
+ console.log(await response.json(), 'Something went wrong');
+ window.debouncer.schedule('favourites', this.postFavourites.bind(this), 200);
}
}
diff --git a/base/Site.php b/base/Site.php
index 43d194a..2276b00 100644
--- a/base/Site.php
+++ b/base/Site.php
@@ -62,6 +62,10 @@
*/
protected static bool $limit_hours = false;
/**
+ * @var bool $has_hours Whether this site has business hours
+ */
+ protected static bool $has_hours = false;
+ /**
* @var bool $enthusiast Whether to scaffold enthusiasts (users that can interact with and save favourites)
*/
protected static bool $enthusiast = false;
diff --git a/base/_setup.php b/base/_setup.php
index 077e84f..544b7e0 100644
--- a/base/_setup.php
+++ b/base/_setup.php
@@ -16,7 +16,7 @@
//require(JVB_DIR.'/base/users.php');
require(JVB_DIR.'/base/Login.php');
require(JVB_DIR.'/base/Membership.php');
-require(JVB_DIR.'/base/options.php');
+require(JVB_DIR.'/base/Options.php');
require(JVB_DIR.'/base/SchemaHelper.php');
$base = apply_filters('jvb_base', 'jvb_');
diff --git a/base/options.php b/base/options.php
index 44a43c5..50f0979 100644
--- a/base/options.php
+++ b/base/options.php
@@ -1,7 +1,145 @@
<?php
+namespace JVBase\base;
+use JVBase\managers\Cache;
+use JVBase\meta\Form;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
+
/**
* Custom options for the site can be set here. They will need to be handled by the child plugin, but you can make use of the Meta.php this way
*/
$options = apply_filters('jvb_options', []);
define('JVB_OPTIONS', $options);
+
+
+class Options {
+ protected static Options $instance;
+ protected array $resetFields = [];
+ protected string $resetInterval = 'daily';
+ protected Fields $fields;
+ protected array $sections = [];
+ protected static array $values = [];
+
+ private function __construct(){
+ $this->setFields();
+ if (!empty($this->resetFields)) {
+ add_action('jvbDailyReset', [$this, 'resetOptions']);
+ }
+ }
+
+ public static function getInstance():Options {
+ if (!isset(self::$instance)) {
+ self::$instance = new self();
+ do_action('jvb_define_options');
+ }
+ return self::$instance;
+ }
+
+ public function setFields():void
+ {
+ $this->fields = new Fields('options');
+ if (Site::has('limit_hours')) {
+ $this->fields->addField(
+ 'today_hours',
+ [
+ 'type' => 'group',
+ 'label' => 'Today\'s Hours',
+ 'fields' => [
+ 'time_start' => [
+ 'type' => 'time',
+ 'label' => 'Open',
+ ],
+ 'time_end' => [
+ 'type' => 'time',
+ 'label' => 'Closed',
+ ]
+ ]
+ ]
+ );
+ $this->fields->addField(
+ 'open_to_public',
+ [
+ 'type' => 'true_false',
+ 'label' => 'Open to Public?'
+ ]
+ );
+
+ if (Site::hasAnyIntegration(['facebook','instagram'])) {
+ $this->fields->addField(
+ 'post_to_social',
+ [
+ 'type' => 'true_false',
+ 'label' => 'Post to Socials?',
+ ]
+ );
+ }
+ }
+
+ if (Site::hasIntegration('gmb') || Site::has('hours')) {
+ $this->fields->addCommon('hours');
+ }
+ }
+ public function fields():Fields
+ {
+ return $this->fields;
+ }
+
+ public function resetOptions():void
+ {
+ if (empty($this->resetFields)) {
+ return;
+ }
+ foreach ($this->resetFields as $field) {
+ self::delete($field);
+ }
+ Cache::for('options')->flush();
+ }
+
+ public static function get(string $fieldName):mixed
+ {
+ if (!array_key_exists($fieldName, self::$values)) {
+ $meta = Meta::forOptions();
+ self::$values[$fieldName] = $meta->get($fieldName);
+ }
+ return self::$values[$fieldName];
+ }
+ public static function delete(string $fieldName):void
+ {
+ if (array_key_exists($fieldName, self::$values)) {
+ unset(self::$values[$fieldName]);
+ }
+ $meta = Meta::forOptions();
+ $meta->delete($fieldName);
+ }
+ public static function set(string $fieldName, mixed $value):void
+ {
+ $meta = Meta::forOptions();
+ $meta->set($fieldName, $value);
+ self::$values = $value;
+ }
+
+ public static function render(array $fieldNames, array $options = [], bool $output = false):string
+ {
+ $meta = Meta::forOptions();
+ $result = Form::renderFormFrom(
+ $meta,
+ 'options',
+ $options,
+ $fieldNames
+ );
+ if ($output) {
+ echo $result;
+ }
+ return $result;
+ }
+
+ public function getFields():array
+ {
+ return array_map(function ($field) {
+ return $field->getConfig();
+ },
+ $this->fields->getFields()
+ );
+ }
+}
diff --git a/checks.php b/checks.php
index d968100..79cc829 100644
--- a/checks.php
+++ b/checks.php
@@ -1,5 +1,6 @@
<?php
+use JVBase\base\Options;
use JVBase\base\Site;
use JVBase\managers\Cache;
use JVBase\registrar\Registrar;
@@ -18,22 +19,21 @@
}
-//function jvbIsOpen():bool
-//{
-//
-// if (!jvbCheck('limit_hours', JVB_SITE)) {
-// return true;
-// }
-// if (get_option(BASE.'open_to_public') !== '1') {
-// return false;
-// }
-// //Check if today_hours is set
-// if (get_option(BASE.'today_hours')) {
-// return jvbIsTimeBetween();
-// }
-// //Default to the stored settings
-// return jvbIsCurrentlyOpen();
-//}
+
+function jvbIsOpen():bool
+{
+ if (!Site::has('limit_hours')) {
+ return true;
+ }
+ if (Options::get('open_to_public') !== 1) {
+ error_log('Not open to public');
+ return false;
+ }
+ if (Options::get('today_hours')) {
+ return jvbIsTimeBetween();
+ }
+ return jvbIsCurrentlyOpen();
+}
function jvbTermHasPosts(int $termID, string $taxonomy):bool
diff --git a/inc/helpers/all.php b/inc/helpers/all.php
index 0891fab..274e00d 100644
--- a/inc/helpers/all.php
+++ b/inc/helpers/all.php
@@ -1,5 +1,7 @@
<?php
+use JVBase\base\Site;
+
if (!defined('ABSPATH')) {
exit;
}
@@ -86,3 +88,4 @@
'post_type' => $type
]);
}
+
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
+ ]);
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
index 594ae51..b21a54b 100644
--- a/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
@@ -47,11 +47,11 @@
{
$fields->addField('openingHours', [
'type' => 'repeater',
- 'label' => __('Opening Hours', 'jvb'),
+ 'label' => 'Opening Hours',
'fields' => [
'dayOfWeek' => [
'type' => 'set',
- 'label' => __('Day(s) of Week', 'jvb'),
+ 'label' => 'Day(s) of Week',
'options' => [
'Mo' => 'Monday',
'Tu' => 'Tuesday',
@@ -65,23 +65,23 @@
],
'opens' => [
'type' => 'time',
- 'label' => __('Opens at', 'jvb'),
+ 'label' => 'Opens at',
'required' => true
],
'closes' => [
'type' => 'time',
- 'label' => __('Closes at', 'jvb'),
+ 'label' => 'Closes at',
'required' => true
]
]
]);
$fields->addField('by_appointment', [
'type' => 'true_false',
- 'label' => __('By Appointment Only', 'jvb'),
+ 'label' => 'By Appointment Only',
]);
$fields->addField('allow_walkins', [
'type' => 'true_false',
- 'label' => __('Walk Ins Welcome', 'jvb')
+ 'label' => 'Walk Ins Welcome'
]);
}
public function formatOpeningHoursSpecificationField(Meta $meta):void
diff --git a/inc/meta/Form.php b/inc/meta/Form.php
index 8b7b414..868abf9 100644
--- a/inc/meta/Form.php
+++ b/inc/meta/Form.php
@@ -53,7 +53,7 @@
/**
* Render complete form from Meta instance
*/
- public static function renderFormFrom(Meta $meta, string $endpoint, array $options = []): string
+ public static function renderFormFrom(Meta $meta, string $endpoint, array $options = [], array $fields = []): string
{
$id = $options['form-id'] ?? $endpoint;
$classes = isset($options['classes']) ? ' class="' . implode(' ', $options['classes']) . '"' : '';
@@ -70,9 +70,10 @@
$output .= '<p>' . esc_html($d) . '</p>';
}
}
-
- foreach ($meta->configs() as $name => $config) {
- $output .= static::render($name, $meta->get($name), $config);
+ $allFields = $meta->getAll($fields);
+ foreach ($allFields as $name => $value) {
+ $config = $meta->config($name);
+ $output .= static::render($name, $value, $config);
}
if (!empty($options['submit'])) {
diff --git a/inc/meta/Meta.php b/inc/meta/Meta.php
index 35a8c3d..d870467 100644
--- a/inc/meta/Meta.php
+++ b/inc/meta/Meta.php
@@ -1,6 +1,7 @@
<?php
namespace JVBase\meta;
+use JVBase\base\Options;
use JVBase\registrar\Registrar;
use WP_Post;
use WP_Term;
@@ -75,7 +76,7 @@
/**
* Create Meta instance for options
*/
- public static function forOptions(?string $baseKey = 'ajv'): self
+ public static function forOptions(?string $baseKey = BASE): self
{
if (array_key_exists($baseKey, self::$instances['options'])) {
return self::$instances['options'][$baseKey];
@@ -118,6 +119,10 @@
$registrar = !is_null($this->slug) ? Registrar::getInstance($this->slug) : false;
$fields = $registrar ? $registrar->getFields() : [];
+ if ($this->type == 'options') {
+ $options = Options::getInstance();
+ $fields = $options->getFields();
+ }
$meta = match($type) {
'post' => get_post_meta($id),
'term' => get_term_meta($id),
diff --git a/inc/registrar/Fields.php b/inc/registrar/Fields.php
index 8e37662..79d07bd 100644
--- a/inc/registrar/Fields.php
+++ b/inc/registrar/Fields.php
@@ -17,7 +17,7 @@
class Fields {
protected array $fields;
- private Registrar $registrar;
+ private ?Registrar $registrar = null;
public function __construct(?string $type = null, ?Registrar $registrar = null) {
$this->registrar = $registrar;
diff --git a/inc/registrar/Registrar.php b/inc/registrar/Registrar.php
index 8b4b57f..1ecadf3 100644
--- a/inc/registrar/Registrar.php
+++ b/inc/registrar/Registrar.php
@@ -14,6 +14,7 @@
use JVBase\registrar\config\Directory;
use JVBase\registrar\config\Feed;
use JVBase\registrar\config\Integration;
+use JVBase\registrar\config\Register;
use JVBase\registrar\config\Section;
use JVBase\registrar\config\SEO;
use JVBase\registrar\helpers\AddIntegrationFields;
@@ -212,6 +213,7 @@
protected Dashboard $dashboard;
protected Directory|false $directory;
protected Feed|false $feed;
+ protected Register|false $login;
// protected Management $management;
// protected Responses $responses;
protected ?SEO $seo = null;
@@ -653,7 +655,7 @@
public function config(string $config):mixed
{
- $allowed = ['breadcrumbs','calendar','dashboard','directory','feed','management','has_responses','seo','trackchanges','verification'];
+ $allowed = ['breadcrumbs','calendar','register','login','dashboard','directory','feed','management','has_responses','seo','trackchanges','verification'];
if (!in_array(strtolower($config), $allowed)) {
error_log('Invalid config requested from Registrar: '.$config);
return [];
@@ -662,6 +664,7 @@
'breadcrumbs' => $this->getBreadcrumbs(),
'dashboard' => $this->getDashboard(),
'directory' => $this->getDirectory(),
+ 'register','login' => $this->getLogin(),
'feed' => $this->getFeed(),
'management' => $this->getManagement(),
'has_responses' => $this->getResponses(),
@@ -670,6 +673,13 @@
'verification' => $this->getVerification()
};
}
+ protected function getLogin():Register|false
+ {
+ if (!isset($this->login)) {
+ $this->login = new Register();
+ }
+ return $this->login;
+ }
protected function getBreadcrumbs():Breadcrumbs
{
if (!isset($this->breadcrumbs)) {
diff --git a/inc/registrar/config/Integration.php b/inc/registrar/config/Integration.php
index 8a829d5..3b9a6a3 100644
--- a/inc/registrar/config/Integration.php
+++ b/inc/registrar/config/Integration.php
@@ -13,7 +13,7 @@
* @var string Must match as defined in the JVBase\integrations namespace
*/
protected string $service_name;
- protected string $content_type;
+ protected ?string $content_type = null;
/**
* @var bool Whether to send to the integration on publish
*/
@@ -45,11 +45,14 @@
}
/**
- * @param string $content must match what integration expects
+ * @param ?string $content must match what integration expects
* @return self
*/
- public function setContentType(string $content):self
+ public function setContentType(?string $content):self
{
+ if (is_null($content)) {
+ return $this;
+ }
$connection = JVB()->connect($this->service_name);
if (!$connection){
error_log('[Integration]::setContentType Service is not setup. '.$this->service_name);
@@ -64,7 +67,7 @@
return $this;
}
- public function getContentType():string
+ public function getContentType():?string
{
return $this->content_type;
}
diff --git a/inc/rest/routes/FavouritesRoutes.php b/inc/rest/routes/FavouritesRoutes.php
index 9f38917..953e4db 100644
--- a/inc/rest/routes/FavouritesRoutes.php
+++ b/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);
}
}
diff --git a/jvb.php b/jvb.php
index 16816ed..6cc5ff8 100644
--- a/jvb.php
+++ b/jvb.php
@@ -259,6 +259,7 @@
add_action('plugins_loaded', 'jvb_site_definitions',1);
add_action('plugins_loaded', 'jvb_registrar_definitions',2);
add_action('plugins_loaded', 'jvb_field_definitions', 3);
+add_action('plugins_loaded', 'jvb_options_definitions',3);
add_action('init', 'jvbLoadBase', 1);
add_action('init', 'jvb_integration_definitions',3);
add_action('init', 'jvb_field_section_definitions', 5);
@@ -279,6 +280,10 @@
do_action('jvb_define_fields');
}
+function jvb_options_definitions():void
+{
+ do_action('jvb_define_options');
+}
function jvbLoadBase():void
{
--
Gitblit v1.10.0