| | |
| | | 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 |
| | |
| | | * @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 |
| | | { |
| | |
| | | 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'], |
| | |
| | | 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 |
| | |
| | | } else { |
| | | $success = $errors = []; |
| | | foreach ($items as $item) { |
| | | $itemResult = $this->createOne($item); |
| | | $itemResult = $this->createOneToService($item); |
| | | if (!is_wp_error($itemResult)) { |
| | | $success[] = $itemResult; |
| | | } else { |
| | |
| | | * @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'); |
| | | } |
| | | |
| | | /** |
| | |
| | | *****************************************************************/ |
| | | 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) { |
| | |
| | | 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 |
| | |
| | | $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 { |
| | |
| | | * @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'); |
| | | } |
| | | |
| | | /** |
| | |
| | | ] |
| | | ]; |
| | | } |
| | | |
| | | 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 |
| | | *****************************************************************/ |
| | |
| | | } |
| | | 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); |
| | | } |
| | | } |