From c68aefb847b09daa0697de7684d3451e2e68ce1e Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 09 Jul 2026 23:10:23 +0000
Subject: [PATCH] =Refactor of Integrations.php to be a bit more useful with a suite of methods for create, update, delete back and forths for integrations where sharing is enabled. Also considering a single instance with a connect method to connect as the site (no user) vs connecting as an individual user - rather than recreating instances for every user.
---
inc/registrar/helpers/AddIntegrationFields.php | 7
JVBase.php | 27
inc/managers/queue/Processor.php | 22
inc/integrations/Square.php | 267 +++----------
base/Site.php | 3
inc/integrations/Integrations.php | 567 +++++++++++++++++++++++++++--
inc/integrations/Facebook.php | 35 -
inc/managers/queue/executors/IntegrationExecutor.php | 25
inc/integrations/Instagram.php | 23
inc/registrar/config/Integration.php | 9
inc/helpers/all.php | 1
assets/js/concise/FormController.js | 2
assets/js/concise/BioManager.js | 2
assets/js/concise/AuthManager.js | 6
inc/integrations/Helcim.php | 63 +-
assets/js/concise/CRUDOld.js | 2
inc/integrations/GoogleMyBusiness.php | 38 -
inc/registrar/Registrar.php | 6
18 files changed, 707 insertions(+), 398 deletions(-)
diff --git a/JVBase.php b/JVBase.php
index 90f903d..e771147 100644
--- a/JVBase.php
+++ b/JVBase.php
@@ -86,7 +86,7 @@
}
- public function __construct()
+ private function __construct()
{
$this->customBlocks = new CustomBlocks();
$this->managers = [
@@ -296,17 +296,20 @@
public function connect(string $service, ?int $userID = null): mixed
{
- if ($userID) {
- if (!$this->userCanConnect($service, $userID)) {
- return null;
- }
-
- if (!array_key_exists($service, $this->integrations)) {
- return null;
- }
- return $this->serviceMap[$service]::getInstance($userID);
- }
- return (array_key_exists($service, $this->integrations)) ? $this->integrations[$service] : null;
+// if ($userID) {
+// if (!$this->userCanConnect($service, $userID)) {
+// return null;
+// }
+//
+// if (!array_key_exists($service, $this->integrations)) {
+// return null;
+// }
+//
+// return $this->serviceMap[$service]::getInstance($userID);
+// }
+ return is_null($userID) ?
+ $this->integrations[$service]??null :
+ (array_key_exists($service, $this->integrations) ? $this->serviceMap[$service]::getInstance($userID) : null);
}
public function userCanConnect(string $service, int $userID): bool
diff --git a/assets/js/concise/AuthManager.js b/assets/js/concise/AuthManager.js
index 29946d2..b8f5c36 100644
--- a/assets/js/concise/AuthManager.js
+++ b/assets/js/concise/AuthManager.js
@@ -78,9 +78,9 @@
'X-WP-Nonce': this.getNonce()
};
- if (!isFormData && typeof options.body === 'object' && !Object.hasOwn(options.body, 'user')) {
- options.body.user = this.getUser();
- }
+ // if (!isFormData && typeof options.body === 'object' && !Object.hasOwn(options.body, 'user')) {
+ // options.body.user = this.getUser();
+ // }
let obj = {
...options,
diff --git a/assets/js/concise/BioManager.js b/assets/js/concise/BioManager.js
index cd058a2..bf2f63a 100644
--- a/assets/js/concise/BioManager.js
+++ b/assets/js/concise/BioManager.js
@@ -19,7 +19,7 @@
if (data === null) {
return;
}
- data.user = window.auth.getUser();
+ // data.user = window.auth.getUser();
if (Object.hasOwn(data, 'term_name') && data['term_name'] === ''){
delete data['term_name'];
diff --git a/assets/js/concise/CRUDOld.js b/assets/js/concise/CRUDOld.js
index 998483d..f662f0c 100644
--- a/assets/js/concise/CRUDOld.js
+++ b/assets/js/concise/CRUDOld.js
@@ -508,7 +508,7 @@
// Get the base content type
data.content = form.closest('dialog')?.dataset.content || this.content;
- data.user = window.auth.getUser();
+ // data.user = window.auth.getUser();
// Collect timeline entries
const timelineGroups = form.querySelectorAll('.upload-group');
diff --git a/assets/js/concise/FormController.js b/assets/js/concise/FormController.js
index ccbea31..e5f3868 100644
--- a/assets/js/concise/FormController.js
+++ b/assets/js/concise/FormController.js
@@ -669,7 +669,7 @@
this.cancelServerSave(form.id);
if (form.useQueue && window.jvbQueue) {
- changes.user = window.auth.getUser();
+ // changes.user = window.auth.getUser();
window.jvbQueue.addToQueue({
endpoint: form.options.endpoint,
data: changes,
diff --git a/base/Site.php b/base/Site.php
index 49df15e..f6ecad0 100644
--- a/base/Site.php
+++ b/base/Site.php
@@ -130,7 +130,8 @@
public static function has(string $property):bool
{
- return property_exists(self::class, $property) && self::${$property} === true;
+ $inst = self::getInstance();
+ return property_exists($inst, $property) && $inst::${$property} === true;
}
public static function hasAny(array $properties):bool
diff --git a/inc/helpers/all.php b/inc/helpers/all.php
index 274e00d..63eac34 100644
--- a/inc/helpers/all.php
+++ b/inc/helpers/all.php
@@ -88,4 +88,3 @@
'post_type' => $type
]);
}
-
diff --git a/inc/integrations/Facebook.php b/inc/integrations/Facebook.php
index f30ee8f..2812607 100644
--- a/inc/integrations/Facebook.php
+++ b/inc/integrations/Facebook.php
@@ -360,31 +360,18 @@
*/
protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void
{
- $post_type = $settings['fb_type'] ?? 'post';
- $sync_immediately = $settings['immediate'] ?? false;
- // Determine the Facebook post type
- $fb_endpoint = self::FB_POST_TYPES[$post_type] ?? 'feed';
-
- $operation_data = [
- 'post_id' => $postID,
- 'fb_type' => $post_type,
- 'endpoint' => $fb_endpoint,
- 'page_id' => $this->page_id
- ];
-
- // Check for scheduled posting
- $schedule_time = get_post_meta($postID, BASE . 'schedule_facebook', true);
- $options = [];
-
- if ($schedule_time && strtotime($schedule_time) > time()) {
- $options['scheduled'] = strtotime($schedule_time);
- } elseif (!$sync_immediately) {
- $options['delay'] = 300; // 5 minute delay for batching
+ if (!$this->hasOAuthCredentials()) {
+ error_log('OAuth Not set up for '.$this->service_name);
+ return;
}
-
- $operation = $update ? 'update_post' : 'create_post';
- $this->queueOperation($operation, $operation_data, $options);
+ $this->queueOperation(self::$syncTo, [
+ 'items' => [$postID],
+ 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author
+ ], [
+ 'delay' => 60
+ ]);
+ Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
}
/**
@@ -393,7 +380,7 @@
public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array
{
try {
- $fb = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this;
+ $fb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this;
switch ($operation->type) {
case 'facebook_create_post':
return $fb->createFacebookPost($data);
diff --git a/inc/integrations/GoogleMyBusiness.php b/inc/integrations/GoogleMyBusiness.php
index b802142..a66a81a 100644
--- a/inc/integrations/GoogleMyBusiness.php
+++ b/inc/integrations/GoogleMyBusiness.php
@@ -216,39 +216,25 @@
public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings):void
{
- $type = $settings['content_type']??'post'; //can be 'post', 'offer', 'event', 'hours', 'info',
- $initial = $settings['initial']?? false;
- $syncOnUpdate = $settings['update']??false;
- if ($update && !$syncOnUpdate) {
- return;
- }
- if (!$initial) {
+
+ if (!$this->hasOAuthCredentials()) {
+ error_log('OAuth Not set up for '.$this->service_name);
return;
}
- $options = $data = [];
- switch ($type) {
- case 'menu_item':
- $options = ['delay' => 60];
- break;
- }
- if (!user_can($post->post_author, 'manage_options')) {
- $data['user'] = $post->post_author;
- }
- $data['post_id'] = $postID;
- $operation = ($update) ? 'update_' : 'create_';
-
- $this->queueOperation(
- $operation.$type,
- $data,
- $options
- );
+ $this->queueOperation(self::$syncTo, [
+ 'items' => [$postID],
+ 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author
+ ], [
+ 'delay' => 60
+ ]);
+ Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
}
public function processOperation(\WP_Error|array $result, object $operation, array $data):\WP_Error|array
{
try {
- $gmb = (array_key_exists('user', $data)) ? new self((int)$data['user']) : $this;
+ $gmb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this;
$gmb->ensureInitialized();
switch ($operation->type) {
case 'gmb_create_menu_item':
@@ -481,7 +467,7 @@
];
}
- $gmb = new self($data['userID']??null);
+ $gmb = self::getInstance($data['userID']??null);
// Build the complete menu structure
$menu_data = $gmb->collectMenu($menu_items);
diff --git a/inc/integrations/Helcim.php b/inc/integrations/Helcim.php
index 7f2556d..f5dc8a5 100644
--- a/inc/integrations/Helcim.php
+++ b/inc/integrations/Helcim.php
@@ -2,6 +2,7 @@
namespace JVBase\integrations;
use Exception;
+use JVBase\meta\Meta;
use WP_Error;
use WP_Post;
use JVBase\ui\Checkout;
@@ -25,6 +26,7 @@
*/
class Helcim extends Integrations
{
+ protected static string $syncCustomer = 'helcim_sync_customer';
protected string $service_name = 'helcim';
protected string|array $apiBase = 'https://api.helcim.com/v2';
@@ -711,9 +713,6 @@
return $desc;
}, 10, 2);
- // Register queue operation types with IntegrationExecutor
- $this->registerQueueTypes();
-
// Register webhook endpoint (handled by parent)
$this->registerWebhookEndpoint();
}
@@ -760,38 +759,16 @@
}
- protected function registerQueueTypes(): void
+ protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
{
$queue = JVB()->queue();
- $executor = new IntegrationExecutor();
- $queue->registry()->register('helcim_sync_to', new TypeConfig(
- executor: $executor,
- chunkKey: 'items',
- chunkSize: 10,
- maxRetries: 3
- ));
-
- $queue->registry()->register('helcim_sync_from', new TypeConfig(
- executor: $executor,
- chunkKey: 'items',
- chunkSize: 10,
- maxRetries: 3
- ));
-
- $queue->registry()->register('helcim_delete_from', new TypeConfig(
- executor: $executor,
- chunkKey: 'external_ids',
- chunkSize: 20,
- maxRetries: 2
- ));
-
- $queue->registry()->register('helcim_import', new TypeConfig(
+ $queue->registry()->register(self::$import, new TypeConfig(
executor: $executor,
maxRetries: 3
));
- $queue->registry()->register('helcim_sync_customer', new TypeConfig(
+ $queue->registry()->register(self::$syncCustomer, new TypeConfig(
executor: $executor,
maxRetries: 2
));
@@ -1028,22 +1005,34 @@
protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
{
- $fields = $this->getSyncFields($postID, 'post', ['share_to_helcim', 'schedule_helcim']);
- if (empty($fields['share_to_helcim'])) {
- return;
- }
-
- // Uses IntegrationExecutor via TypeRegistry instead of FilteredExecutor
- $this->queueOperation('sync_to', [
+ error_log('==== [Helcim]::handleTheSavePost ====');
+ $this->queueOperation(self::$syncTo, [
'items' => [$postID],
- 'user_id' => $this->userID,
+ 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author
], [
'priority' => 'high',
'delay' => 30,
]);
+ Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
+ }
+ /**
+ * Handle post deletion
+ */
+ public function handleDeletePost(int $postID): void
+ {
+ $item_id = Meta::forPost($postID)->get("_{$this->service_name}_item_id");
- update_post_meta($postID, BASE . '_helcim_sync_status', 'queued');
+ if (empty($item_id)) {
+ return;
+ }
+ $this->queueOperation(self::$deleteFrom, [
+ 'external_ids' => [$item_id],
+ 'post_id' => $postID,
+ ], [
+ 'priority' => 'high',
+ ]);
+
}
protected function handleImportFromHelcim(): array
diff --git a/inc/integrations/Instagram.php b/inc/integrations/Instagram.php
index 77abcf0..3968e45 100644
--- a/inc/integrations/Instagram.php
+++ b/inc/integrations/Instagram.php
@@ -6,6 +6,7 @@
namespace JVBase\integrations;
use JVBase\integrations\Integrations;
+use JVBase\meta\Meta;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
@@ -261,24 +262,22 @@
*/
public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
{
- // Check if post has featured image (Instagram requirement)
+
+ if (!$this->hasOAuthCredentials()) {
+ error_log('OAuth Not set up for '.$this->service_name);
+ return;
+ }
if (!has_post_thumbnail($postID)) {
- $this->logError('Cannot post to Instagram without featured image', [
- 'post_id' => $postID
- ]);
return;
}
- // Queue the Instagram post creation
- $this->queueOperation('create_post', [
- 'post_id' => $postID,
- 'type' => get_post_type($postID)
+ $this->queueOperation(self::$syncTo, [
+ 'items' => [$postID],
+ 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author
], [
- 'priority' => 'normal',
- 'delay' => 60 // Wait 1 minute to ensure all metadata is saved
+ 'delay' => 60
]);
-
- update_post_meta($postID, BASE . '_instagram_sync_status', 'queued');
+ Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
}
/**
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index 761e6a0..f7ce033 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -3,6 +3,9 @@
use Exception;
use JVBase\managers\Cache;
+use JVBase\managers\queue\executors\IntegrationExecutor;
+use JVBase\managers\queue\mergers\DefaultMerger;
+use JVBase\managers\queue\TypeConfig;
use JVBase\meta\Form;
use JVBase\meta\Meta;
use JVBase\managers\ErrorHandler;
@@ -10,8 +13,11 @@
use JVBase\registrar\Registrar;
use WP_Error;
use WP_Post;
+use WP_Query;
use WP_REST_Request;
use WP_REST_Response;
+use WP_Term;
+use WP_User;
if (!defined('ABSPATH')) {
exit;
@@ -30,15 +36,19 @@
{
//Flag to allow for custom settings (defaults, etc) for an integration in the dashboard
public static bool $hasExtraOptions = false;
+ public bool $hasBatchCreate = false;
+ public bool $hasBatchUpdate = false;
+ public bool $hasBatchDelete = false;
+ public bool $canCreateOnUpdate = false;
/**
* Queue types
* These types match with IntegrationExecutor
*/
- protected static string $syncTo = 'sync_to';
- protected static string $deleteFrom = 'delete_from';
- protected static string $syncFrom = 'sync_from';
- protected static string $syncCustomer = 'sync_customer';
- protected static string $import = 'import';
+ protected static string $syncTo;
+ protected static string $deleteFrom;
+ protected static string $syncFrom;
+ protected static string $syncCustomer;
+ protected static string $import;
/**
* API Configuration
* These properties define how the integration connects to external services
@@ -208,9 +218,13 @@
];
}
+ self::$syncTo = $this->service_name.'_sync_to';
+ self::$deleteFrom = $this->service_name.'_delete_from';
+ self::$syncFrom = $this->service_name.'_sync_from';
+ //TODO: What is the difference between sync_from and import?
+ self::$import = $this->service_name.'import_from';
+
add_filter('jvbShouldRenderMeta', [$this, 'checkRenderField'], 10, 4);
-
-
}
protected function addFilters():bool
@@ -312,21 +326,7 @@
protected function getPostTypes(): void
{
- $key = BASE . $this->service_name . '_sync_post_types';
- $postTypes = get_option($key, false);
- if (!$postTypes) {
- $postTypes = [];
- foreach (Registrar::getRegistered('post') as $registrar) {
- $registrar = Registrar::getInstance($registrar);
- if ($registrar->hasIntegration($this->service_name)) {
- $postTypes[] = $registrar->getSlug();
- }
- }
-
- update_option($key, $postTypes);
- }
-
- $this->syncPostTypes = $postTypes;
+ $this->syncPostTypes = Registrar::withIntegration($this->service_name);
}
protected function getTaxonomies():void
@@ -687,9 +687,9 @@
protected function clearCache():array
{
- $success = $this->cache->flush();
+ $this->cache->flush();
return [
- 'success' => $success,
+ 'success' => true,
];
}
@@ -1058,11 +1058,8 @@
):bool {
$queue = JVB()->queue();
- // Add service prefix to operation type
- $operation_type = strtolower($this->service_name) . '_' . $type;
-
$queued = $queue->queueOperation(
- $operation_type,
+ $type,
$this->userID ?? 0,
array_merge($data, ['service' => $this->service_name, 'user' => $this->userID??0]),
array_merge([
@@ -1204,31 +1201,85 @@
*/
protected function registerHooks(): void
{
- add_filter(BASE . 'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
- add_action( 'wp_ajax_'.BASE.$this->service_name.'_oauth_callback', [$this, 'handleAjaxResponse'] );
- add_action( 'wp_ajax_nopriv_'.BASE.$this->service_name.'_oauth_callback', [$this, 'handleAjaxResponse'] );
- if ($this->handleWebhooks){
+ //Handled by IntegrationExecutor now
+// add_filter(BASE . 'handle_bulk_operation', [$this, 'processOperation'], 10, 3);
+ if (is_null($this->userID)) {
+ add_action( 'wp_ajax_'.BASE.$this->service_name.'_oauth_callback', [$this, 'handleAjaxResponse'] );
+ add_action( 'wp_ajax_nopriv_'.BASE.$this->service_name.'_oauth_callback', [$this, 'handleAjaxResponse'] );
+ }
+
+ if ($this->handleWebhooks && is_null($this->userID)){
$this->registerWebhookEndpoint();
}
// Let child classes register additional hooks if needed
$this->registerAdditionalHooks();
- if (!empty($this->syncPostTypes)) {
- add_action('save_post', [$this, 'handleSavePost'], 20, 3);
+ if (!empty($this->syncPostTypes) && is_null($this->userID)) {
+ $this->addSavePost();
add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
if ($this->canSync['delete']) {
add_action('before_delete_post', [$this, 'handleDeletePost'], 10, 1);
}
}
- if (!empty($this->syncTaxonomies)) {
+ if (!empty($this->syncTaxonomies) && is_null($this->userID)) {
add_action('saved_term', [$this, 'handleSaveTerm'], 20, 5);
if ($this->canSync['delete']) {
add_action('pre_delete_term', [$this, 'handleDeleteTerm'], 10, 2);
}
}
- }
+ add_action('init', [$this, 'registerQueueTypes'], 10);
+ }
+ public function addSavePost():void
+ {
+ if (!has_action('save_post', [$this, 'handleSavePost'])) {
+ add_action('save_post', [$this, 'handleSavePost'], 20, 3);
+ }
+ }
+ public function removeSavePost():void
+ {
+ remove_action('save_post', [$this, 'handleSavePost'], 20, 3);
+ }
+
+
+ public function registerQueueTypes():void
+ {
+ if (empty($this->syncTaxonomies) && empty($this->syncPostTypes)) {
+ return;
+ }
+ $queue = JVB()->queue();
+ $executor = new IntegrationExecutor();
+
+ $queue->registry()->register(self::$syncTo, new TypeConfig(
+ mergeable: new DefaultMerger('items'),
+ executor: $executor,
+ chunkKey: 'items',
+ chunkSize: 50,
+ maxRetries: 3,
+ ));
+
+ if ($this->canSync['delete']) {
+ $queue->registry()->register(self::$deleteFrom, new TypeConfig(
+ mergeable: new DefaultMerger('external_ids'),
+ executor: $executor,
+ chunkKey: 'external_ids',
+ chunkSize: 200,
+ maxRetries: 2
+ ));
+ }
+
+ $queue->registry()->register(self::$syncFrom, new TypeConfig(
+ executor: $executor,
+ maxRetries: 3
+ ));
+
+ $this->registerAdditionalQueueTypes($executor);
+ }
+ protected function registerAdditionalQueueTypes(IntegrationExecutor $executor):void
+ {
+ //Empty. Integration extensions can register additional operation types from here.
+ }
public function handleAjaxResponse()
{
@@ -2097,6 +2148,11 @@
*/
public function handleSavePost(int $postID, WP_Post $post, bool $update): void
{
+ if (!is_null($this->userID)) {
+ return;
+ }
+ error_log('=== ['.$this->service_name.']::handleSavePost called');
+
if (!$postID || $postID === 0) {
return;
}
@@ -2105,40 +2161,50 @@
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($postID)) return;
+
+ if (empty($this->syncPostTypes) || !in_array(jvbNoBase($postType), $this->syncPostTypes)) {
+ error_log('Not handling save for '.$this->service_name.' because there are no syncPostTypes: '.print_r($this->syncPostTypes, true));
+ return;
+ }
+
$registrar = Registrar::getInstance($postType);
if (!$registrar){
return;
}
- if (empty($this->syncPostTypes)) {
- return;
- }
-
$settings = $registrar->hasIntegration($this->service_name)??null;
if (!$settings) {
+ error_log('Not handling save for '.$this->service_name.' because of no registrar settings');
return;
}
$settings = $registrar->getIntegrationConfig($this->service_name);
if (!$settings){
+ error_log('Not handling save for '.$this->service_name.' because of no integration config '.print_r($settings, true));
return;
}
$fields = $this->getSyncFields($postID, 'post', ['schedule_'.$this->service_name]);
if (!$fields['share_to_'.$this->service_name]) {
+ error_log('Not handling save for '.$this->service_name.' because of no share_to_'.$this->service_name.' '.print_r($fields, true));
return;
}
$isShared = isset($fields["_{$this->service_name}_item_id"]);
if ($update && $isShared && !$fields['_keep_synced_'.$this->service_name]) {
+ error_log('Not handling save for '.$this->service_name.' because it is already shared, and not set to keep synced. ');
return;
}
if ($post->post_status !== 'publish' && !$isShared) {
+ error_log('Not handling save for '.$this->service_name.' because post status is not publish, and it is not already shared.');
return;
}
+ error_log('==== Sending to integration\'s handleTheSavePost '.$this->service_name.' ====');
+ $this->removeSavePost();
$this->handleTheSavePost($postID, $post, $update, $settings);
+ $this->addSavePost();
}
@@ -3518,4 +3584,425 @@
{
return $this->icon;
}
+
+ public function getServiceItemID(int $itemID, string $type = 'post'):string
+ {
+ return match($type) {
+ 'post' => Meta::forPost($itemID)->get("_{$this->service_name}_item_id"),
+ 'term','taxonomy' => Meta::forTerm($itemID)->get("_{$this->service_name}_item_id"),
+ 'user' => Meta::forUser($itemID)->get("_{$this->service_name}_item_id"),
+ default => ''
+ };
+ }
+
+ public function canBatchUpdate():bool
+ {
+ return $this->hasBatchUpdate;
+ }
+ public function canBatchCreate():bool
+ {
+ return $this->hasBatchCreate;
+ }
+ public function canBatchDelete():bool
+ {
+ return $this->hasBatchDelete;
+ }
+
+ protected function updateItemStatus(int|array $items, string $status = 'pending', string $type = 'post'):void
+ {
+ if (!is_array($items)) {
+ $items = [$items];
+ }
+ foreach ($items as $ID) {
+ match ($type) {
+ 'post' => Meta::forPost($ID)->set('_'.$this->service_name.'_sync_status', $status),
+ 'term' => Meta::forTerm($ID)->set('_'.$this->service_name.'_sync_status', $status),
+ 'user' => Meta::forUser($ID)->set('_'.$this->service_name.'_sync_status', $status),
+ default => false
+ };
+ }
+ }
+
+ public function updateBatchToService(array $data):array
+ {
+ $type = $data['type']??'post';
+
+ $newlyCreated = [];
+ if (!$this->canCreateOnUpdate) {
+ $created = array_filter($data['items'], function($ID) use ($type) {
+ return !empty($this->getServiceItemID($ID, $type));
+ });
+
+ //Test to see if we have any that haven't been created yet.
+ //For some services, items may have to be created before they can be updated
+ if (count($created) !== count($data['items'])) {
+ $notCreated = array_filter($data['items'], function($ID) use ($type) {
+ return empty($this->getServiceItemID($ID, $type));
+ });
+ $newData = $data;
+ $newData['items'] = $notCreated;
+ $newlyCreated = $this->createBatchToService($newData);
+ }
+
+ // If we don't have any that are created, just send the noUpdatedItems response, with any newly created items added
+ if (empty($created)) {
+ return $this->noUpdatedItems($newlyCreated);
+ }
+ $data['items'] = $created;
+ }
+
+
+ $items = $this->formatItems($data['items'], $type);
+
+ if (empty($items)) {
+ return $this->noUpdatedItems($newlyCreated);
+ }
+
+ if ($this->hasBatchUpdate) {
+ $response = $this->sendBatchUpdate($items);
+ if (!is_wp_error($response)) {
+ $result = $this->processBatchUpdateResponse($data, $response);
+ } else {
+ $this->logError('Batch update failed',[
+ 'method' => 'updateBatchToService',
+ 'item_ids' => $data['items'],
+ 'error' => $response
+ ]);
+ $this->updateItemStatus($data['items'], 'error');
+
+ $result = [
+ 'outcome' => 'failed_permanent',
+ 'result' => 'Could not update items'
+ ];
+ }
+ } else {
+ $success = $errors = [];
+ //Does not have batch update, manually update each one
+ foreach ($items as $item) {
+ $itemResult = $this->updateOne($item);
+ if (!is_wp_error($itemResult)) {
+ $success[] = $itemResult;
+ } else {
+ $errors[] = $itemResult;
+ }
+ }
+
+ $result = [
+ 'outcome' => empty($errors) ? 'success' : (empty($success) ? 'failed' : 'partial'),
+ 'result' => [
+ 'success' => $success,
+ 'errors' => $errors
+ ]
+ ];
+ }
+
+ return array_merge($result, $newlyCreated);
+ }
+
+ /**
+ * To be implemented by integration extension. Updates a single item
+ * @param array $item
+ * @return array|WP_Error
+ */
+ protected function updateOne(array $item):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own updateOne method');
+ }
+
+ /**
+ * Overridden by child classes
+ * @param array $items
+ * @return array|WP_Error
+ */
+ protected function sendBatchUpdate(array $items):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own sendBatchUpdate');
+ }
+ /**
+ * Overridden by child classes
+ * @param array $items
+ * @return array|WP_Error
+ */
+ protected function sendBatchCreate(array $items):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own sendBatchCreate');
+ }
+
+ /**
+ * To be implemented by extensions
+ * @param array $data
+ * @param array $response
+ * @return array
+ */
+ protected function processBatchUpdateResponse(array $data, array $response):array
+ {
+ return [
+ 'outcome' => 'failed',
+ 'result' => [
+ 'message' => $this->service_name.' should implement processBatchUpdateResponse.'
+ ]
+ ];
+ }
+ /**
+ * To be implemented by extensions
+ * @param array $data
+ * @param array $response
+ * @return array
+ */
+ protected function processBatchCreateResponse(array $data, array $response):array
+ {
+ return [
+ 'outcome' => 'failed',
+ 'result' => [
+ 'message' => $this->service_name.' should implement processBatchCreateResponse.'
+ ]
+ ];
+ }
+
+ /**
+ * Defaults to an array of formatted items. Can be overridden in child classes
+ * @param array $itemIDs
+ * @param string $type
+ * @return array
+ */
+ protected function formatItems(array $itemIDs, string $type):array
+ {
+ $items = [];
+ foreach ($itemIDs as $ID) {
+ try {
+ $items[] = $this->formatForService($ID, $type);
+ } catch (Exception $e){
+ $this->logError('Could not format Item for service',[
+ 'itemID' => $ID,
+ 'type' => $type,
+ 'message' => $e->getMessage(),
+ ]);
+ }
+ }
+ return $items;
+ }
+
+ /**
+ * Must be defined according to how each service needs it to be
+ * @param int $itemID
+ * @param string $type
+ * @return array
+ * @throws Exception
+ */
+ protected function formatForService(int $itemID, string $type = 'post'):array
+ {
+ throw new Exception('formatForService must be implemented by child class');
+ }
+ protected function noUpdatedItems(array $created):array
+ {
+ $result = [
+ 'outcome' => 'success',
+ 'result' => [
+ 'message' => 'No items to update',
+ 'updated' => [],
+ 'errors' => [],
+ ]
+ ];
+
+ if (!empty($created)) {
+ error_log('Result before: '.print_r($result, true));
+ $result = array_merge($result, $created);
+ error_log('Result after merge: '.print_r($result, true));
+ }
+ return $result;
+ }
+
+ protected function noCreatedItems(array $updated):array
+ {
+ $result = [
+ 'outcome' => 'success',
+ 'result' => [
+ 'message' => 'No items to create',
+ 'created' => [],
+ 'errors' => [],
+ ]
+ ];
+ if (!empty($updated)) {
+ $result = array_merge($result, $updated);
+ }
+ return $result;
+ }
+
+ public function createBatchToService($data):array
+ {
+ $type = $data['type']??'post';
+
+ //Check if any of the submitted ids are already created
+ $created = array_filter($data['items'],
+ function($ID) use ($type) {
+ return !empty($this->getServiceItemID($ID, $type));
+ });
+
+ $updated = [];
+ if (!empty($created)) {
+ $updateData = $data;
+ $updateData['items'] = $created;
+ $updated = $this->updateBatchToService($data);
+
+ //remove any updated items from the original items to process
+ $data['items'] = array_filter($data['items'], function ($ID) use ($created) {
+ return !in_array($ID, $created);
+ });
+ }
+
+ $items = $this->formatItems($data['items'], $type);
+ if (empty($items)) {
+ return $this->noCreatedItems($updated);
+ }
+
+ $response = [
+ 'outcome' => 'failed',
+ 'result' => [
+ 'message' => 'No result'
+ ]
+ ];
+ if ($this->hasBatchCreate) {
+ $response = $this->sendBatchCreate($items);
+ if (!is_wp_error($response)) {
+ $result = $this->processBatchCreateResponse($data, $response);
+ } else {
+ $this->logError('Batch create failed',[
+ 'method' => 'createBatchToService',
+ 'item_ids' => $data['items'],
+ 'error' => $response
+ ]);
+ $this->updateItemStatus($data['items'], 'error');
+
+ $result = [
+ 'outcome' => 'failed_permanent',
+ 'result' => 'Could not update items'
+ ];
+ }
+ } else {
+ $success = $errors = [];
+ foreach ($items as $item) {
+ $itemResult = $this->createOne($item);
+ if (!is_wp_error($itemResult)) {
+ $success[] = $itemResult;
+ } else {
+ $errors[] = $itemResult;
+ }
+ }
+ $result = [
+ 'outcome' => empty($errors) ? 'success' : (empty($success) ? 'failed' : 'partial'),
+ 'result' => [
+ 'success' => $success,
+ 'errors' => $errors
+ ]
+ ];
+ }
+
+ return array_merge($result, $updated);
+ }
+
+ /**
+ * To be implemented by integration extension. Updates a single item
+ * @param array $item
+ * @return array|WP_Error
+ */
+ protected function createOne(array $item):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own createOne');
+ }
+
+ public function findWPItem(string $integrationID, ?string $type = null):array|false
+ {
+ if (empty($integrationID)) {
+ error_log('Integrations::findWPItem No Integration ID received.');
+ return false;
+ }
+ $types = ['post', 'term', 'user'];
+ if (!is_null($type) && !in_array($type, $types)) {
+ error_log('Integrations::findWPItem invalid type sent: '.$type.'; defaulting to checking all');
+ $type = null;
+ }
+ if (!is_null($type)) {
+ return $this->queryWPType($integrationID, $type);
+ } else {
+ foreach ($types as $type) {
+ $check = $this->queryWPType($integrationID, $type);
+ if ($check) {
+ return $check;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param string $integrationID
+ * @param string|null $type
+ * @return array|false if success, an array of $ID and $type
+ */
+ public function queryWPType(string $integrationID, ?string $type = null):array|false
+ {
+ if (!in_array($type, ['post', 'term', 'user'])) {
+ return $this->findWPItem($integrationID);
+ }
+ return match($type) {
+ 'post' => $this->findWPPost($integrationID),
+ 'term' => $this->findWPTerm($integrationID),
+ 'user' => $this->findWPUser($integrationID),
+ default => false
+ };
+ }
+ public function findWPPost(string $integrationID):array|false
+ {
+ if (empty($integrationID)) {
+ return false;
+ }
+ $get = new WP_Query([
+ 'meta_query' => [
+ 'key' => BASE."_{$this->service_name}_item_id",
+ 'value' => $integrationID,
+ ],
+ 'fields' => 'ids',
+ 'posts_per_page' => 1,
+ ]);
+ return $get->have_posts() && !empty($get->posts) ? [$get->posts[0], 'post'] : false;
+ }
+ public function findWPTerm(string $integrationID):array|false
+ {
+ if (empty($integrationID)) {
+ return false;
+ }
+ $get = get_terms([
+ 'meta_query' => [
+ 'key' => BASE."_{$this->service_name}_item_id",
+ 'value' => $integrationID,
+ ],
+ 'fields' => 'ids',
+ 'hide_empty'=> true,
+ 'number' => 1
+ ]);
+ return is_wp_error($get) || empty($get) ? false : [$get[0], 'term'];
+ }
+ public function findWPUser(string $integrationID):array|false
+ {
+ if (empty($integrationID)) {
+ return false;
+ }
+ $get = get_users([
+ 'meta_query' => [
+ 'key' => BASE."_{$this->service_name}_item_id",
+ 'value' => $integrationID,
+ ],
+ 'fields' => 'ids',
+ 'number' => 1,
+ ]);
+ return is_wp_error($get) || empty($get) ? false : [$get[0], 'user'];
+ }
+
+ /*********************************************************************************
+ * Syncing WP items from Service
+ *********************************************************************************/
+ protected function createItemFromService(array $itemData):bool
+ {
+
+ }
}
diff --git a/inc/integrations/Square.php b/inc/integrations/Square.php
index 5e1a831..c0191a3 100644
--- a/inc/integrations/Square.php
+++ b/inc/integrations/Square.php
@@ -28,6 +28,7 @@
*/
class Square extends Integrations
{
+ protected static string $syncCustomer = 'square_sync_customer';
protected array $allowedContent = [
'REGULAR',
'FOOD_AND_BEV',
@@ -90,14 +91,16 @@
public static function getInstance(?int $userID = null):self
{
$key = is_null($userID) ? 'base' : $userID;
+
if (!array_key_exists($key, self::$instances)) {
- self::$instances[$key] = new static($userID);
+ self::$instances[$key] = new self($userID);
}
return self::$instances[$key];
}
protected function __construct(?int $userID = null)
{
+
// Display properties
$this->title = 'Square';
$this->icon = 'square-logo';
@@ -786,8 +789,8 @@
'name' => $variation['name'],
'pricing_type' => 'FIXED_PRICING',
'price_money' => [
- 'amount' => intval($variation['_square_price'] * 100), // Convert to cents
- 'currency' => 'USD'
+ 'amount' => $this->formatPrice($variation['_square_price']??0),
+ 'currency' => $this->getCurrency()
]
]
];
@@ -801,8 +804,8 @@
'name' => 'Regular',
'pricing_type' => 'FIXED_PRICING',
'price_money' => [
- 'amount' => intval(($itemData['_square_price'] ?? 0) * 100),
- 'currency' => 'USD'
+ 'amount' => $this->formatPrice($itemData['_square_price'] ?? 0),
+ 'currency' => $this->getCurrency()
]
]
];
@@ -858,7 +861,6 @@
return;
}
- add_action('wp_login', [$this, 'trackUserLogin'], 10, 2);
add_action('wp_enqueue_scripts', [$this, 'enqueueScripts']);
add_filter('jvbAdditionalActions', [Checkout::class, 'render']);
@@ -880,8 +882,6 @@
add_filter('jvb_checkout_browse_text', function () {
return 'browse our menu';
});
- // Register queue executor types
- add_action('init', [$this, 'registerQueueTypes'], 10);
}
/**
@@ -910,29 +910,9 @@
]);
}
- public function registerQueueTypes(): void
+ protected function registerAdditionalQueueTypes(IntegrationExecutor $executor): void
{
$queue = JVB()->queue();
- $executor = new IntegrationExecutor();
-
- $queue->registry()->register(self::$syncTo, new TypeConfig(
- executor: $executor,
- chunkKey: 'items',
- chunkSize: 50,
- maxRetries: 3
- ));
-
- $queue->registry()->register(self::$deleteFrom, new TypeConfig(
- executor: $executor,
- chunkKey: 'external_ids',
- chunkSize: 200,
- maxRetries: 2
- ));
-
- $queue->registry()->register(self::$syncFrom, new TypeConfig(
- executor: $executor,
- maxRetries: 3
- ));
$queue->registry()->register(self::$syncCustomer, new TypeConfig(
executor: $executor,
@@ -954,15 +934,15 @@
*/
protected function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings): void
{
+ error_log('==== [Square]::handleTheSavePost ====');
$this->queueOperation(self::$syncTo, [
'items' => [$postID],
- 'user_id' => $this->userID,
+ 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author
], [
'priority' => 'high',
'delay' => 30,
]);
-
- update_post_meta($postID, BASE . '_square_sync_status', 'queued');
+ Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued');
}
/**
@@ -970,16 +950,18 @@
*/
public function handleDeletePost(int $postID): void
{
- $square_id = get_post_meta($postID, BASE . '_square_catalog_id', true);
+ $item_id = $this->getServiceItemID($postID);
- if ($square_id) {
- $this->queueOperation(self::$deleteFrom, [
- 'external_ids' => [$square_id],
- 'post_id' => $postID,
- ], [
- 'priority' => 'high',
- ]);
+ if (empty($item_id)) {
+ return;
}
+ $this->queueOperation(self::$deleteFrom, [
+ 'external_ids' => [$item_id],
+ 'post_id' => $postID,
+ ], [
+ 'priority' => 'high',
+ ]);
+
}
/**
@@ -1063,6 +1045,9 @@
private function processBatchSyncResponse(array $response, array $map, array &$success, array &$errors):void
{
+ error_log('==== SQUARE::processBatchSyncResponse =====');
+ error_log('Full response: '.print_r($response, true));
+
// Handle successful objects
if (!empty($response['objects'])) {
foreach ($response['objects'] as $object) {
@@ -1162,6 +1147,14 @@
// Get existing Square catalog ID if it exists
$existing_square_id = get_post_meta($postID, BASE . '_square_catalog_id', true);
+ $registrar = Registrar::getInstance($post_type);
+ $product_type = 'FOOD_AND_BEV';
+ if ($registrar) {
+ $conf = $registrar->getIntegration($this->service_name);
+ if ($conf) {
+ $product_type = $conf->getContentType();
+ }
+ }
// Build the base catalog object
$catalog_object = [
@@ -1170,7 +1163,7 @@
'item_data' => [
'name' => $post->post_title,
'description' => wp_strip_all_tags($post->post_content),
- 'product_type' => 'FOOD_AND_BEV',
+ 'product_type' => $product_type,
'variations' => [],
'is_taxable' => true,
]
@@ -1182,10 +1175,9 @@
}
// Add variations
- $variations = $meta->get('product_variations');
+ $variations = $meta->get('_square_product_variations');
if (empty($variations)) {
// Create default variation if none exist
- $price = floatval($meta->get('price') ?: 0);
$catalog_object['item_data']['variations'][] = [
'type' => 'ITEM_VARIATION',
'id' => $existing_square_id ? null : '#'.BASE.'menu_item_' . $postID . '_var_default',
@@ -1194,32 +1186,41 @@
'ordinal' => 0,
'pricing_type' => 'FIXED_PRICING',
'price_money' => [
- 'amount' => intval($price * 100), // Convert dollars to cents
- 'currency' => 'CAD'
+ 'amount' => $this->formatPrice($meta->get('_square_price')),
+ 'currency' => $this->getCurrency()
],
'sellable' => true,
'stockable' => true
]
];
} else {
+ $resetVariations = false;
foreach ($variations as $index => $variation) {
- $existing_var_id = get_post_meta($postID, BASE . '_square_variation_' . $index . '_id', true);
+ $id = '#'.BASE.'menu_item_' . $postID . '_var_' . $index;
+ if (empty($variation['item_id'])) {
+ $resetVariations = true;
+ $variations[$index]['item_id'] = $id;
+ $variation['item_id'] = $id;
+ }
$catalog_object['item_data']['variations'][] = [
'type' => 'ITEM_VARIATION',
- 'id' => $existing_var_id ?: '#'.BASE.'menu_item_' . $postID . '_var_' . $index,
+ 'id' => $variation['item_id'],
'item_variation_data' => [
'name' => $variation['name'] ?? 'Variation ' . ($index + 1),
'ordinal' => $index,
'pricing_type' => 'FIXED_PRICING',
'price_money' => [
- 'amount' => intval(floatval($variation['price'] ?? 0) * 100),
- 'currency' => 'CAD'
+ 'amount' =>$this->formatPrice($variation['price'] ?? 0),
+ 'currency' => $this->getCurrency()
],
'sellable' => true,
'stockable' => true
]
];
}
+ if ($resetVariations) {
+ $meta->set('_square_product_variations', $variations);
+ }
}
// Add categories if they exist
@@ -1262,98 +1263,7 @@
return $catalog_object;
}
- /**
- * Build variations from repeater field
- */
- private function buildVariations(int $postID, array $values, string $post_type): array
- {
- $variations = [];
- $product_variations = $values['product_variations'] ?? [];
- // Get variation field mapping
- $variation_map = $this->getVariationMapping($post_type);
-
- // If we have repeater variations
- if (!empty($product_variations) && is_array($product_variations)) {
- foreach ($product_variations as $index => $variation_data) {
- // Skip empty variations
- if (empty($variation_data['name']) && empty($variation_data['_square_sku'])) {
- continue;
- }
-
- $variation = [
- 'type' => 'ITEM_VARIATION',
- 'id' => '#' . $post_type . '_' . $postID . '_var_' . $index,
- 'item_variation_data' => [
- 'name' => $variation_data['name'] ?? 'Variation ' . ($index + 1),
- 'ordinal' => $index,
- 'pricing_type' => 'FIXED_PRICING'
- ]
- ];
-
- // Check for existing Square variation ID
- $square_var_id = get_post_meta($postID, BASE . '_square_variation_' . $index . '_id', true);
- if ($square_var_id) {
- $variation['id'] = $square_var_id;
- }
-
- // Map variation fields
- foreach ($variation_map as $square_field => $wp_field) {
- if (isset($variation_data[$wp_field])) {
- switch ($square_field) {
- case 'price':
- $variation['item_variation_data']['price_money'] = [
- 'amount' => intval($variation_data[$wp_field] * 100), // Convert to cents
- 'currency' => 'CAD'
- ];
- break;
-
- case 'sku':
- $variation['item_variation_data']['_square_sku'] = $variation_data[$wp_field];
- break;
-
- case 'track_inventory':
- $variation['item_variation_data']['track_inventory'] = (bool)$variation_data[$wp_field];
- break;
-
- case 'service_duration':
- if (!empty($variation_data[$wp_field])) {
- $variation['item_variation_data']['service_data'] = [
- 'duration_minutes' => intval($variation_data[$wp_field])
- ];
- }
- break;
-
- default:
- $variation['item_variation_data'][$square_field] = $variation_data[$wp_field];
- break;
- }
- }
- }
-
- $variations[] = $variation;
- }
- }
-
- // If no variations exist, create a default one from base price
- if (empty($variations) && !empty($values['price'])) {
- $variations[] = [
- 'type' => 'ITEM_VARIATION',
- 'id' => '#' . $post_type . '_' . $postID . '_var_default',
- 'item_variation_data' => [
- 'name' => 'Regular',
- 'ordinal' => 0,
- 'pricing_type' => 'FIXED_PRICING',
- 'price_money' => [
- 'amount' => intval($values['price'] * 100),
- 'currency' => 'CAD'
- ]
- ]
- ];
- }
-
- return $variations;
- }
/**
* Get variation mapping for post type
@@ -1616,13 +1526,9 @@
];
}
- // Generate username from email
- $username = sanitize_user(current(explode('@', $email)));
- $username = $this->generateUniqueUsername($username);
-
// Create user account without password (they'll set it via email)
$user_id = wp_create_user(
- $username,
+ $email,
wp_generate_password(20, true, true), // Temporary random password
$email
);
@@ -1637,7 +1543,7 @@
// Set user role (assuming you have a customer role defined)
$user = new \WP_User($user_id);
- $user->set_role(BASE.'foodie'); // Or whatever role
+ $user->set_role(BASE.'foodie');
// Generate password reset key
$reset_key = get_password_reset_key($user);
@@ -1653,7 +1559,7 @@
// Link to Square customer if exists
$square_customer_id = $this->getOrCreateSquareCustomer([
'email' => $email,
- 'name' => $username
+ 'name' => $email
]);
if ($square_customer_id) {
@@ -1668,22 +1574,6 @@
}
/**
- * Generate unique username
- */
- private function generateUniqueUsername(string $base): string
- {
- $username = $base;
- $counter = 1;
-
- while (username_exists($username)) {
- $username = $base . $counter;
- $counter++;
- }
-
- return $username;
- }
-
- /**
* Send welcome email with password setup
*/
private function sendWelcomeEmail(\WP_User $user, string $reset_key): void
@@ -1699,7 +1589,7 @@
"%s\n\n" .
"Once you've set your password, you can:\n" .
"- View your order history\n" .
- "- Save your favorite items\n" .
+// "- Save your favorite items\n" .
"- Speed up checkout with saved payment methods\n\n" .
"If you didn't create this account, please ignore this email.\n\n" .
"Thanks,\n",
@@ -1715,49 +1605,6 @@
);
}
- /**
- * Track user login for security
- */
- public function trackUserLogin(string $user_login, \WP_User $user): void
- {
- // Check if user has Square integration
- $role = jvbUserRole($user->ID);
- $registrar = Registrar::getInstance($role);
- if ($registrar) {
- $config = $registrar->getIntegration($this->service_name);
- if ($config->isCustomer()) {
- $login_count = (int)get_user_meta($user->ID, BASE . '_square_login_count', true);
- $login_count++;
-
- update_user_meta($user->ID, BASE . '_square_login_count', $login_count);
- update_user_meta($user->ID, BASE . '_square_last_login', current_time('mysql'));
-
- // Check if password reset is needed
- if ($login_count % self::PASSWORD_RESET_INTERVAL === 0) {
- $this->schedulePasswordReset($user->ID);
- }
- }
- }
- }
-
- /**
- * Schedule password reset for security
- */
- private function schedulePasswordReset(int $user_id): void
- {
- update_user_meta($user_id, BASE . '_square_password_reset_required', true);
-
- // Send notification
- $user = get_user_by('ID', $user_id);
- if ($user) {
- JVB()->email()->sendEmail(
- $user->user_email,
- '['.get_bloginfo('name').'] Security Code',
- 'For your security, enter this code to continue accessing your account and saved payment methods.',
- );
- }
- }
-
/******************************************************************
* WEBHOOK HANDLING
******************************************************************/
@@ -3643,4 +3490,10 @@
}, $posts->posts);
}
+
+ public function formatPrice(string|float|int $priceValue):int
+ {
+ //Convert dollars to cents
+ return intval(floatval($priceValue) * 100);
+ }
}
diff --git a/inc/managers/queue/Processor.php b/inc/managers/queue/Processor.php
index e735921..4178df4 100644
--- a/inc/managers/queue/Processor.php
+++ b/inc/managers/queue/Processor.php
@@ -218,17 +218,17 @@
$op->errorMessage = $e->getMessage();
- JVB()->error()->log(
- '[Queue]:processOne',
- $e->getMessage(),
- [
- 'operation_id' => $op->id,
- 'type' => $op->type,
- 'user_id' => $op->userId,
- 'retries' => $op->retries,
- ],
- $op->outcome === 'failed_permanent' ? 'critical' : 'warning'
- );
+// JVB()->error()->log(
+// '[Queue]:processOne',
+// $e->getMessage(),
+// [
+// 'operation_id' => $op->id,
+// 'type' => $op->type,
+// 'user_id' => $op->userId,
+// 'retries' => $op->retries,
+// ],
+// $op->outcome === 'failed_permanent' ? 'critical' : 'warning'
+// );
}
private function calculateBackoff(int $attempt): string
diff --git a/inc/managers/queue/executors/IntegrationExecutor.php b/inc/managers/queue/executors/IntegrationExecutor.php
index ea79960..428dc88 100644
--- a/inc/managers/queue/executors/IntegrationExecutor.php
+++ b/inc/managers/queue/executors/IntegrationExecutor.php
@@ -86,9 +86,6 @@
*/
private function parseOperationType(string $type): array
{
- // Remove BASE prefix if present (e.g. 'jvb_helcim_sync_to' → 'helcim_sync_to')
- $type = str_replace(BASE, '', $type);
-
$pos = strpos($type, '_');
if ($pos === false) {
throw new Exception("Invalid integration operation type: {$type}");
@@ -103,21 +100,12 @@
/**
* Resolve integration instance, optionally for a specific user
*/
- private function resolveIntegration(string $serviceName, int $userId): ?object
+ private function resolveIntegration(string $serviceName, ?int $userId = null): ?object
{
- if (!isset($this->integrationCache[$serviceName])) {
- $this->integrationCache[$serviceName] = JVB()->connect($serviceName);
+ if ($userId === 0) {
+ $userId = null;
}
-
- $integration = $this->integrationCache[$serviceName];
-
- // If operation has a user context, re-instantiate for that user
- if ($integration && $userId) {
- $class = get_class($integration);
- return new $class($userId);
- }
-
- return $integration;
+ return JVB()->connect($serviceName, $userId);
}
/*****************************************************************
@@ -137,6 +125,11 @@
return new Result(outcome: 'success', result: ['synced' => [], 'message' => 'No items to sync']);
}
+ if ($integration->hasBatchUpdate()) {
+ $result = $integration->updateBatchToService($data['items']);
+ return new Result(outcome: $result['outcome'], result: $result['result']);
+ }
+
foreach ($items as $postID) {
try {
$result = $integration->syncPostToService((int)$postID);
diff --git a/inc/registrar/Registrar.php b/inc/registrar/Registrar.php
index 58590ba..b30b0cf 100644
--- a/inc/registrar/Registrar.php
+++ b/inc/registrar/Registrar.php
@@ -680,8 +680,8 @@
public static function withIntegration(string $integration, ?string $type = null):array
{
self::ensureInstanced();
-
- if (!Site::has($integration)) {
+ Site::getInstance();
+ if (!Site::hasIntegration($integration)) {
error_log('[Registrar]::withIntegration Integration not available to fetch: '.$integration);
return [];
}
@@ -690,7 +690,7 @@
if (!is_null($type) && $inst->type !== $type) {
return false;
}
- return array_key_exists($integration, $this->integrationConfigs);
+ return array_key_exists($integration, $inst->integrationConfigs);
}));
}
diff --git a/inc/registrar/config/Integration.php b/inc/registrar/config/Integration.php
index cab3a4c..6316e6c 100644
--- a/inc/registrar/config/Integration.php
+++ b/inc/registrar/config/Integration.php
@@ -27,6 +27,7 @@
* @var bool whether this is a customer role, used if this is a user-based Registrar
*/
protected bool $isCustomer;
+ protected ?string $category = null;
@@ -73,6 +74,14 @@
return $this->content_type;
}
+ public function setCategory(string $category):void
+ {
+ $check = Registrar::getInstance($category);
+ if ($check) {
+ $this->category = $check->getBased();
+ }
+ }
+
public function setInitial(bool $set):self
{
$this->initial = $set;
diff --git a/inc/registrar/helpers/AddIntegrationFields.php b/inc/registrar/helpers/AddIntegrationFields.php
index bf615e7..6aad5fa 100644
--- a/inc/registrar/helpers/AddIntegrationFields.php
+++ b/inc/registrar/helpers/AddIntegrationFields.php
@@ -73,6 +73,7 @@
'type' => 'true_false',
'label' => 'Share To '.$this->allowed[$this->service_name],
'section' => 'sync',
+ 'default' => 1
]
];
if ($this->config->getUpdate()){
@@ -84,7 +85,8 @@
'field' => 'share_to_'.$this->service_name,
'value' => 1,
'operator' => '=='
- ]
+ ],
+ 'default' => 1,
];
$fields["_{$this->service_name}_item_id"] = [
@@ -128,7 +130,8 @@
'field' => 'share_to_'.$this->service_name,
'operator' => '==',
'value' => 1
- ]
+ ],
+ 'default' => 1,
];
}
$additional = JVB()->connect($this->service_name)->getAdditionalFields($this->config->getContentType());
--
Gitblit v1.10.0