Jake Vanderwerf
2026-08-26 ccf5e61a075c35d24befc175994b5cb8957ca322
=Extra JVB() calls replaced, minor tweaks
60 files modified
1 files deleted
1529 ■■■■■ changed files
checks.php 2 ●●●●● patch | view | raw | blame | history
inc/blocks/CustomBlocks.php 16 ●●●●● patch | view | raw | blame | history
inc/blocks/DrawerMenuBlock.php 4 ●●●●● patch | view | raw | blame | history
inc/blocks/FeedBlock.php 6 ●●●●● patch | view | raw | blame | history
inc/blocks/FeedBlockOld.php 6 ●●●●● patch | view | raw | blame | history
inc/blocks/FormBlock.php 4 ●●●●● patch | view | raw | blame | history
inc/blocks/MenuBlock.php 100 ●●●●● patch | view | raw | blame | history
inc/blocks/SummaryBlock.php 19 ●●●●● patch | view | raw | blame | history
inc/blocks/TimelineBlock.php 5 ●●●●● patch | view | raw | blame | history
inc/forms/PostSelector.php 6 ●●●●● patch | view | raw | blame | history
inc/helpers/renderFields.php 4 ●●●●● patch | view | raw | blame | history
inc/integrations/ServiceMap.php 9 ●●●●● patch | view | raw | blame | history
inc/integrations/_Base.php 3 ●●●●● patch | view | raw | blame | history
inc/integrations/services/GoogleMapsOld.php 10 ●●●●● patch | view | raw | blame | history
inc/integrations/services/GoogleMyBusiness.php 361 ●●●●● patch | view | raw | blame | history
inc/integrations/services/GoogleMyBusinessOld.php 4 ●●●●● patch | view | raw | blame | history
inc/integrations/services/InstagramOld.php 7 ●●●●● patch | view | raw | blame | history
inc/integrations/services/UmamiOld.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/CustomTable.php 2 ●●●●● patch | view | raw | blame | history
inc/managers/Dashboard/Dashboard.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/Dashboard/DashboardPage.php 6 ●●●●● patch | view | raw | blame | history
inc/managers/DashboardManager.php 13 ●●●●● patch | view | raw | blame | history
inc/managers/DirectoryManager.php 7 ●●●●● patch | view | raw | blame | history
inc/managers/Favourites.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/IconsManager.php 74 ●●●●● patch | view | raw | blame | history
inc/managers/InvitationsManager.php 2 ●●●●● patch | view | raw | blame | history
inc/managers/Login.php 2 ●●●●● patch | view | raw | blame | history
inc/managers/MagicLinks.php 13 ●●●●● patch | view | raw | blame | history
inc/managers/OperationQueue.php 12 ●●●●● patch | view | raw | blame | history
inc/managers/Referrals.php 7 ●●●●● patch | view | raw | blame | history
inc/managers/Responses.php 13 ●●●●● patch | view | raw | blame | history
inc/managers/SEO/render/SchemaOutput.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/SEO/render/Traits/_Properties/reviewTrait.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/TaxonomyRelationships.php 4 ●●●●● patch | view | raw | blame | history
inc/managers/UserTermsManager.php 2 ●●●●● patch | view | raw | blame | history
inc/managers/queue/Queue.php 3 ●●●●● patch | view | raw | blame | history
inc/managers/queue/executors/ContentExecutor.php 3 ●●●●● patch | view | raw | blame | history
inc/managers/queue/executors/IntegrationExecutor.php 14 ●●●●● patch | view | raw | blame | history
inc/meta/Storage.php 27 ●●●●● patch | view | raw | blame | history
inc/registrar/Posts.php 2 ●●●●● patch | view | raw | blame | history
inc/registrar/Registrar.php 7 ●●●●● patch | view | raw | blame | history
inc/registrar/config/seo/Schema.php 12 ●●●●● patch | view | raw | blame | history
inc/rest/RegisterRoutes.php 359 ●●●●● patch | view | raw | blame | history
inc/rest/Rest.php 8 ●●●●● patch | view | raw | blame | history
inc/rest/RestRouteManager.php 10 ●●●●● patch | view | raw | blame | history
inc/rest/routes/ContentRoutes.php 16 ●●●●● patch | view | raw | blame | history
inc/rest/routes/ContentTermsRoutes.php 9 ●●●●● patch | view | raw | blame | history
inc/rest/routes/FeedRoutes.php 4 ●●●●● patch | view | raw | blame | history
inc/rest/routes/Invitations.php 8 ●●●●● patch | view | raw | blame | history
inc/rest/routes/LoginRoutes.php 139 ●●●●● patch | view | raw | blame | history
inc/rest/routes/NewsRoutes.php 8 ●●●●● patch | view | raw | blame | history
inc/rest/routes/QueueRoutes.php 4 ●●●●● patch | view | raw | blame | history
inc/rest/routes/ResponseRoutes.php 2 ●●●●● patch | view | raw | blame | history
inc/rest/routes/ShopRoutes.php 11 ●●●●● patch | view | raw | blame | history
inc/rest/routes/TermRoutes.php 9 ●●●●● patch | view | raw | blame | history
inc/rest/routes/UploadRoutes.php 2 ●●●●● patch | view | raw | blame | history
inc/utility/Image.php 14 ●●●●● patch | view | raw | blame | history
inc/utility/Video.php 10 ●●●●● patch | view | raw | blame | history
jvb.php 39 ●●●●● patch | view | raw | blame | history
src/forms/render.php 54 ●●●●● patch | view | raw | blame | history
templates/dashboard/sections/favourites.php 12 ●●●●● patch | view | raw | blame | history
checks.php
@@ -37,7 +37,7 @@
function jvbTermHasPosts(int $termID, string $taxonomy):bool
{
    $cache = Cache::for('termUtility', 30*60)->connect('taxonomy');
    $cache = Cache::for('termUtility', 'term', 30*60);
    return $cache->remember(
        $termID,
        function() use($taxonomy, $termID) {
inc/blocks/CustomBlocks.php
@@ -1405,9 +1405,6 @@
        if (!static::$currentLoop) {
            if (JVB_TESTING) {
                jvbDump('No loop stored');
            }
            return $content;
        }
        if (static::$currentLoop->have_posts()) {
@@ -1943,9 +1940,7 @@
            );
        }
        if (JVB_TESTING) {
            jvbDump('Could not create template part block for '.$block['blockName']);
        }
        return $content;
//      $isHeaderTemplate = (
@@ -2792,6 +2787,9 @@
                    );
                }
                return $classes;
            case 'anchor':
                //TODO: What's this doing?
                return '';
            default:
                if (JVB_TESTING && !is_admin() &&!in_array($key, $this->ignore)) {
//                  TESTING
@@ -3154,7 +3152,9 @@
                    break;
                }
                return $this->getColorStyle($value, $type);
            case 'anchor':
                //TODO: What should we do here?
                break;
            // Any other attributes that need direct styling
            default:
                if (JVB_TESTING && !is_admin() && !in_array($key, $this->ignore)) {
@@ -3184,7 +3184,7 @@
                        break;
                    default:
                        if (JVB_TESTING) {
                            jvbDump($value, 'No preset found for size: '.print_r($value, true));
                            error_log('No preset found for size: '.print_r($value, true));
                        }
                }
            }
inc/blocks/DrawerMenuBlock.php
@@ -49,9 +49,7 @@
        }
        $cache = Cache::for('drawer');
        if (JVB_TESTING) {
            $cache->flush();
        }
        if (!is_front_page()) {
            $menu_items[] = [
inc/blocks/FeedBlock.php
@@ -32,12 +32,6 @@
    public function __construct()
    {
//      // Initialize cache with connections
//      $this->cache = Cache::for('feed_block','post', WEEK_IN_SECONDS);
//      if (JVB_TESTING) {
//          $this->cache->flush();
//      }
        add_action('init', [$this, 'registerBlock']);
    }
inc/blocks/FeedBlockOld.php
@@ -22,9 +22,7 @@
    {
        // Initialize cache with connections
        $this->cache = Cache::for('feed_block', WEEK_IN_SECONDS);
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerBlock']);
    }
@@ -117,7 +115,7 @@
        $this->config = $this->buildParams($attributes);
        return $this->cache->remember(
            $this->cache->generateKey($this->config),
            $this->config,
            function() {
                return $this->renderBlock();
            }
inc/blocks/FormBlock.php
@@ -36,9 +36,7 @@
    {
        $this->cache = Cache::for('forms', null, WEEK_IN_SECONDS);
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        // Initialize forms from filter
        $this->forms = $this->registerForms();
        $this->form_contact = apply_filters('jvb_form_contact', '');
inc/blocks/MenuBlock.php
@@ -215,54 +215,78 @@
                        <div>
                            <h4>%s â€” %s</h4>
                            %s
                        </div>', //Note: we had 3 %s%s%s before the last </div> last time. This was to add optional ingredients or preparation time duration, not default in WooCommerce
                        </div><a href="?add-to-cart=%d&variation_id=%d"
   data-quantity="1"
   data-product_id="%d"
   data-variation_id="%d"
   class="add_to_cart_button ajax_add_to_cart add-to-cart">
   %s
   <span>Add to Cart</span>
</a>', //Note: we had 3 %s%s%s before the last </div> last time. This was to add optional ingredients or preparation time duration, not default in WooCommerce
                        $row['title'],
                        jvbCurrency($row['price']),
                        empty($row['description']) ? '' : wpautop($row['description']),
                        $ID,
                        $row['id'],
                        $ID,
                        $row['id'],
                        jvbIcon('plus-square')
                );
                $out .= Form::render(
                    $row['id'],
                    '',
                    [
                        'type'  => 'quantity',
                        'label' => $row['title'],
                        'min'   => 0,
                        'max'   => 30,
                        'data'  => [
                            'price' => $row['price'],
                            'id'    => $row['id'],
                            'sku'   => $row['sku'],
                            'name'  => $row['title'],
                            'add-to-cart'   => ''
                        ],
                        'add'   => 'Add 1 '.$row['title'].' to cart',
                        'remove'    => 'Remove 1 '.$row['title']. ' from cart',
                    ]
                );
//              $out .= Form::render(
//                  $row['id'],
//                  '',
//                  [
//                      'type'  => 'quantity',
//                      'label' => $row['title'],
//                      'min'   => 0,
//                      'max'   => 30,
//                      'data'  => [
//                          'price' => $row['price'],
//                          'id'    => $row['id'],
//                          'sku'   => $row['sku'],
//                          'name'  => $row['title'],
//                          'add-to-cart'   => ''
//                      ],
//                      'add'   => 'Add 1 '.$row['title'].' to cart',
//                      'remove'    => 'Remove 1 '.$row['title']. ' from cart',
//                  ]
//              );
                $out .= '</div>';
            }
        } else {
            $out .= Form::render(
            $out .= sprintf(
                '<a href="?add-to-cart=%d"
                   data-quantity="1"
                   data-product_id="%d"
                   class="add_to_cart_button ajax_add_to_cart add-to-cart">
                   %s
                    <span>Add to Cart</span>
                </a>',
                $ID,
                '',
                [
                    'type'  => 'quantity',
                    'label' => 'Add to Order',
                    'min'   => 0,
                    'max'   => 30,
                    'step'  => 1,
                    'data'  => [
                        'price' => $fields['_price'],
                        'id'    => $ID,
                        'name'  => $fields['post_title'],
                        'step'  => 1,
                        'add-to-cart'   => '',
                    ],
                    'add'   => 'Add 1 '.$fields['post_title'].' to cart',
                    'remove'    => 'Remove 1 '.$fields['title']. ' from cart',
                ]
                $ID,
                jvbIcon('plus-square')
            );
//          $out .= Form::render(
//              $ID,
//              '',
//              [
//                  'type'  => 'quantity',
//                  'label' => 'Add to Order',
//                  'min'   => 0,
//                  'max'   => 30,
//                  'step'  => 1,
//                  'data'  => [
//                      'price' => $fields['_price'],
//                      'id'    => $ID,
//                      'name'  => $fields['post_title'],
//                      'step'  => 1,
//                      'add-to-cart'   => '',
//                  ],
//                  'add'   => 'Add 1 '.$fields['post_title'].' to cart',
//                  'remove'    => 'Remove 1 '.$fields['title']. ' from cart',
//              ]
//          );
        }
        $out .= '</div></div>';
inc/blocks/SummaryBlock.php
@@ -28,9 +28,7 @@
        $this->cache = Cache::for('summary_block', 'post', WEEK_IN_SECONDS);
        add_action('init', [ $this, 'registerBlock' ]);
        add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
        if (JVB_TESTING) {
            $this->cache->flush();
        }
    }
    public function registerBlock()
@@ -50,21 +48,10 @@
        return (is_tax()) ? 'tax' : 'content';
    }
    protected function generateKey():string
    {
        $obj = get_queried_object();
        return match (true) {
            is_tax() => jvbNoBase($obj->taxonomy) . $obj->term_id,
            is_a($obj, 'WP_Post_type') => jvbNoBase($obj->name),
            is_a($obj, 'WP_User') => jvbUserRole($obj->ID),
            default => jvbNoBase($obj->post_type) . $obj->ID,
        };
    }
    public function render(array $attributes, string $content, WP_Block $block)
    {
        $this->config = $this->getConfig();
        $key = $this->generateKey();
        if (is_tax()) {
            $this->cache = Cache::for('summary_tax', 'term', WEEK_IN_SECONDS);
        } elseif (is_author()) {
@@ -138,7 +125,7 @@
        ob_start();
        $this->renderBlock();
        $content = ob_get_clean();
        $this->cache->set($key, $content);
        $this->cache->set(get_queried_object_id(), $content);
        return $content;
    }
inc/blocks/TimelineBlock.php
@@ -4,6 +4,7 @@
use JVBase\managers\Cache;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
use JVBase\rest\routes\ContentRoutes;
use WP_Block;
if (!defined('ABSPATH')) {
@@ -86,7 +87,7 @@
    protected function renderHeader():void
    {
        $meta = Meta::forPost($this->parentID);
        $sharedFields = JVB()->routes('content')->getTimelineSharedFields($this->content);
        $sharedFields = ContentRoutes::getInstance()->getTimelineSharedFields($this->content);
        $fields = $meta->getAll($sharedFields);
        $extra = $meta->getAll();
        ?>
@@ -170,7 +171,7 @@
    {
        $all = $this->children;
        array_unshift($all, $this->parentID);
        $uniqueFields = JVB()->routes('content')->getTimelineUniqueFields($this->content);
        $uniqueFields = ContentRoutes::getInstance()->getTimelineUniqueFields($this->content);
        foreach ($all as $i => $ID) {
            $meta = Meta::forPost($ID);
inc/forms/PostSelector.php
@@ -185,8 +185,8 @@
        }
        // Check cache
        $cache_key = $this->cache->generateKey($args);
        $cached_result = $this->cache->get($cache_key);
        $cached_result = $this->cache->get($args);
        if ($cached_result) {
            return new WP_REST_Response($cached_result);
        }
@@ -224,7 +224,7 @@
        ];
        // Cache the result
        $this->cache->set($cache_key, $result, 300); // 5 minute cache
        $this->cache->set($args, $result, 300); // 5 minute cache
        return new WP_REST_Response($result);
    }
inc/helpers/renderFields.php
@@ -603,8 +603,8 @@
    }
    $cache = Cache::for('tax_locations', 'term');
    $key = $cache->generateKey($location);
    $cached = $cache->get($location);
    $cached = false;
    if ($cached) {
        return $cached;
@@ -636,7 +636,7 @@
    );
    $cached = '<p class="find-us">'.jvbIcon('map-pin').'Find us here: '.$google.$apple.'</p>';
    $cache->set($key, $cached);
    $cache->set($location, $cached);
    return $cached;
}
inc/integrations/ServiceMap.php
@@ -26,6 +26,15 @@
            : false;
    }
    public function getServiceUser(string $service, ?int $userID = null):Integrations|false
    {
        $service = $this->getService($service);
        if ($service->userID !== $userID && method_exists($service, 'switchUser')) {
            $service->switchUser($userID);
        }
        return $service;
    }
    public function getAvailableServices():array
    {
        return array_filter(array_keys($this->serviceMap), function ($service) {
inc/integrations/_Base.php
@@ -2,6 +2,7 @@
namespace JVBase\integrations;
use JVBase\managers\Cache;
use JVBase\managers\Errors;
use JVBase\managers\IconsManager;
if (!defined('ABSPATH')) {
@@ -108,7 +109,7 @@
    protected function logError(string $method, string $message, array $context = []):void
    {
        $context['service'] = $this->service_name;
        JVB()->error()->log('['.self::class.']'.$method, $message, $context);
        Errors::getInstance()->log('['.self::class.']'.$method, $message, $context);
    }
    protected function getReturnURL():string
inc/integrations/services/GoogleMapsOld.php
@@ -202,7 +202,7 @@
            'key' => $this->private_key
        ];
        $key = $this->cache->generateKey(['geocode', $address]);
        $key =['geocode', $address];
        $cached = $this->cache->get($key);
        if ($cached) {
            return $cached;
@@ -256,7 +256,7 @@
            'key' => $this->private_key
        ];
        $key = $this->cache->generateKey(['reverse', $lat, $lng]);
        $key = ['reverse', $lat, $lng];
        $cached = $this->cache->get($key);
        if ($cached) {
            return $cached;
@@ -302,7 +302,7 @@
        $default_fields = ['name', 'formatted_address', 'geometry', 'rating', 'formatted_phone_number'];
        $fields = empty($fields) ? $default_fields : $fields;
        $key = $this->cache->generateKey(['place', $place_id, $fields]);
        $key = ['place', $place_id, $fields];
        $cached = $this->cache->get($key);
        if ($cached) {
            return $cached;
@@ -355,7 +355,7 @@
            $params['type'] = $type;
        }
        $key = $this->cache->generateKey(['nearby', $lat, $lng, $radius, $type]);
        $key = ['nearby', $lat, $lng, $radius, $type];
        $cached = $this->cache->get($key);
        if ($cached) {
            return $cached;
@@ -406,7 +406,7 @@
            'key' => $this->private_key
        ];
        $key = $this->cache->generateKey(['distance', $origin_str, $dest_str, $mode]);
        $key = ['distance', $origin_str, $dest_str, $mode];
        $cached = $this->cache->get($key);
        if ($cached) {
            return $cached;
inc/integrations/services/GoogleMyBusiness.php
@@ -9,7 +9,9 @@
use JVBase\integrations\OAuthURLs;
use JVBase\integrations\Requests;
use JVBase\integrations\SyncTo;
use JVBase\integrations\SyncToPost;
use JVBase\integrations\UserConnection;
use WP_Error;
if (!defined('ABSPATH')) {
@@ -18,10 +20,10 @@
class GoogleMyBusiness extends Integrations
{
    use Admin, Instance, OAuth, Requests, UserConnection, SyncTo;
    use Admin, Instance, OAuth, Requests, UserConnection, SyncToPost;
    protected string $readMask = 'name,title,storefrontAddress,metadata,openInfo,storeCode,categories,phoneNumbers,labels,specialHours';
    protected string $businessV4;
    protected function __construct()
    {
        $this->service_name = 'gmb';
@@ -35,6 +37,9 @@
        ];
        $this->supportsWebp = false;
        $this->baseUrl = 'https://mybusinessbusinessinformation.googleapis.com';
        $this->businessV4 = 'https://mybusiness.googleapis.com/v4';
        $this->defineFields();
        $this->defineInstructions();
        $this->defineOAuth();
@@ -140,7 +145,12 @@
    protected function getRequestHeaders(): array
    {
        // TODO: Implement getRequestHeaders() method.
        return [
            'Authorization' => 'Bearer ' . $this->getAccessToken(),
            'Content-Type' => 'application/json',
            'Accept' => 'application/json'
        ];
    }
    protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array
@@ -151,14 +161,16 @@
        return match($endpoint) {
            'https://mybusinessaccountmanagement.googleapis.com/v1/accounts' => $this->handleGetAccountsResponse($response, $method),
            sprintf('%s/%s/%s/reviews', $this->businessV4, $this->getAccountID()??'', $this->getLocationID()??'') => $this->handleGetReviewResponse($response, $method),
            sprintf('https://mybusinessbusinessinformation.googleapis.com/v1/%s/locations', $this->getAccountID()??'') => $this->handleGetLocationsResponse($response, $method),
            sprintf('%s/v1/%s', $this->baseUrl,$this->getLocationID()??'') => $this->handleGetLocationResponse($response, $method),
            default => $this->response(false, 'Unhandled response for '.$endpoint.' endpoint, with method: '.$method)
        };
    }
    protected function refreshAccessToken(OAuthCredentials $credentials): array
    {
        // TODO: Implement refreshAccessToken() method.
    }
    /*********************************************************
@@ -177,6 +189,31 @@
        return $stored;
    }
    protected function getLocation(bool $force = false):?array
    {
        $location_name = $this->getLocationID();
        if (empty($location_name)) {
            return null;
        }
        $params = [
            'readMask'  => $this->readMask
        ];
        return $this->getRequest(
            sprintf(
                '%s/v1/%s',
                $this->baseUrl,
                $location_name,
            ),
            $params,
            $force
        );
    }
        protected function handleGetLocationResponse(WP_Error|array $response, string $method):?array
        {
            return $response??null;
        }
    protected function getLocationID():string|false
    {
        return $this->loadCredentials()['location_id']??false;
@@ -254,11 +291,323 @@
    protected function setContentTypes(): void
    {
        // TODO: Implement setContentTypes() method.
        $this->contentTypes = [
            'menu_item' => $this->buildMenuItemContentType(),
            'post'      => $this->buildPostContentType(),
            'event'     => $this->buildEventContentType(),
            'offer'     => $this->buildOfferContentType(),
        ];
    }
        protected function buildMenuItemContentType():array
        {
            return [
                'nutritionFacts'    => [
                    'type'  => 'repeater',
                    'label' => 'Nutrition',
                    'fields'    => [
                        'name'  => [
                            'type'  => 'select',
                            'label' => 'Name',
                            'options'   => [
                                'calories' => 'Calories',
                                'totalFat'  => 'Total Fat',
                                'cholesterol' => 'Cholesterol',
                                'sodium'    => 'Sodium',
                                'totalCarbohydrate' => 'Total Carbohydrates',
                                'protein'   => 'Protein'
                            ]
                        ],
                        'amount'    => [
                            'type'  => 'number',
                            'label' => 'Amount',
                        ],
                        'unit'  => [
                            'type' => 'select',
                            'label' => 'Unit',
                            'options'   => [
                                'MILLIGRAM' => 'Milligram',
                                'GRAM'  => 'Gram',
                                'CALORIE'   => 'Calorie'
                            ]
                        ]
                    ],
                    'description'   => 'Recommended. Example: Total Fat: 3g',
                    'section'   => 'gmb',
                ],
                'servesNumPeople'   => [
                    'type'  => 'number',
                    'label' => 'Number of People this Serves',
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'portionSize'   => [
                    'type'  => 'text',
                    'label' => 'Portion size',
                    'description' => 'Example: 8-piece of nuggets',
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'preparationMethods' => [
                    'type'  => 'textarea',
                    'label' => 'Preparation Methods',
                    'description'   => 'Specific methods that the food can be prepared in',
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'cuisines'  => [
                    'type'  => 'text',
                    'label' => 'Cuisines',
                    'description'   => 'Recommended. The specific cuisine of the food item',
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'spiciness' => [
                    'type'  => 'select',
                    'label' => 'Spiciness',
                    'options'   => [
                        'none'  => 'None',
                        'mild'  => 'Mild',
                        'medium'    => 'Medium',
                        'hot'   => 'Hot',
                    ],
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'allergen'  => [
                    'type'  => 'checkbox',
                    'label' => 'Allergens',
                    'options'   => [
                        'dairy' => 'Dairy',
                        'egg'   => 'Egg',
                        'fish'  => 'Fish',
                        'peanut'    => 'Peanut',
                        'shellfish' => 'Shellfish',
                        'soy'       => 'Soy',
                        'tree nut'  => 'Tree Nut',
                        'wheat'     => 'Wheat'
                    ],
                    'description'   => 'Recommended',
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ],
                'dietaryRestriction'    => [
                    'type'  => 'checkbox',
                    'label' => 'Dietary Restrictions',
                    'description' => 'Recommended.',
                    'options'   => [
                        'halal' => 'Halal',
                        'kosher'    => 'Kosher',
                        'organic'   => 'Organic',
                        'vegan'     => 'Vegan',
                        'vegetarian'    => 'Vegetarian',
                        'gluten-free'   => 'Gluten Free'
                    ],
                    'bulkEdit'  => true,
                    'section'   => 'gmb',
                ]
            ];
        }
        protected function buildPostContentType():array
        {
            return [
                'summary' => [
                    'type' => 'textarea',
                    'label' => 'Post Summary',
                    'required' => true
                ],
                'language_code' => [
                    'type' => 'text',
                    'label' => 'Language',
                    'default' => 'en-CA'
                ],
                'media' => [
                    'type' => 'image',
                    'label' => 'Photo',
                ],
                'call_to_action' => [
                    'type' => 'group',
                    'fields' => [
                        'type' => [
                            'type' => 'select',
                            'options' =>
                                [
                                    'BOOK' => 'Book Now',
                                    'ORDER' => 'Order Now',
                                    'SHOP' => 'Shop Now',
                                    'LEARN_MORE' => 'Learn More',
                                    'SIGN_UP' => 'Sign up',
                                    'CALL' => 'Call'
                                ]
                        ],
                        'url' => [
                            'type' => 'url',
                            'label' => 'Action URL'
                        ]
                    ]
                ]
            ];
        }
        protected function buildEventContentType():array
        {
            return [
                'summary' => [
                    'type' => 'textarea',
                    'label' => 'Event Description',
                    'required' => true
                ],
                'event' => [
                    'title' => [
                        'type' => 'text',
                        'label' => 'Event Title',
                        'required' => true
                    ],
                    'start_date' => [
                        'type' => 'date',
                        'label' => 'Start Date',
                        'required' => true
                    ],
                    'end_date' => [
                        'type' => 'date',
                        'label' => 'End Date'
                    ],
                    'start_time' => [
                        'type' => 'time',
                        'label' => 'Start Time'
                    ],
                    'end_time' => [
                        'type' => 'time',
                        'label' => 'End Time'
                    ]
                ],
                'media' => [
                    'type' => 'image',
                    'label' => 'Event Photo'
                ]
            ];
        }
        protected function buildOfferContentType():array
        {
            return [
                'summary' => [
                    'type' => 'textarea',
                    'label' => 'Offer Description',
                    'required' => true
                ],
                'offer' => [
                    'coupon_code' => [
                        'type' => 'text',
                        'label' => 'Coupon Code'
                    ],
                    'redeem_online_url' => [
                        'type' => 'url',
                        'label' => 'Redemption URL'
                    ],
                    'terms_conditions' => [
                        'type' => 'textarea',
                        'label' => 'Terms & Conditions']
                ],
                'media' => ['type' => 'image', 'label' => 'Offer Photo']
            ];
        }
    protected function formatForService(int $itemID, string $type = 'post'): array
    {
        // TODO: Implement formatForService() method.
    }
    public function getReviews(int $number = 5):array
    {
        if (!$this->getLocationID() || !$this->getAccountID()){
            return [];
        }
        return $this->cache->remember(
            ['reviews', $this->getLocationID(), $number],
            function() use ($number) {
                try {
                    return $this->getRequest(
                        sprintf(
                            "%s/{$this->getAccountID()}/{$this->getLocationID()}/reviews",
                            $this->businessV4
                        ),
                        [
                            'orderBy'   => 'updateTime desc'
                        ],
                    );
                } catch (Exception $e) {
                    $this->logError(
                        'getReviews',
                    'Failed to fetch Reviews',
                    [
                        'error' => $e->getMessage()
                    ]);
                    return [];
                }
            }
        );
    }
        protected function handleGetReviewResponse(WP_Error|array $response, string $method):array
        {
            return $response;
        }
        /**********************************************************************
         * REVIEW UTILITY
        **********************************************************************/
        /**
         * Get the URL to view all Google reviews for the current location
         * @return string|null The reviews viewing URL or null if not available
         */
        public function getReviewsViewUrl():?string
        {
            $location = $this->getLocation();
            if (!$location) {
                return null;
            }
            if (!empty($location['metadata']['mapsUrl']??'')) {
                return $location['metadata']['mapsUrl'];
            }
            if (!empty($location['metadata']['placeId']??'')) {
                return sprintf(
                    'https://search.google.com/local/reviews?placeid=%s',
                    urlencode($location['metadata']['placeId'])
                );
            }
            return null;
        }
    /**
     * Get the URL to leave a review for the current location
     * @return string|null The review URL or null if not available
     */
        public function getReviewUrl():?string
        {
            $location = $this->getLocation();
            if (!$location) {
                return null;
            }
            if (!empty($location['metadata']['placeId']??'')) {
                return sprintf(
                    'https://search.google.com/local/writereview?placeid=%s',
                    urlencode($location['metadata']['placeId'])
                );
            }
            if (!empty($location['metadata']['mapsUrl']??'')) {
                return sprintf(
                    '%s/reviews',
                    $location['metadata']['mapsUrl']
                );
            }
            return null;
        }
    /**************************************************************************
     * FOOD MENU HELPERS
    **************************************************************************/
}
inc/integrations/services/GoogleMyBusinessOld.php
@@ -143,9 +143,7 @@
            ]
        );
        if (JVB_TESTING) {
            $this->cache->flush();
        }
    }
    protected function initialize(): void
inc/integrations/services/InstagramOld.php
@@ -7,6 +7,7 @@
namespace JVBase\integrations\services;
use JVBase\integrations\Integrations;
use JVBase\meta\Meta;
use WP_Error;
if (!defined('ABSPATH')) {
    exit;
@@ -165,14 +166,14 @@
    }
    public function isSetUp():bool {
        $fb = JVB()->connect('facebook');
        $fb = Facebook::getInstance();
        return $fb->isSetUp();
    }
    protected function getOAuthCredentials(): array
    {
        // Get Facebook integration
        $facebook = JVB()->connect('facebook');
        $facebook = Facebook::getInstance();
        if (!$facebook || !$facebook->isSetUp()) {
            return [];
@@ -190,7 +191,7 @@
    public function getCredentials():array {
        $credentials = parent::getCredentials();
        if (empty($credentials)) {
            $facebook = JVB()->connect('facebook');
            $facebook = Facebook::getInstance();
            if (!$facebook || !$facebook->isSetUp()) {
                return [];
inc/integrations/services/UmamiOld.php
@@ -145,10 +145,6 @@
     */
    public function renderTrackingScript(): void
    {
        // Skip on local environments
        if (JVB_TESTING) {
            return;
        }
        if (!$this->isSetUp() || is_admin()) {
            return;
        }
inc/managers/CustomTable.php
@@ -1178,7 +1178,7 @@
    private function logError(string $operation, array $context = []): void
    {
        if (function_exists('JVB')) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                $this->tableName,
                "CustomTable {$operation} failed: " . $this->wpdb->last_error,
                $context,
inc/managers/Dashboard/Dashboard.php
@@ -19,9 +19,7 @@
    private function __construct() {
        self::$based = BASE.$this->slug;
        $this->cache = Cache::for('dashboard', 'post');
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        $this->registerDashboard();
        $this->buildPages();
        $this->buildSections();
inc/managers/Dashboard/DashboardPage.php
@@ -24,10 +24,8 @@
    protected Cache $cache;
    public function __construct(string $title, string $slug = '', string $icon ='', int $parent = 0) {
        $this->cache = Cache::for('dashboard');
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        $this->cache = Cache::for('dash_pages');
        $this->title = $title;
        $this->setSlug(empty($slug) ? $title : $slug);
        $this->icon = $icon;
inc/managers/DashboardManager.php
@@ -28,9 +28,7 @@
    public function __construct()
    {
        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->user();
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerDashboard']);
@@ -964,10 +962,11 @@
        if ($connection !== 'integrations') {
            $userID = (Site::has('has_membership')) ? $this->user->ID : null;
            $integration = JVB()->connect($connection, $userID);
            echo '<h1>Managing '.$integration->title.'</h1>';
            $integration->renderDefaults();
            $integration = $this->getServiceUser($connection, $userID);
            if (method_exists($integration, 'renderDefaults')) {
                echo '<h1>Managing '.$integration->title.'</h1>';
                $integration->renderDefaults();
            }
        } else {
            ?>
            <section class="item-grid integrations">
inc/managers/DirectoryManager.php
@@ -6,6 +6,7 @@
}
use JVBase\Instance;
use JVBase\integrations\services\Umami;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use WP_Block;
@@ -31,9 +32,7 @@
        $this->perPage = $perPage;
        $this->cache = Cache::for('directory', WEEK_IN_SECONDS);
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        jvb_register_do_once('buildDirectories', [$this, 'activate']);
        add_action('init', [$this, 'registerDirectories']);
@@ -680,7 +679,7 @@
    public function renderLettersList(array $list, string $type):string
    {
        $umami = JVB()->connect('umami');
        $umami = Umami::getInstance();
        $out = '<ul class="list-none">';
        foreach ($list as $letter => $items) {
inc/managers/Favourites.php
@@ -812,7 +812,7 @@
                'item_id'   => $post_id
            ])->deleteResults();
        } catch (Exception $e) {
            JVB()->error()->log('cleanupPostFavourites', $e->getMessage(), [
            Errors::getInstance()->log('cleanupPostFavourites', $e->getMessage(), [
                'post_id' => $post_id
            ]);
        }
@@ -835,7 +835,7 @@
                'item_id'   => $term_id
            ])->deleteResults();
        } catch (Exception $e) {
            JVB()->error()->log('cleanupTermFavourites', $e->getMessage(), [
            Errors::getInstance()->log('cleanupTermFavourites', $e->getMessage(), [
                'term_id' => $term_id,
                'taxonomy'=> $taxonomy
            ]);
inc/managers/IconsManager.php
@@ -26,6 +26,15 @@
    protected array $map = [];
    protected const MAX_VERSIONS = 5;
    protected string $timestampKey;
    protected string $iconKey;
    protected string $iconHistoryKey;
    protected static string $allIcons = 'icons_all_used';
    protected static string $updateIcons = 'icons_needs_update';
    protected static string $iconMap = 'icons_map';
    /**
     * Factory method - get or create instance for a source
     */
@@ -34,6 +43,9 @@
        if (!isset(self::$instances[$source])) {
            self::$instances[$source] = new self($source);
        }
        self::$instances[$source]->setTimestampKey(BASE.'icons_'.$source.'_timestamp');
        self::$instances[$source]->setIconKey(BASE.'icons_'.$source);
        self::$instances[$source]->setIconHistoryKey(BASE.'icons_history_'.$source);
        return self::$instances[$source];
    }
@@ -72,6 +84,19 @@
        $this->registerInstanceHooks();
    }
    protected function setIconKey(string $key):void
    {
        $this->iconKey = $key;
    }
    protected function setTimestampKey(string $key):void
    {
        $this->timestampKey = $key;
    }
    protected function setIconHistoryKey(string $key):void
    {
        $this->iconHistoryKey = $key;
    }
    /**
@@ -124,7 +149,7 @@
     */
    protected function loadStoredIcons(): void
    {
        $allIcons = get_option(BASE.'usedIcons', []);
        $allIcons = get_option(BASE.self::$allIcons, []);
        $storedIcons = $allIcons[$this->source] ?? [];
        // Merge stored icons with any existing icons (like custom icons)
@@ -304,20 +329,19 @@
     */
    protected function saveIcons(): void
    {
        $allIcons = [];
        foreach (self::$instances as $source => $instance) {
            $allIcons[$source] = $instance->icons;
        }
        $allIcons = array_map(function ($instance) {
            return $instance->icons;
        }, self::$instances);
        update_option(BASE.'usedIcons', $allIcons);
        update_option(BASE.self::$allIcons, $allIcons);
        // Track WHICH source needs updating
        $needsUpdate = get_option(BASE.'icons_needs_update', []);
        $needsUpdate = get_option(BASE.self::$updateIcons, []);
        if (!is_array($needsUpdate)) {
            $needsUpdate = [];
        }
        $needsUpdate[$this->source] = true;
        update_option(BASE.'icons_needs_update', $needsUpdate);
        update_option(BASE.self::$updateIcons, $needsUpdate);
    }
    /**
@@ -345,7 +369,7 @@
    protected function addMap(): void
    {
        $map = get_option(BASE.'iconMap');
        $map = get_option(BASE.self::$iconMap);
        if (!$map) {
            $map = [
                'seo'   => 'robot'
@@ -362,7 +386,7 @@
            if (Site::hasAnyIntegration()) {
                $map['integrations'] = 'plugs-connected';
            }
            update_option(BASE.'iconMap', $map);
            update_option(BASE.self::$iconMap, $map);
        }
        $this->map = apply_filters('jvbMapIcons', $map);
@@ -404,10 +428,13 @@
    public static function checkCSS(): void
    {
        $needsUpdate = get_option(BASE.'icons_needs_update', []);
        $needsUpdate = get_option(BASE.self::$updateIcons, []);
        $needsUpdate = array_filter($needsUpdate, function ($item) {
            return $item === true;
        });
        if (!empty($needsUpdate)) {
            error_log('Regenerating CSS for sources: ' . implode(', ', array_keys($needsUpdate)));
            delete_option(BASE.'icons_needs_update');
            delete_option(BASE.self::$updateIcons);
            self::regenerateAllCSS($needsUpdate);
        }
    }
@@ -422,7 +449,7 @@
        }
        // Load all icons from database option
        $allIcons = get_option(BASE.'usedIcons', []);
        $allIcons = get_option(BASE.self::$allIcons, []);
        // If no specific sources provided, regenerate all
        if (empty($sourcesToUpdate)) {
@@ -535,7 +562,7 @@
        }
        // Track usage - only if not already tracked
        if (!isset($this->icons[$style])) {
        if (!array_key_exists($style, $this->icons)) {
            $this->icons[$style] = [];
        }
@@ -649,7 +676,7 @@
    public function registerStyle(): void
    {
        $timestamp = Cache::for('icons_'.$this->source)->getTimestamp();
        $timestamp = get_option($this->timestampKey);
        $handle = 'jvb-icons-' . $this->source;
        wp_register_style(
@@ -687,7 +714,7 @@
                }
            }
        }
        update_option($this->timestampKey, time());
        return $this->minifyCss($css);
    }
@@ -743,9 +770,8 @@
     */
    public function clearIconCache(): void
    {
        delete_option(BASE . 'icon_usage_list'); // Legacy
        delete_option(BASE.'usedIcons');
        delete_option(BASE.'iconMap');
        delete_option(self::$allIcons);
        delete_option(self::$iconMap);
        // Clear cache for all sources
        foreach (self::$instances as $source => $instance) {
@@ -777,12 +803,12 @@
            $history = array_slice($history, 0, self::MAX_VERSIONS);
        }
        update_option(BASE.'icon_css_history_' . $this->source, $history);
        update_option($this->iconHistoryKey, $history);
    }
    public function getVersionHistory(): array
    {
        return get_option(BASE.'icon_css_history_' . $this->source, []);
        return get_option($this->iconHistoryKey, []);
    }
    public function restoreVersion(int $timestamp): bool
@@ -818,12 +844,12 @@
    public function forceRefresh(): void
    {
        $this->clearIconCache();
        $needsUpdate = get_option(BASE.'icons_needs_update', []);
        $needsUpdate = get_option(BASE.self::$updateIcons, []);
        if (!is_array($needsUpdate)) {
            $needsUpdate = [];
        }
        $needsUpdate[$this->source] = true;
        update_option(BASE.'icons_needs_update', $needsUpdate);
        update_option(BASE.self::$updateIcons, $needsUpdate);
        Cache::for('icons_'.$this->source)->flush();
    }
@@ -834,7 +860,7 @@
            return false;
        }
        $history = get_option(BASE.'icon_css_history_' . $this->source, []);
        $history = get_option($this->iconHistoryKey, []);
        $merged_icons = [];
        // Collect icons from selected versions
inc/managers/InvitationsManager.php
@@ -390,7 +390,7 @@
        } catch (Exception $e) {
            $this->table->rollback();
            JVB()->error()->log(
            Errors::getInstance()->log(
                'invitation_create',
                'Error processing invitations: ' . $e->getMessage(),
                ['user_id' => $userID],
inc/managers/Login.php
@@ -771,7 +771,7 @@
        if (!Site::hasIntegration('cloudflare')) {
            return;
        }
        JVB()->connect('cloudflare')->renderTurnstile();
        Cloudflare::getInstance()->renderTurnstile();
    }
    protected function maybeTurnstileScripts(): void
inc/managers/MagicLinks.php
@@ -6,6 +6,7 @@
use JVBase\Instance;
use JVBase\integrations\services\Cloudflare;
use JVBase\meta\Form;
use JVBase\rest\routes\LoginRoutes;
use JVBase\ui\Tabs;
use WP_User;
use WP_Error;
@@ -380,7 +381,7 @@
    #[NoReturn] protected function processSignup(array $token_data): void
    {
        if (!array_key_exists('email', $token_data) || !array_key_exists('name', $token_data)) {
            JVB()->error()->log('MagicLinkManager', 'Could not process Signup');
            Errors::getInstance()->log('MagicLinkManager', 'Could not process Signup');
            $this->cleanURL();
        }
        $user_id = wp_create_user(
@@ -427,7 +428,7 @@
    protected function processReferralSignup(array $token_data): void
    {
        if (!array_key_exists('email', $token_data) || !array_key_exists('name', $token_data)) {
            JVB()->error()->log('[MagicLinkManager]Could not process Referral Signup');
            Errors::getInstance()->log('[MagicLinkManager]Could not process Referral Signup');
            return;
        }
@@ -447,13 +448,13 @@
            'role'          => $role
        ]);
        if (!is_wp_error($user_id)) {
            $response = JVB()->routes('login')->login($email, $pass, true);
            if ($response) {
            $response = LoginRoutes::getInstance()->login($email, $pass, true);
            if (!is_wp_error($response)) {
                wp_safe_redirect(home_url('/dash?welcome=1&referral=1'));
                exit;
            }
        } else {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[MagicLinkManager]',
                $user_id->get_error_message(),
                $token_data
@@ -470,7 +471,7 @@
        $user = get_user_by('ID', $token_data['user_id']);
        if (!$user) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                'MagicLinkManager',
                'Invalid user, could not reset password',
            );
inc/managers/OperationQueue.php
@@ -547,7 +547,7 @@
            ];
        } catch (Exception $e) {
            JVB()->error()->log('queue', $e->getMessage(), $data, 'high');
            Errors::getInstance()->log('queue', $e->getMessage(), $data, 'high');
            return new WP_Error('queue_failed', $e->getMessage());
        }
    }
@@ -1029,7 +1029,7 @@
                );
                // Log this as a critical error
                JVB()->error()->log(
                Errors::getInstance()->log(
                    '[OperationQueue]:restartStuckOperations',
                    "Operation failed after maximum retries",
                    [
@@ -1057,7 +1057,7 @@
                );
                // Log the backoff
                JVB()->error()->log(
                Errors::getInstance()->log(
                    '[OperationQueue]:restartStuckOperations',
                    "Operation requeued with exponential backoff",
                    [
@@ -1184,7 +1184,7 @@
            if (!is_array($filterResult) || !isset($filterResult['success'])) {
                // Log invalid filter response and quit early
                $invalid_response = is_object($filterResult) ? get_class($filterResult) : gettype($filterResult);
                JVB()->error()->log(
                Errors::getInstance()->log(
                    '[OperationQueue]:processOperation',
                    "Invalid filter response format - expected array with 'success' key",
                    [
@@ -1348,7 +1348,7 @@
        } catch (Exception $e) {
            // Handle exception...
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[OperationQueue]:processOperation',
                "Exception during operation processing: " . $e->getMessage(),
                [
@@ -1887,7 +1887,7 @@
            return $value;
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[OperationQueue]:getOperationValue',
                'Error retrieving operation value: ' . $e->getMessage(),
                [
inc/managers/Referrals.php
@@ -66,11 +66,6 @@
        $this->cache = Cache::for('referrals', WEEK_IN_SECONDS);
//      $this->requestCache = Cache::for('referral_requests', WEEK_IN_SECONDS)->connect('referrals', true);
        $this->statsCache = Cache::for('referral_stats', WEEK_IN_SECONDS)->connect('referrals', true)->user();
        if (JVB_TESTING) {
            $this->cache->flush();
//          $this->requestCache->flush();
            $this->statsCache->flush();
        }
        $this->referralPage = $this->getReferralPageId();
        $this->settings = $this->getRewardSettings();
@@ -852,7 +847,7 @@
    public function getTopReferrers(int $limit = 10, string $period = 'all'): array
    {
        return $this->statsCache->remember(
            $this->statsCache->generateKey(['per_page' => $limit, 'period' => $period]),
            ['per_page' => $limit, 'period' => $period],
            function() use ($limit, $period) {
                $where = '';
                if ($period !== 'all') {
inc/managers/Responses.php
@@ -157,6 +157,11 @@
                'message'   => 'Response content is empty'
            ];
        }
        $registrar = Registrar::getInstance($type);
        if ($registrar) {
            $type = $registrar->getBased();
        }
        $created = $this->responses->insert([
            'item_id'   => $item_id,
            'user_id'   => $user_id,
@@ -337,15 +342,19 @@
        return $responses;
    }
    public function getAllItemResponses(int $itemID, array $args = [], bool $includeChildren = true):array
    public function getAllItemResponses(int $itemID, array $oldArgs = [], bool $includeChildren = true):array
    {
        $args = wp_parse_args($args, [
        $args = wp_parse_args($oldArgs, [
            'orderby'   => 'created_at',
            'order'     => 'DESC',
            'page'      => 1,
            'per_page'  => 20,
        ]);
        if (array_key_exists('type', $oldArgs) || array_key_exists('content', $oldArgs) || array_key_exists('post_type', $oldArgs)) {
            $args['type'] = jvbCheckBase($oldArgs['type']??$oldArgs['content']??$oldArgs['post_type']);
        }
        $where = [
            'item_id'   => $itemID,
            'parent_id' => null
inc/managers/SEO/render/SchemaOutput.php
@@ -98,9 +98,7 @@
            if (!is_front_page()) {
                $website = SchemaHelper::getInstance()::schema('website');
                if (!empty($website)) {
                    if (JVB_TESTING) {
                        Cache::for('websiteSchema')->flush();
                    }
                    $website = Cache::for('websiteSchema')->remember(
                        'schema',
                        function () {
inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
@@ -1,6 +1,7 @@
<?php
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\integrations\services\GoogleMyBusiness;
use JVBase\managers\SEO\render\Thing\CreativeWork\Review;
use JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating;
use JVBase\managers\SEO\render\Thing\Person\Person;
@@ -169,7 +170,8 @@
    {
        $userID = (int) get_post_meta($meta->id(), BASE.'profile_link', true);
        if ($userID && $userID > 0) {
            $integration = JVB()->connect('gmb', $userID);
            $integration = GoogleMyBusiness::getInstance();
            $integration->switchUser($userID);
            if ($integration && $integration->isSetUp()){
                $fetched = $integration->getReviews(20);
inc/managers/TaxonomyRelationships.php
@@ -197,7 +197,7 @@
    {
        $term_posts = $this->cache->remember(
            $this->cache->generateKey(['id' => $term_id, 'taxonomy' => $taxonomy]),
            ['id' => $term_id, 'taxonomy' => $taxonomy],
            function () use ($term_id, $taxonomy) {
                global $wpdb;
                return $wpdb->get_col($wpdb->prepare(
@@ -212,7 +212,7 @@
        $related_term_posts = $this->cache->remember(
            $this->cache->generateKey(['id' => $related_term_id, 'taxonomy' => $related_taxonomy]),
            ['id' => $related_term_id, 'taxonomy' => $related_taxonomy],
            function () use($related_term_id, $related_taxonomy) {
                global $wpdb;
                return $wpdb->get_col($wpdb->prepare(
inc/managers/UserTermsManager.php
@@ -318,7 +318,7 @@
                'result'    => $results
            ];
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[UserTermsManager]:processOperation',
                "Exception during operation processing: " . $e->getMessage(),
                [
inc/managers/queue/Queue.php
@@ -5,6 +5,7 @@
}
use JVBase\Instance;
use JVBase\managers\Errors;
use JVBase\rest\routes\QueueRoutes;
use WP_Error;
use WP_REST_Request;
@@ -103,7 +104,7 @@
            ];
        } catch (\Exception $e) {
            JVB()->error()->log('queue', $e->getMessage(), $data, 'high');
            Errors::getInstance()->log('queue', $e->getMessage(), $data, 'high');
            return new WP_Error('queue_failed', $e->getMessage());
        }
    }
inc/managers/queue/executors/ContentExecutor.php
@@ -2,6 +2,7 @@
namespace JVBase\managers\queue\executors;
use JVBase\managers\queue\{Executor, Operation, Progress, Result, Storage};
use JVBase\managers\Errors;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
use Exception;
@@ -43,7 +44,7 @@
            };
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[ContentExecutor]:execute',
                $e->getMessage(),
                [
inc/managers/queue/executors/IntegrationExecutor.php
@@ -3,6 +3,9 @@
use JVBase\managers\queue\{Executor, Operation, Progress, Result};
use Exception;
use JVBase\integrations\Integrations;
use JVBase\integrations\ServiceMap;
use JVBase\managers\Errors;
if (!defined('ABSPATH')) {
    exit;
@@ -28,11 +31,8 @@
 */
final class IntegrationExecutor implements Executor
{
    /**
     * Map of integration service names to class identifiers.
     * Populated on first use from JVB()->connect().
     */
    private array $integrationCache = [];
    use ServiceMap;
    public function execute(Operation $operation, Progress $progress): Result
    {
@@ -60,7 +60,7 @@
            };
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[IntegrationExecutor]:execute',
                $e->getMessage(),
                [
@@ -105,7 +105,7 @@
        if ($userId === 0) {
            $userId = null;
        }
        return JVB()->connect($serviceName, $userId);
        return $this->getServiceUser($serviceName, $userId);
    }
    /*****************************************************************
inc/meta/Storage.php
@@ -3,6 +3,7 @@
use Exception;
use JVBase\managers\Cache;
use JVBase\managers\Errors;
if (!defined('ABSPATH')) {
    exit;
@@ -163,14 +164,13 @@
            $this->wpdb->query('COMMIT');
            // Update post modified timestamp
            Cache::invalidateItem($item->objectType, $item->id);
            $this->clearCache($item);
            return true;
        } catch (Exception $e) {
            $this->wpdb->query('ROLLBACK');
            JVB()->error()->log('meta_storage', $e->getMessage(), [
            Errors::getInstance()->log('meta_storage', $e->getMessage(), [
                'item_id' => $item->id,
                'object_type' => $item->objectType,
                'fields' => array_keys($dirty)
@@ -326,7 +326,7 @@
                $results[$meta->item()->id ?? 'options'] = false;
            }
            JVB()->error()->log('meta_storage', 'Bulk save failed: ' . $e->getMessage(), [], 'error');
            Errors::getInstance()->log('meta_storage', 'Bulk save failed: ' . $e->getMessage(), [], 'error');
            return $results;
        }
@@ -609,12 +609,21 @@
    public function clearCache(Item $item): void
    {
        match ($item->objectType) {
            'post' => clean_post_cache($item->id),
            'term' => clean_term_cache($item->id),
            'user', 'integrations' => clean_user_cache($item->id),
            default => null
        };
        switch ($item->objectType) {
            case 'post':
                Cache::clearPost($item->id);
                clean_post_cache($item->id);
                break;
            case 'term':
                Cache::clearTerm($item->id);
                clean_term_cache($item->id);
                break;
            case 'user':
            case 'integrations':
                Cache::clearUser($item->id);
                clean_user_cache($item->id);
                break;
        }
    }
    // â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
inc/registrar/Posts.php
@@ -356,7 +356,7 @@
        }
        if (is_wp_error($registered)) {
            JVB()->error()->log('JVBase\registrar\Posts', 'Could not register post type', $registered->get_error_messages());
            Errors::getInstance()->log('JVBase\registrar\Posts', 'Could not register post type', $registered->get_error_messages());
        }
    }
inc/registrar/Registrar.php
@@ -1070,9 +1070,6 @@
        if ($block['blockName'] !== 'core/post-content') {
            return $content;
        }
        if (JVB_TESTING) {
            Cache::for($this->slug)->flush();
        }
        $per_page = 10;
        $page = $_GET['tp']??1;
@@ -1089,7 +1086,7 @@
            'order'     => 'desc',
        ]);
        $cache = Cache::for($this->slug)->connect('taxonomy');
        $cache = Cache::for($this->slug, 'term');
        $max = $cache->remember(
            'max',
            function () {
@@ -1131,7 +1128,7 @@
        $out = $nav. Cache::for($this->slug)->remember(
            $cache->generateKey(['type' =>'contentArchive', ... $args]),
            ['type' =>'contentArchive', ... $args],
            function() use ($args) {
inc/registrar/config/seo/Schema.php
@@ -158,9 +158,7 @@
        public function outputContentTaxArchiveSchema():array
        {
            $ID = get_the_ID();
            if (JVB_TESTING) {
                $this->cache->flush();
            }
            return $this->cache->remember(
                $ID,
                function() use ($ID) {
@@ -264,13 +262,7 @@
                    $itemList->setItemListElement($itemListItems);
                    $class->setMainEntity($itemList);
                    $schema = $class->outputSchema();
                    if (JVB_TESTING) {
//                      error_log('Generated archive schema: '.print_r($schema, true));
                    }
                    return $schema;
                    return $class->outputSchema();
                }
            );
        }
inc/rest/RegisterRoutes.php
File was deleted
inc/rest/Rest.php
@@ -4,9 +4,11 @@
use JVBase\Instance;
use JVBase\integrations\services\Cloudflare;
use JVBase\managers\Cache;
use JVBase\managers\Errors;
use JVBase\meta\Meta;
use JVBase\registrar\Registrar;
use JVBase\base\Site;
use JVBase\rest\routes\TermRoutes;
use WP_REST_Request;
use WP_REST_Response;
use Exception;
@@ -634,7 +636,7 @@
        ]);
        try {
            JVB()->error()->log('security_audit', $event, $context, 'info');
            Errors::getInstance()->log('security_audit', $event, $context, 'info');
        } catch (Exception $e) {
            error_log("Security Audit: {$event} - " . json_encode($context));
        }
@@ -646,7 +648,7 @@
    protected function logError(string $message, array $context = [], string $severity = 'error'): void
    {
        try {
            JVB()->error()->log(static::class, $message, $context, $severity);
            Errors::getInstance()->log(static::class, $message, $context, $severity);
        } catch (Exception $e) {
            error_log(static::class . " Error: {$message} - " . json_encode($context));
        }
@@ -898,7 +900,7 @@
                            'name'  => $term->name,
                            'slug'  => $term->slug,
                            'parent'    => $term->parent,
                            'path'  => JVB()->routes('term')->getTermPath($term->term_id, $term->name, $taxonomy),
                            'path'  => TermRoutes::getInstance()->getTermPath($term->term_id, $term->name, $taxonomy),
                            'taxonomy'  => jvbNoBase($term->taxonomy),
                            'count' => $term->count,
                        ];
inc/rest/RestRouteManager.php
@@ -3,6 +3,8 @@
use DateTime;
use DateTimeZone;
use JVBase\integrations\services\Cloudflare;
use JVBase\managers\Errors;
use JVBase\registrar\Registrar;
use JVBase\managers\OperationQueue;
use JVBase\managers\Cache;
@@ -208,7 +210,7 @@
    protected function logError(string $message, array $context = [], string $severity = 'error')
    {
        try {
            JVB()->error()->log(
            Errors::getInstance()->log(
                'artist_invitations', // component
                $message,
                $context,
@@ -917,7 +919,7 @@
        // Use existing error handler
        try {
            JVB()->error()->log(
            Errors::getInstance()->log(
                'security_audit',
                $event,
                $context,
@@ -1045,7 +1047,7 @@
    protected function verifyTurnstile(string $token): bool
    {
        if (!Site::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) {
        if (!Site::hasIntegration('cloudflare') || !Cloudflare::getInstance()->isSetUp()) {
            return true;
        }
@@ -1053,6 +1055,6 @@
            return false;
        }
        return JVB()->connect('cloudflare')->verifyTurnstile($token);
        return Cloudflare::getInstance()->verifyTurnstile($token);
    }
}
inc/rest/routes/ContentRoutes.php
@@ -49,9 +49,7 @@
        $this->termCache->user();
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        add_action('init', [$this, 'registerContentExecutors'], 5);
    }
@@ -279,8 +277,8 @@
            $args['s'] = sanitize_text_field($params['search']);
        }
        $key = $this->cache->generateKey($args);
        $cached = $this->cache->get($key);
        $cached = $this->cache->get($args);
        if ($cached) {
            return $cached;
        }
@@ -308,7 +306,7 @@
        ];
        $this->cache->set($key, $data);
        $this->cache->set($args, $data);
        return Response::success($data);
    }
@@ -343,14 +341,14 @@
            $args['s'] = sanitize_text_field($params['search']);
        }
        $key = $this->cache->generateKey($args);
        // Check HTTP cache headers with the specific content type
//      $cache_check = $this->checkHeaders($request, $key);
//      if ($cache_check) {
//          return $cache_check;
//      }
        $cache = $this->cache->get($key);
        $cache = $this->cache->get($args);
        if ($cache) {
            return Response::success($cache);
        }
@@ -389,7 +387,7 @@
        $this->taxonomies = [];
        $data['items'] =array_map([$this, 'prepareTerm'], $terms);
        $this->cache->set($key, $data);
        $this->cache->set($args, $data);
        return Response::success($data);
    }
inc/rest/routes/ContentTermsRoutes.php
@@ -261,9 +261,8 @@
        $status = $request->get_param('status');
        $page = (int)$request->get_param('page');
        $cacheKey = $this->cache->generateKey(compact('termID', 'status', 'page'));
        return $this->cache->remember($cacheKey, function() use ($termID, $status, $page) {
        return $this->cache->remember(compact('termID', 'status', 'page'), function() use ($termID, $status, $page) {
            $perPage = 20;
            $offset = ($page - 1) * $perPage;
@@ -272,7 +271,7 @@
            if ($status === 'active') {
                $query = $query->where(['end_date' => null]);
            } elseif ($status === 'inactive') {
                $query = $query->whereNotNull('end_date');
                $query = $query->where(['end_date'=> ['NOT NULL', null]]);
            }
            $total = $query->countResults();
@@ -334,9 +333,7 @@
        $status = $request->get_param('status');
        $page = (int)$request->get_param('page');
        $cacheKey = $this->cache->generateKey(compact('termID', 'status', 'page'));
        return $this->cache->remember($cacheKey, function() use ($termID, $status, $page) {
        return $this->cache->remember(compact('termID', 'status', 'page'), function() use ($termID, $status, $page) {
            $perPage = 20;
            $offset = ($page - 1) * $perPage;
inc/rest/routes/FeedRoutes.php
@@ -36,9 +36,7 @@
        parent::__construct();
        $this->cache = Cache::for('feed');
        if (JVB_TESTING) {
            $this->cache->flush();
        }
    }
inc/rest/routes/Invitations.php
@@ -88,8 +88,8 @@
        ];
        // Check cache
        $key = $this->cache->generateKey($args);
        if ($cached = $this->cache->get($key)) {
        if ($cached = $this->cache->get($args)) {
            return $this->success($cached);
        }
@@ -99,7 +99,7 @@
            : $this->getUserInvitations($args);
        // Cache result
        $this->cache->set($key, $result);
        $this->cache->set($args, $result);
        return $this->success($result);
    }
@@ -107,7 +107,7 @@
    /**
     * Get invitations for a specific term
     */
    protected function getTermInvitations(array $args): array
    public function getTermInvitations(array $args): array
    {
        // Check permission
        if (!Roles::getInstance()()->isManager($args['user'], $args['to_term'])) {
inc/rest/routes/LoginRoutes.php
@@ -159,15 +159,8 @@
            error_log('[handleLogin]Turnstile succeeded');
        }
        // Attempt authentication
        $user = wp_authenticate($email, $password);
        $user = $this->login($email, $password, $remember);
        if (is_wp_error($user)) {
            $this->auditLog('login_failed', [
                'email' => $email,
                'error' => $user->get_error_code(),
            ]);
            return $this->error(
                $this->getLoginErrorMessage($user),
                $user->get_error_code(),
@@ -175,21 +168,6 @@
            );
        }
        // Set auth cookie
        wp_clear_auth_cookie();
        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID, $remember);
        // Store session fingerprint
        $this->storeSessionFingerprint($user->ID, $request);
        do_action('wp_login', $user->user_login, $user);
        $this->auditLog('login_success', [
            'user_id' => $user->ID,
            'email' => $email,
        ]);
        // Get redirect URL
        $redirect = $this->getRedirectUrl($user, $redirect_to);
@@ -201,6 +179,40 @@
        ]);
    }
    public function login(string $email, string $password, bool $remember, ?WP_REST_Request $request = null):WP_User|WP_Error
    {
        // Attempt authentication
        $user = wp_authenticate($email, $password);
        if (is_wp_error($user)) {
            $this->auditLog('login_failed', [
                'email' => $email,
                'error' => $user->get_error_code(),
            ]);
            return $user;
        }
        // Set auth cookie
        wp_clear_auth_cookie();
        wp_set_current_user($user->ID);
        wp_set_auth_cookie($user->ID, $remember);
        // Store session fingerprint
        if ($request) {
            $this->storeSessionFingerprint($user->ID, $request);
        }
        do_action('wp_login', $user->user_login, $user);
        $this->auditLog('login_success', [
            'user_id' => $user->ID,
            'email' => $email,
        ]);
        return $user;
    }
    /**
     * Handle user registration
     */
@@ -506,87 +518,6 @@
        ]);
    }
    /************************************************************
     * SESSION FINGERPRINTING
     *
     * Detects session hijacking by validating that session hasn't
     * moved to a different device/network. Uses IP class (not full IP)
     * to allow mobile network changes without breaking sessions.
     ************************************************************/
    /**
     * Store session fingerprint for hijacking detection
     */
    protected function storeSessionFingerprint(int $user_id, WP_REST_Request $request): void
    {
        if (!defined('JVB_SESSION_FINGERPRINT') || !JVB_SESSION_FINGERPRINT) {
            return;
        }
        $fingerprint = $this->generateSessionFingerprint($request);
        update_user_meta($user_id, BASE . 'session_fingerprint', $fingerprint);
        update_user_meta($user_id, BASE . 'session_timestamp', time());
    }
    /**
     * Generate session fingerprint for hijacking detection
     */
    protected function generateSessionFingerprint(WP_REST_Request $request): string
    {
        return hash('sha256', implode('|', [
            $request->get_header('User-Agent') ?? '',
            // Use IP class instead of full IP to allow for mobile network changes
            $this->getIPClass(
                $request->get_header('X-Forwarded-For')
                    ?: $request->get_header('X-Real-IP')
                    ?: $_SERVER['REMOTE_ADDR'] ?? ''
            )
        ]));
    }
    /**
     * Get IP class (first 3 octets) for session validation
     * Allows for minor IP changes (common with mobile networks)
     */
    protected function getIPClass(string $ip): string
    {
        $parts = explode('.', $ip);
        return implode('.', array_slice($parts, 0, 3));
    }
    /**
     * Validate session fingerprint against stored value
     */
    protected function validateSessionFingerprint(int $user_id, WP_REST_Request $request): bool
    {
        // Only enforce if enabled in config
        if (!defined('JVB_SESSION_FINGERPRINT') || !JVB_SESSION_FINGERPRINT) {
            return true;
        }
        $stored = get_user_meta($user_id, BASE . 'session_fingerprint', true);
        $current = $this->generateSessionFingerprint($request);
        if (empty($stored)) {
            // First request - store fingerprint
            update_user_meta($user_id, BASE . 'session_fingerprint', $current);
            update_user_meta($user_id, BASE . 'session_timestamp', time());
            return true;
        }
        // Compare using timing-safe comparison
        return hash_equals($stored, $current);
    }
    /**
     * Clear session fingerprint (call on logout)
     */
    protected function clearSessionFingerprint(int $user_id): void
    {
        delete_user_meta($user_id, BASE . 'session_fingerprint');
        delete_user_meta($user_id, BASE . 'session_timestamp');
    }
    /**************************************************************
     * HELPERS
     **************************************************************/
inc/rest/routes/NewsRoutes.php
@@ -108,8 +108,8 @@
    public function getNews(WP_REST_Request $request):WP_REST_Response
    {
        $args = $this->buildQueryArgs($request);
        $key = $this->cache->generateKey($args);
        $cache = $this->cache->get($key);
        $cache = $this->cache->get($args);
        if ($cache) {
             return $this->success($cache);
        }
@@ -125,7 +125,7 @@
            'total_pages'   => $query->max_num_pages
        ];
        $this->cache->set($key, $results);
        $this->cache->set($args, $results);
        return $this->success($results);
    }
@@ -325,7 +325,7 @@
        $artist = jvbContentFromUser($post->post_author);
        $upvotes = (int) get_post_meta($post->ID, BASE.'upvotes', true);
        $downvotes = (int) get_post_meta($post->ID, BASE.'downvotes', true);
        $comments = JVB()->routes('comments')->getItemResponse($post->ID, BASE.'news');
        $comments = ResponseRoutes::getInstance()->getItemResponse($post->ID, ['post_type' => BASE.'news']);
        return [
            'id'            => $post->ID,
inc/rest/routes/QueueRoutes.php
@@ -25,9 +25,7 @@
        parent::__construct();
        $this->cache = Cache::for($this->cacheName, 'custom', $this->cacheTtl)->user();
        if (JVB_TESTING) {
            $this->cache->flush();
        }
    }
    /**
inc/rest/routes/ResponseRoutes.php
@@ -119,7 +119,7 @@
    /**
     * Get responses with karma calculations
     */
    protected function getItemResponse(int $item_id, array $args = []): array|WP_Error
    public function getItemResponse(int $item_id, array $args = []): array|WP_Error
    {
        $responses = Responses::getInstance()->getAllItemResponses($item_id, $args);
        // Format responses
inc/rest/routes/ShopRoutes.php
@@ -3,6 +3,7 @@
use JVBase\JVB;
use JVBase\managers\Emails;
use JVBase\managers\Errors;
use JVBase\managers\ImageGenerator;
use JVBase\managers\UploadManager;
use JVBase\registrar\Registrar;
@@ -164,7 +165,7 @@
        foreach ($setData as $name => $value) {
            if ($name === 'shop') {
                JVB()->routes('shop')->requestShopAdmission($operation->user_id, $value);
                $this->requestShopAdmission($operation->user_id, $value);
                $setData['requested_shop'] = $value;
                unset($setData['shop']);
            }
@@ -1007,7 +1008,7 @@
            return true;
        } catch (Exception $e) {
            $wpdb->query('ROLLBACK');
            JVB()->error()->log(
            Errors::getInstance()->log(
                'shop',
                "Error accepting artist invitation: " . $e->getMessage(),
                [
@@ -1075,7 +1076,7 @@
            return true;
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                'shop',
                "Error declining artist invitation: " . $e->getMessage(),
                [
@@ -1157,7 +1158,7 @@
            return true;
        } catch (Exception $e) {
            $wpdb->query('ROLLBACK');
            JVB()->error()->logError("Error adding artist to shop: " . $e->getMessage(), [
            Errors::getInstance()->logError("Error adding artist to shop: " . $e->getMessage(), [
                'user_id' => $user_id,
                'shop_id' => $shop_id
            ]);
@@ -1241,7 +1242,7 @@
            return true;
        } catch (Exception $e) {
            $wpdb->query('ROLLBACK');
            JVB()->error()->log(
            Errors::getInstance()->log(
                'shop',
                "Error removing artist from shop: " . $e->getMessage(),
                [
inc/rest/routes/TermRoutes.php
@@ -3,6 +3,7 @@
use JVBase\Instance;
use JVBase\managers\Cache;
use JVBase\managers\Errors;
use JVBase\managers\TaxonomyRelationships;
use JVBase\registrar\Registrar;
use JVBase\rest\Rest;
@@ -27,9 +28,7 @@
        $this->cacheName = 'terms';
        parent::__construct();
        $this->cache = Cache::for('terms', 'term');
        if (JVB_TESTING) {
            $this->cache->flush();
        }
        $this->per_page = 20;
@@ -559,7 +558,7 @@
            $membership = Site::membership();
            if ($membership && $membership->has('term_approval')) {
                $approval_routes = JVB()->routes('approvals');
                $approval_routes = ApprovalRoutes::getInstance();
                $request_id = $approval_routes->createTermApprovalRequest(
                    $user_id,
                    $taxonomy,
@@ -602,7 +601,7 @@
            ], 201); // 201 Created
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                'terms',
                'Term creation failed: ' . $e->getMessage(),
                ['user_id' => $user_id, 'taxonomy' => $taxonomy, 'name' => $name]
inc/rest/routes/UploadRoutes.php
@@ -708,7 +708,7 @@
            return $this->queued($ID['operation_id'], 'Files uploaded and posts queued for creation');
        } catch (Exception $e) {
            JVB()->error()->log(
            Errors::getInstance()->log(
                '[UploadRoutes]:handleGroupingRequest',
                $e->getMessage(),
                [
inc/utility/Image.php
@@ -17,18 +17,14 @@
    public function __construct()
    {
        $this->cache = Cache::for('images')->connect('post');
        $this->imgData = Cache::for('imageData')->connect('post');
        if (JVB_TESTING) {
            $this->cache->flush();
            $this->imgData->flush();
        }
        $this->cache = Cache::for('images', 'post');
        $this->imgData = Cache::for('imageData', 'post');
    }
    public function formatImage(int $ID, string $start = 'tiny', string $replace = 'large', bool $addLink = true, ?string $postSlug = null):string
    {
        $return = $this->cache->remember(
            $this->cache->generateKey(['ID' => $ID, 'start' => $start, 'replace' => $replace]),
            ['ID' => $ID, 'start' => $start, 'replace' => $replace],
            function() use ($ID, $start, $replace) {
                // Define size order for progressive enhancement
                $sizeOrder = ['tiny', 'directory-preview', 'thumbnail', 'medium', 'large', 'full'];
@@ -188,9 +184,7 @@
    public static function SVG(string $slug):string
    {
        $cache = Cache::for('svgs');
        if (JVB_TESTING) {
            $cache->flush();
        }
        return $cache->remember(
            $slug,
            function() use ($slug) {
inc/utility/Video.php
@@ -15,12 +15,8 @@
    public function __construct()
    {
        $this->cache = Cache::for('videos')->connect('post');
        $this->vidData = Cache::for('vidData')->connect('post');
        if (JVB_TESTING) {
            $this->cache->flush();
            $this->vidData->flush();
        }
        $this->cache = Cache::for('videos', 'post');
        $this->vidData = Cache::for('vidData', 'post');
    }
    public static function get(int $ID, array $args = []):string
    {
@@ -29,7 +25,7 @@
    public function formatVideo(int $ID, array $args = []):string
    {
        return $this->cache->remember(
            $this->cache->generateKey(array_merge(['id' => $ID], $args)),
            array_merge(['id' => $ID], $args),
            function() use ($ID, $args) {
                $url = wp_get_attachment_url($ID);
                $meta = wp_get_attachment_metadata($ID);
jvb.php
@@ -121,11 +121,7 @@
    add_filter('show_admin_bar', '__return_false');
//}
//if (JVB_TESTING) {
//  error_log('In testing mode...');
//} else {
//  error_log('Not in testing mode...');
//}
const JVB_DIR = WP_PLUGIN_DIR . '/jvb';
define('JVB_URL', plugin_dir_url(__FILE__));
@@ -514,15 +510,15 @@
{
    echo '<div class="screen-reader-text live-region" aria-live="polite" role="status"></div>';
}
/*
add_action('wp_head', 'jvbFrontendBase',1);
function jvbFrontendBase():void
{
//add_action('wp_head', 'jvbFrontendBase',1);
//function jvbFrontendBase():void
//{
//  ?>
<!--    <script type="text/javascript">window.jvbBase = '--><?php //= BASE ?>//';</script>
//  <?php
//}
    <script type="text/javascript">window.jvbBase = '<?= BASE ?>';</script>
    <?php
}
*/
add_action('after_setup_theme', 'jvbImageSize');
@@ -549,23 +545,6 @@
//  return $result;
//}, 10, 3);
add_filter('rest_authentication_errors', function($result) {
    // Don't override existing authentication
    if (is_wp_error($result) || $result === true) {
        return $result;
    }
    // Try to authenticate from cookie
    $cookie_user = wp_validate_auth_cookie('', 'logged_in');
    if ($cookie_user) {
        wp_set_current_user($cookie_user);
        return true;
    }
    return $result;
}, 99);
add_action('wp_footer', 'jvb_back_to_top');
function jvb_back_to_top():void
src/forms/render.php
@@ -1,55 +1,7 @@
<?php
use JVBase\meta\Form;
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}
/**
 * Form Block Render
 *
 * @package Edmonton_Ink
 */
/**
 * Renders the form block on the frontend
 *
 * @param array    $attributes The block attributes.
 * @param string   $content    The block content.
 * @param WP_Block $block      The block instance.
 * @return string The rendered output.
 */
function jvbRenderFormBlock(array $attributes, string $content, WP_Block $block):string
{
    // Get form type from attributes
    $form_type = isset($attributes['formType']) ? $attributes['formType'] : '';
    // If no form type selected, return a message
    if (empty($form_type)) {
        return '<div class="jvb-form-error">No form type selected. Please edit this block and select a form type.</div>';
    }
    // Get other attributes
    $show_labels = isset($attributes['showLabels']) ? $attributes['showLabels'] : true;
    $custom_email_to = isset($attributes['customEmailTo']) ? $attributes['customEmailTo'] : '';
    // Set custom options for the form
    $form_options = array();
    // Set custom email recipient if provided
    if (!empty($custom_email_to)) {
        $form_options['email_to'] = $custom_email_to;
    }
    // Render the form with the specified options
    $form_output = JVB()->forms()->renderForm($form_type);
    // Get block classes
    $wrapper_attributes = get_block_wrapper_attributes([
        'class' => 'jvb-forms'
    ]);
    // Return the wrapped form
    return sprintf(
        '<div %1$s>%2$s</div>',
        $wrapper_attributes,
        $form_output
    );
}
templates/dashboard/sections/favourites.php
@@ -1,4 +1,7 @@
<?php
use JVBase\managers\Favourites;
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}
@@ -8,10 +11,11 @@
}
$user_id = get_current_user_id();
$JVB = JVB();
$favourites = $JVB->routes('favourites');
$counts =$favourites->getFavouriteCounts($user_id, false);
$lists =get_user_meta($user_id, BASE.'favourite_lists', true)?:[];
$favourites = Favourites::getInstance();
$counts =$favourites->getFavouriteCounts($user_id);
$lists = $favourites->getLists(['user_id' => $user_id]);
global $types;
global $feed_types;