From 0dfe1d8afafc59c4a5559c498342668d5a58d6ef Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 23 Jul 2026 22:41:41 +0000
Subject: [PATCH] =Still working away at the Integrations overhaul
---
inc/integrations/SyncTo.php | 182 +++++++++++++++++++++++++++++++++++++++------
1 files changed, 158 insertions(+), 24 deletions(-)
diff --git a/inc/integrations/SyncTo.php b/inc/integrations/SyncTo.php
index dd1e9ca..077887b 100644
--- a/inc/integrations/SyncTo.php
+++ b/inc/integrations/SyncTo.php
@@ -2,14 +2,34 @@
namespace JVBase\integrations;
use Exception;
+use JVBase\meta\Meta;
+use JVBase\meta\Sanitizer;
+use JVBase\registrar\Registrar;
use WP_Error;
+use WP_Post;
+use WP_Term;
+use WP_User;
if (!defined('ABSPATH')) {
exit;
}
+/**
+ * For Syncing to a service, the basic flow is:
+ * 1) Queuing an operation
+ * a) using the $syncTo as the operation type
+ * b) the $data has:
+ * i) key of 'posts', 'terms', 'users' depending on the source data
+ * ii) key of 'user' to set user's specific integration connection, or the site-wide connection
+ * 2) IntegrationExecutor then determines if it is a sync to operation
+ * -> from there, it calls the integrations {action}BatchToService or {create}OneToService
+ *
+ * Most integrations will just need to set the $hasBatchUpdate, $hasBatchDelete, $hasBatchCreate flags to signal we can condense it into a single request,
+ * set the formatForService() method if anything deviates from the defaults
+ * and the endpoint for a particular action
+ */
trait SyncTo {
- use SyncHelpers;
+ use SyncHelpers, UserConnection;
/**
* Must be defined according to how each service needs it to be
* @param int $itemID
@@ -17,16 +37,13 @@
* @return array
* @throws Exception
*/
- protected function formatForService(int $itemID, string $type = 'post'):array
- {
- throw new Exception('formatForService must be implemented by child class');
- }
+ abstract protected function formatForService(int $itemID, string $type = 'post'):array;
/**
* Defaults to an array of formatted items. Can be overridden in child classes
* @param array $itemIDs
* @param string $type
- * @return array
+ * @return array Either an array of formatted items, or a single formatted item (if there is 1)
*/
protected function formatItems(array $itemIDs, string $type):array
{
@@ -35,21 +52,39 @@
try {
$items[] = $this->formatForService($ID, $type);
} catch (Exception $e){
- $this->logError('Could not format Item for service',[
+ $this->logError('formatItems', 'Could not format Item for service',[
'itemID' => $ID,
'type' => $type,
'message' => $e->getMessage(),
]);
}
}
- return $items;
+ return count($items) === 1 ? $items[0] : $items;
+ }
+
+ protected function determineType(array $data):string|false
+ {
+ $type = false;
+ if (array_key_exists('posts', $data)) {
+ $type = 'post';
+ } else if (array_key_exists('terms', $data)) {
+ $type = 'term';
+ } else if (array_key_exists('users', $data)) {
+ $type = 'user';
+ }
+ return $type;
}
/***************************************************************
* Item creation
***************************************************************/
public function createBatchToService($data):array
{
- $type = $data['type']??'post';
+ $type = $this->determineType($data);
+
+ if (!$type) {
+ $this->logError('createBatchToService', 'No expected keys in data', ['data' => $data]);
+ return $this->noCreatedItems([]);
+ }
//Check if any of the submitted ids are already created
$created = array_filter($data['items'],
@@ -74,18 +109,12 @@
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',[
+ $this->logError('createBatchToService','Batch create failed',[
'method' => 'createBatchToService',
'item_ids' => $data['items'],
'error' => $response
@@ -100,7 +129,7 @@
} else {
$success = $errors = [];
foreach ($items as $item) {
- $itemResult = $this->createOne($item);
+ $itemResult = $this->createOneToService($item);
if (!is_wp_error($itemResult)) {
$success[] = $itemResult;
} else {
@@ -124,9 +153,9 @@
* @param array $item
* @return array|WP_Error
*/
- protected function createOne(array $item):array|WP_Error
+ protected function createOneToService(array $item):array|WP_Error
{
- return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own createOne');
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own createOneToService');
}
/**
@@ -160,7 +189,12 @@
*****************************************************************/
public function updateBatchToService(array $data):array
{
- $type = $data['type']??'post';
+ $type = $this->determineType($data);
+
+ if (!$type) {
+ $this->logError('createBatchToService', 'No expected keys in data', ['data' => $data]);
+ return $this->noUpdatedItems([]);
+ }
$newlyCreated = [];
if (!$this->canCreateOnUpdate) {
@@ -198,7 +232,7 @@
if (!is_wp_error($response)) {
$result = $this->processBatchUpdateResponse($data, $response);
} else {
- $this->logError('Batch update failed',[
+ $this->logError('updateBatchToService','Batch update failed',[
'method' => 'updateBatchToService',
'item_ids' => $data['items'],
'error' => $response
@@ -214,7 +248,7 @@
$success = $errors = [];
//Does not have batch update, manually update each one
foreach ($items as $item) {
- $itemResult = $this->updateOne($item);
+ $itemResult = $this->updateOneToService($item);
if (!is_wp_error($itemResult)) {
$success[] = $itemResult;
} else {
@@ -239,9 +273,9 @@
* @param array $item
* @return array|WP_Error
*/
- protected function updateOne(array $item):array|WP_Error
+ protected function updateOneToService(array $item):array|WP_Error
{
- return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own updateOne method');
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own updateOneToService method');
}
/**
@@ -270,6 +304,62 @@
]
];
}
+
+ public function deleteOneToService(array $item):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own deleteOneToService');
+ }
+ public function sendBatchDelete(array $items):array|WP_Error
+ {
+ return new WP_Error('invalid', 'Integration '.$this->service_name.' must implement its own sendBatchDelete');
+ }
+
+ public function deleteBatchToService(array $data):array
+ {
+ $type = $this->determineType($data);
+
+ if (!$type) {
+ $this->logError('createBatchToService', 'No expected keys in data', ['data' => $data]);
+ return $this->noCreatedItems([]);
+ }
+
+ //Check if any submitted ids do not have an integration item ID
+ $notCreated = array_filter($data['items'],
+ function($ID) use ($type) {
+ return empty($this->getServiceItemID($ID, $type));
+ });
+ if (!empty($notCreated)) {
+ $this->logError('deleteBatchToService','Could not delete items',['items' => $notCreated]);
+ }
+
+ $created = array_filter($data['items'],
+ function($ID) use ($type) {
+ return !empty($this->getServiceItemID($ID, $type));
+ });
+
+ if ($this->hasBatchDelete) {
+ $result = $this->sendBatchDelete($created);
+ } else {
+ $errors = $success = [];
+ foreach ($created as $item) {
+ try {
+ $success[] = $this->deleteOneToService($item);
+ } catch (Exception $e) {
+ $errors[] = $e->getMessage();
+ }
+ }
+
+ $result = [
+ 'outcome' => empty($errors) ? 'success' : (empty($success) ? 'failed' : 'partial'),
+ 'result' => [
+ 'success' => $success,
+ 'errors' => $errors
+ ]
+ ];
+ }
+ return $result;
+ }
+
/*****************************************************************
* UTILITY
*****************************************************************/
@@ -307,4 +397,48 @@
}
return $result;
}
+
+ /****************************************************************
+ * UTILITY
+ ****************************************************************/
+ protected function getSyncFields(int $itemID, string $type, array $additionalFields = []):array
+ {
+ $meta = match ($type) {
+ 'post' => Meta::forPost($itemID),
+ 'term' => Meta::forTerm($itemID),
+ 'user' => Meta::forUser($itemID),
+ default => false
+ };
+ if (!$meta) {
+ return [];
+ }
+
+ $content = match ($type) {
+ 'post' => get_post_type($itemID),
+ 'term' => get_term($itemID)?->taxonomy,
+ 'user' => jvbUserRole($itemID)
+ };
+ $registrar = Registrar::getInstance($content);
+ if ($registrar) {
+ $additional = $this->getAdditionalFields($registrar->getIntegration($this->service_name)->getContentType());
+ $additional = array_combine(
+ array_map(fn($k) => str_starts_with($k, '_'.$this->service_name) ? $k : "_{$this->service_name}_{$k}", array_keys($additional)),
+ $additional
+ );
+ $additionalFields = array_merge($additionalFields, $additional);
+ }
+
+
+ $fields = [
+ 'share_to_' . $this->service_name,
+ '_keep_synced_' . $this->service_name,
+ "_{$this->service_name}_item_id",
+ "_{$this->service_name}_last_sync",
+ "_{$this->service_name}_shared_at",
+ "_{$this->service_name}_sync_status",
+ "_{$this->service_name}_scheduled_at",
+ ... $additionalFields
+ ];
+ return $meta->getAll($fields);
+ }
}
--
Gitblit v1.10.0