=Extra JVB() calls replaced, minor tweaks
1 files deleted
60 files modified
| | |
| | | |
| | | 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) { |
| | |
| | | |
| | | if (!static::$currentLoop) { |
| | | |
| | | if (JVB_TESTING) { |
| | | jvbDump('No loop stored'); |
| | | } |
| | | return $content; |
| | | } |
| | | if (static::$currentLoop->have_posts()) { |
| | |
| | | ); |
| | | |
| | | } |
| | | if (JVB_TESTING) { |
| | | jvbDump('Could not create template part block for '.$block['blockName']); |
| | | } |
| | | |
| | | return $content; |
| | | |
| | | // $isHeaderTemplate = ( |
| | |
| | | ); |
| | | } |
| | | return $classes; |
| | | case 'anchor': |
| | | //TODO: What's this doing? |
| | | return ''; |
| | | default: |
| | | if (JVB_TESTING && !is_admin() &&!in_array($key, $this->ignore)) { |
| | | // TESTING |
| | |
| | | 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)) { |
| | |
| | | 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)); |
| | | } |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | $cache = Cache::for('drawer'); |
| | | if (JVB_TESTING) { |
| | | $cache->flush(); |
| | | } |
| | | |
| | | |
| | | if (!is_front_page()) { |
| | | $menu_items[] = [ |
| | |
| | | |
| | | 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']); |
| | | } |
| | | |
| | |
| | | { |
| | | // Initialize cache with connections |
| | | $this->cache = Cache::for('feed_block', WEEK_IN_SECONDS); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | |
| | | add_action('init', [$this, 'registerBlock']); |
| | | } |
| | |
| | | |
| | | $this->config = $this->buildParams($attributes); |
| | | return $this->cache->remember( |
| | | $this->cache->generateKey($this->config), |
| | | $this->config, |
| | | function() { |
| | | return $this->renderBlock(); |
| | | } |
| | |
| | | { |
| | | $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', ''); |
| | |
| | | <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>'; |
| | |
| | | $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() |
| | |
| | | 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()) { |
| | |
| | | ob_start(); |
| | | $this->renderBlock(); |
| | | $content = ob_get_clean(); |
| | | $this->cache->set($key, $content); |
| | | $this->cache->set(get_queried_object_id(), $content); |
| | | return $content; |
| | | } |
| | | |
| | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\routes\ContentRoutes; |
| | | use WP_Block; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | 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(); |
| | | ?> |
| | |
| | | { |
| | | $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); |
| | |
| | | } |
| | | |
| | | // 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); |
| | | } |
| | |
| | | ]; |
| | | |
| | | // 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); |
| | | } |
| | |
| | | } |
| | | |
| | | $cache = Cache::for('tax_locations', 'term'); |
| | | $key = $cache->generateKey($location); |
| | | |
| | | $cached = $cache->get($location); |
| | | $cached = false; |
| | | if ($cached) { |
| | | return $cached; |
| | |
| | | ); |
| | | |
| | | $cached = '<p class="find-us">'.jvbIcon('map-pin').'Find us here: '.$google.$apple.'</p>'; |
| | | $cache->set($key, $cached); |
| | | $cache->set($location, $cached); |
| | | |
| | | return $cached; |
| | | } |
| | |
| | | : 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) { |
| | |
| | | namespace JVBase\integrations; |
| | | |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\IconsManager; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | 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 |
| | |
| | | 'key' => $this->private_key |
| | | ]; |
| | | |
| | | $key = $this->cache->generateKey(['geocode', $address]); |
| | | $key =['geocode', $address]; |
| | | $cached = $this->cache->get($key); |
| | | if ($cached) { |
| | | return $cached; |
| | |
| | | 'key' => $this->private_key |
| | | ]; |
| | | |
| | | $key = $this->cache->generateKey(['reverse', $lat, $lng]); |
| | | $key = ['reverse', $lat, $lng]; |
| | | $cached = $this->cache->get($key); |
| | | if ($cached) { |
| | | return $cached; |
| | |
| | | $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; |
| | |
| | | $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; |
| | |
| | | '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; |
| | |
| | | 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')) { |
| | |
| | | |
| | | 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'; |
| | |
| | | ]; |
| | | $this->supportsWebp = false; |
| | | |
| | | $this->baseUrl = 'https://mybusinessbusinessinformation.googleapis.com'; |
| | | $this->businessV4 = 'https://mybusiness.googleapis.com/v4'; |
| | | |
| | | $this->defineFields(); |
| | | $this->defineInstructions(); |
| | | $this->defineOAuth(); |
| | |
| | | |
| | | 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 |
| | |
| | | |
| | | 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. |
| | | |
| | | } |
| | | |
| | | /********************************************************* |
| | |
| | | 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; |
| | |
| | | |
| | | 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 |
| | | **************************************************************************/ |
| | | } |
| | |
| | | ] |
| | | ); |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | } |
| | | |
| | | protected function initialize(): void |
| | |
| | | namespace JVBase\integrations\services; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | } |
| | | |
| | | 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 []; |
| | |
| | | public function getCredentials():array { |
| | | $credentials = parent::getCredentials(); |
| | | if (empty($credentials)) { |
| | | $facebook = JVB()->connect('facebook'); |
| | | $facebook = Facebook::getInstance(); |
| | | |
| | | if (!$facebook || !$facebook->isSetUp()) { |
| | | return []; |
| | |
| | | */ |
| | | public function renderTrackingScript(): void |
| | | { |
| | | // Skip on local environments |
| | | if (JVB_TESTING) { |
| | | return; |
| | | } |
| | | if (!$this->isSetUp() || is_admin()) { |
| | | return; |
| | | } |
| | |
| | | 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, |
| | |
| | | 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(); |
| | |
| | | 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; |
| | |
| | | public function __construct() |
| | | { |
| | | $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->user(); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | |
| | | add_action('init', [$this, 'registerDashboard']); |
| | | |
| | |
| | | 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"> |
| | |
| | | } |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\services\Umami; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\base\Site; |
| | | use WP_Block; |
| | |
| | | $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']); |
| | |
| | | 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) { |
| | |
| | | 'item_id' => $post_id |
| | | ])->deleteResults(); |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log('cleanupPostFavourites', $e->getMessage(), [ |
| | | Errors::getInstance()->log('cleanupPostFavourites', $e->getMessage(), [ |
| | | 'post_id' => $post_id |
| | | ]); |
| | | } |
| | |
| | | '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 |
| | | ]); |
| | |
| | | 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 |
| | | */ |
| | |
| | | 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]; |
| | | } |
| | | |
| | |
| | | $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; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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) |
| | |
| | | */ |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | protected function addMap(): void |
| | | { |
| | | $map = get_option(BASE.'iconMap'); |
| | | $map = get_option(BASE.self::$iconMap); |
| | | if (!$map) { |
| | | $map = [ |
| | | 'seo' => 'robot' |
| | |
| | | if (Site::hasAnyIntegration()) { |
| | | $map['integrations'] = 'plugs-connected'; |
| | | } |
| | | update_option(BASE.'iconMap', $map); |
| | | update_option(BASE.self::$iconMap, $map); |
| | | } |
| | | |
| | | $this->map = apply_filters('jvbMapIcons', $map); |
| | |
| | | |
| | | 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); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | // 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)) { |
| | |
| | | } |
| | | |
| | | // Track usage - only if not already tracked |
| | | if (!isset($this->icons[$style])) { |
| | | if (!array_key_exists($style, $this->icons)) { |
| | | $this->icons[$style] = []; |
| | | } |
| | | |
| | |
| | | |
| | | public function registerStyle(): void |
| | | { |
| | | $timestamp = Cache::for('icons_'.$this->source)->getTimestamp(); |
| | | $timestamp = get_option($this->timestampKey); |
| | | $handle = 'jvb-icons-' . $this->source; |
| | | |
| | | wp_register_style( |
| | |
| | | } |
| | | } |
| | | } |
| | | |
| | | update_option($this->timestampKey, time()); |
| | | return $this->minifyCss($css); |
| | | } |
| | | |
| | |
| | | */ |
| | | 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) { |
| | |
| | | $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 |
| | |
| | | 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(); |
| | | } |
| | |
| | | return false; |
| | | } |
| | | |
| | | $history = get_option(BASE.'icon_css_history_' . $this->source, []); |
| | | $history = get_option($this->iconHistoryKey, []); |
| | | $merged_icons = []; |
| | | |
| | | // Collect icons from selected versions |
| | |
| | | } catch (Exception $e) { |
| | | $this->table->rollback(); |
| | | |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'invitation_create', |
| | | 'Error processing invitations: ' . $e->getMessage(), |
| | | ['user_id' => $userID], |
| | |
| | | if (!Site::hasIntegration('cloudflare')) { |
| | | return; |
| | | } |
| | | JVB()->connect('cloudflare')->renderTurnstile(); |
| | | Cloudflare::getInstance()->renderTurnstile(); |
| | | } |
| | | |
| | | protected function maybeTurnstileScripts(): void |
| | |
| | | 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; |
| | |
| | | #[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( |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | '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 |
| | |
| | | $user = get_user_by('ID', $token_data['user_id']); |
| | | |
| | | if (!$user) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'MagicLinkManager', |
| | | 'Invalid user, could not reset password', |
| | | ); |
| | |
| | | ]; |
| | | |
| | | } 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()); |
| | | } |
| | | } |
| | |
| | | ); |
| | | |
| | | // Log this as a critical error |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[OperationQueue]:restartStuckOperations', |
| | | "Operation failed after maximum retries", |
| | | [ |
| | |
| | | ); |
| | | |
| | | // Log the backoff |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[OperationQueue]:restartStuckOperations', |
| | | "Operation requeued with exponential backoff", |
| | | [ |
| | |
| | | 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", |
| | | [ |
| | |
| | | |
| | | } catch (Exception $e) { |
| | | // Handle exception... |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[OperationQueue]:processOperation', |
| | | "Exception during operation processing: " . $e->getMessage(), |
| | | [ |
| | |
| | | |
| | | return $value; |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[OperationQueue]:getOperationValue', |
| | | 'Error retrieving operation value: ' . $e->getMessage(), |
| | | [ |
| | |
| | | $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(); |
| | |
| | | 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') { |
| | |
| | | '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, |
| | |
| | | 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 |
| | |
| | | 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 () { |
| | |
| | | <?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; |
| | |
| | | { |
| | | $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); |
| | | |
| | |
| | | { |
| | | |
| | | $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( |
| | |
| | | |
| | | |
| | | $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( |
| | |
| | | 'result' => $results |
| | | ]; |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[UserTermsManager]:processOperation', |
| | | "Exception during operation processing: " . $e->getMessage(), |
| | | [ |
| | |
| | | } |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\rest\routes\QueueRoutes; |
| | | use WP_Error; |
| | | use WP_REST_Request; |
| | |
| | | ]; |
| | | |
| | | } 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()); |
| | | } |
| | | } |
| | |
| | | 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; |
| | |
| | | }; |
| | | |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[ContentExecutor]:execute', |
| | | $e->getMessage(), |
| | | [ |
| | |
| | | |
| | | 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; |
| | |
| | | */ |
| | | 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 |
| | | { |
| | |
| | | }; |
| | | |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | '[IntegrationExecutor]:execute', |
| | | $e->getMessage(), |
| | | [ |
| | |
| | | if ($userId === 0) { |
| | | $userId = null; |
| | | } |
| | | return JVB()->connect($serviceName, $userId); |
| | | return $this->getServiceUser($serviceName, $userId); |
| | | } |
| | | |
| | | /***************************************************************** |
| | |
| | | |
| | | use Exception; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | $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) |
| | |
| | | $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; |
| | | } |
| | |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | | // ───────────────────────────────────────────────────────────── |
| | |
| | | } |
| | | |
| | | 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()); |
| | | } |
| | | } |
| | | |
| | |
| | | if ($block['blockName'] !== 'core/post-content') { |
| | | return $content; |
| | | } |
| | | if (JVB_TESTING) { |
| | | Cache::for($this->slug)->flush(); |
| | | } |
| | | |
| | | $per_page = 10; |
| | | $page = $_GET['tp']??1; |
| | |
| | | 'order' => 'desc', |
| | | ]); |
| | | |
| | | $cache = Cache::for($this->slug)->connect('taxonomy'); |
| | | $cache = Cache::for($this->slug, 'term'); |
| | | $max = $cache->remember( |
| | | 'max', |
| | | function () { |
| | |
| | | |
| | | |
| | | $out = $nav. Cache::for($this->slug)->remember( |
| | | $cache->generateKey(['type' =>'contentArchive', ... $args]), |
| | | ['type' =>'contentArchive', ... $args], |
| | | function() use ($args) { |
| | | |
| | | |
| | |
| | | public function outputContentTaxArchiveSchema():array |
| | | { |
| | | $ID = get_the_ID(); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | return $this->cache->remember( |
| | | $ID, |
| | | function() use ($ID) { |
| | |
| | | $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(); |
| | | } |
| | | ); |
| | | } |
| | |
| | | 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; |
| | |
| | | ]); |
| | | |
| | | 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)); |
| | | } |
| | |
| | | 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)); |
| | | } |
| | |
| | | '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, |
| | | ]; |
| | |
| | | |
| | | 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; |
| | |
| | | protected function logError(string $message, array $context = [], string $severity = 'error') |
| | | { |
| | | try { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'artist_invitations', // component |
| | | $message, |
| | | $context, |
| | |
| | | |
| | | // Use existing error handler |
| | | try { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'security_audit', |
| | | $event, |
| | | $context, |
| | |
| | | |
| | | protected function verifyTurnstile(string $token): bool |
| | | { |
| | | if (!Site::hasIntegration('cloudflare') || !JVB()->connect('cloudflare')->isSetUp()) { |
| | | if (!Site::hasIntegration('cloudflare') || !Cloudflare::getInstance()->isSetUp()) { |
| | | return true; |
| | | } |
| | | |
| | |
| | | return false; |
| | | } |
| | | |
| | | return JVB()->connect('cloudflare')->verifyTurnstile($token); |
| | | return Cloudflare::getInstance()->verifyTurnstile($token); |
| | | } |
| | | } |
| | |
| | | $this->termCache->user(); |
| | | |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | add_action('init', [$this, 'registerContentExecutors'], 5); |
| | | } |
| | | |
| | |
| | | $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; |
| | | } |
| | |
| | | ]; |
| | | |
| | | |
| | | $this->cache->set($key, $data); |
| | | $this->cache->set($args, $data); |
| | | |
| | | return Response::success($data); |
| | | } |
| | |
| | | $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); |
| | | } |
| | |
| | | $this->taxonomies = []; |
| | | $data['items'] =array_map([$this, 'prepareTerm'], $terms); |
| | | |
| | | $this->cache->set($key, $data); |
| | | $this->cache->set($args, $data); |
| | | |
| | | return Response::success($data); |
| | | } |
| | |
| | | $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; |
| | | |
| | |
| | | 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(); |
| | |
| | | $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; |
| | | |
| | |
| | | parent::__construct(); |
| | | $this->cache = Cache::for('feed'); |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | |
| | | ]; |
| | | |
| | | // Check cache |
| | | $key = $this->cache->generateKey($args); |
| | | if ($cached = $this->cache->get($key)) { |
| | | |
| | | if ($cached = $this->cache->get($args)) { |
| | | return $this->success($cached); |
| | | } |
| | | |
| | |
| | | : $this->getUserInvitations($args); |
| | | |
| | | // Cache result |
| | | $this->cache->set($key, $result); |
| | | $this->cache->set($args, $result); |
| | | |
| | | return $this->success($result); |
| | | } |
| | |
| | | /** |
| | | * 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'])) { |
| | |
| | | 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(), |
| | |
| | | ); |
| | | } |
| | | |
| | | // 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); |
| | | |
| | |
| | | ]); |
| | | } |
| | | |
| | | 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 |
| | | */ |
| | |
| | | ]); |
| | | } |
| | | |
| | | /************************************************************ |
| | | * 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 |
| | | **************************************************************/ |
| | |
| | | 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); |
| | | } |
| | |
| | | 'total_pages' => $query->max_num_pages |
| | | ]; |
| | | |
| | | $this->cache->set($key, $results); |
| | | $this->cache->set($args, $results); |
| | | |
| | | return $this->success($results); |
| | | } |
| | |
| | | $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, |
| | |
| | | parent::__construct(); |
| | | $this->cache = Cache::for($this->cacheName, 'custom', $this->cacheTtl)->user(); |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | /** |
| | | * 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 |
| | |
| | | |
| | | use JVBase\JVB; |
| | | use JVBase\managers\Emails; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\ImageGenerator; |
| | | use JVBase\managers\UploadManager; |
| | | use JVBase\registrar\Registrar; |
| | |
| | | |
| | | 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']); |
| | | } |
| | |
| | | return true; |
| | | } catch (Exception $e) { |
| | | $wpdb->query('ROLLBACK'); |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'shop', |
| | | "Error accepting artist invitation: " . $e->getMessage(), |
| | | [ |
| | |
| | | |
| | | return true; |
| | | } catch (Exception $e) { |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'shop', |
| | | "Error declining artist invitation: " . $e->getMessage(), |
| | | [ |
| | |
| | | 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 |
| | | ]); |
| | |
| | | return true; |
| | | } catch (Exception $e) { |
| | | $wpdb->query('ROLLBACK'); |
| | | JVB()->error()->log( |
| | | Errors::getInstance()->log( |
| | | 'shop', |
| | | "Error removing artist from shop: " . $e->getMessage(), |
| | | [ |
| | |
| | | |
| | | use JVBase\Instance; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\managers\Errors; |
| | | use JVBase\managers\TaxonomyRelationships; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | |
| | | $this->cacheName = 'terms'; |
| | | parent::__construct(); |
| | | $this->cache = Cache::for('terms', 'term'); |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | |
| | | $this->per_page = 20; |
| | | |
| | |
| | | |
| | | $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, |
| | |
| | | ], 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] |
| | |
| | | |
| | | 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(), |
| | | [ |
| | |
| | | |
| | | 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']; |
| | |
| | | public static function SVG(string $slug):string |
| | | { |
| | | $cache = Cache::for('svgs'); |
| | | if (JVB_TESTING) { |
| | | $cache->flush(); |
| | | } |
| | | |
| | | return $cache->remember( |
| | | $slug, |
| | | function() use ($slug) { |
| | |
| | | |
| | | 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 |
| | | { |
| | |
| | | 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); |
| | |
| | | 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__)); |
| | |
| | | { |
| | | 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'); |
| | |
| | | // 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 |
| | |
| | | <?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 |
| | | ); |
| | | } |
| | |
| | | <?php |
| | | |
| | | use JVBase\managers\Favourites; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | |
| | | } |
| | | |
| | | $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; |