Jake Vanderwerf
2026-07-05 fff721dd185f5b97f7ae7a6e64189e55887ff590
inc/integrations/Integrations.php
@@ -6,6 +6,8 @@
use JVBase\meta\Form;
use JVBase\meta\Meta;
use JVBase\managers\ErrorHandler;
use JVBase\registrar\helpers\AddIntegrationFields;
use JVBase\registrar\Registrar;
use WP_Error;
use WP_Post;
use WP_REST_Request;
@@ -26,6 +28,17 @@
 */
abstract class Integrations
{
   //Flag to allow for custom settings (defaults, etc) for an integration in the dashboard
   public static bool $hasExtraOptions = 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';
   /**
    * API Configuration
    * These properties define how the integration connects to external services
@@ -55,7 +68,7 @@
    * Used for UI rendering in admin interfaces
    */
   public string $title;  // Human-readable service name (e.g., 'Google My Business')
   public string $icon;   // Phosphoricons icon slug
   public string $icon = '';   // Phosphoricons icon slug
   /**
    * Credentials & State
@@ -91,6 +104,7 @@
      'test_connection' => 'Test Connection',
   ];
   protected array $allowedContent = [];
   /**
    * Caching Configuration
@@ -131,8 +145,8 @@
      'update' => false,     // Can update already-shared posts
      'delete' => false,     // Can remove posts from the service
   ];
   protected array $syncPostTypes = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
   protected array $syncTaxonomies = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
   protected array $syncPostTypes = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by Registrar.php if the integration name exists as a key in [ 'integrations' => []]
   protected array $syncTaxonomies = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by Registrar.php if the integration name exists as a key in [ 'integrations' => []]
   protected array $contentTypes = [];   // Integration's available content types. Set by child classes' getContentTypes
   protected bool $has_content = false; // Whether integration has content that can sync
   /**
@@ -163,7 +177,7 @@
   protected bool $is_healthy = true;
   protected bool $handleWebhooks = false;
   public function __construct(?int $userID = null)
   protected function __construct(?int $userID = null)
   {
      $this->cacheName = $this->cacheName ?: $this->service_name;
      $this->userID = $userID;
@@ -195,6 +209,13 @@
      }
      add_filter('jvbShouldRenderMeta', [$this, 'checkRenderField'], 10, 4);
   }
   protected function addFilters():bool
   {
      return is_null($this->userID);
   }
   protected function setContentTypes():void
@@ -295,12 +316,10 @@
      $postTypes = get_option($key, false);
      if (!$postTypes) {
         $postTypes = [];
         // Get from JVB_CONTENT
         foreach (JVB_CONTENT as $type => $config) {
            if (array_key_exists('integrations', $config) && array_key_exists($this->service_name, $config['integrations'])) {
               $postTypes[] = $type;
         foreach (Registrar::getRegistered('post') as $registrar) {
            $registrar = Registrar::getInstance($registrar);
            if ($registrar->hasIntegration($this->service_name)) {
               $postTypes[] = $registrar->getSlug();
            }
         }
@@ -318,11 +337,10 @@
      if (!$taxonomies) {
         // Combine both content and taxonomy filtering
         $taxonomies = [];
         // Get from JVB_TAXONOMY (content taxonomies)
         foreach (JVB_TAXONOMY as $type => $config) {
            if (jvbCheck('is_content', $config) &&
               isset($config['integrations'][$this->service_name])) {
               $taxonomies[] = $type;
         foreach (Registrar::withFeature('is_content', 'term') as $type) {
            $registrar = Registrar::getInstance($type);
            if ($registrar->hasIntegration($this->service_name)) {
               $taxonomies[] = $registrar->getSlug();
            }
         }
@@ -1413,51 +1431,6 @@
   }
   /**
    * Switch user context
    */
   public function switchUser(int $user_id): void
   {
      if ($this->userID === $user_id) {
         return;
      }
      // Clean up current context
      $this->cleanup();
      // Switch context
      $this->userID = $user_id;
      $this->credentials = [];
      $this->resetTokenRefreshFlag();  // ADD THIS LINE
      $this->ensureInitialized();
   }
   public function getAsUser(int $user_id) {
      return new $this($user_id);
   }
   /**
    * Clean up resources
    */
   protected function cleanup(): void
   {
      // Clear sensitive data
      $this->credentials = [];
      // Clear request history
      $this->request_history = [];
   }
   /**
    * Destructor - ensure cleanup
    */
   public function __destruct()
   {
      $this->cleanup();
   }
   /***************************************************************
      ERROR HANDLING
    ***************************************************************/
@@ -2124,23 +2097,34 @@
    */
   public function handleSavePost(int $postID, WP_Post $post, bool $update): void
   {
      if (jvbNoSaveIt($postID, $post)) {
      if (!$postID || $postID === 0) {
         return;
      }
      $postType = jvbNoBase($post->post_type);
      if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
      if (wp_is_post_revision($postID)) return;
      $registrar = Registrar::getInstance($postType);
      if (!$registrar){
         return;
      }
      if (empty($this->syncPostTypes)) {
         return;
      }
      $config = JVB_CONTENT[jvbNoBase($post->post_type)]??null;
      if (!$config) {
         return;
      }
      $settings = $config['integrations'][$this->service_name]??null;
      $settings = $registrar->hasIntegration($this->service_name)??null;
      if (!$settings) {
         return;
      }
      $settings = $registrar->getIntegrationConfig($this->service_name);
      if (!$settings){
         return;
      }
      $fields = $this->getSyncFields($postID, 'post', ['schedule_'.$this->service_name]);
      if (!$fields['share_to_'.$this->service_name]) {
         return;
@@ -2243,13 +2227,13 @@
      if (!in_array($noBase, $this->syncTaxonomies)) {
         return;
      }
      // Check if taxonomy is content-type
      $config = JVB_TAXONOMY[$noBase] ?? null;
      if (!$config || !($config['is_content'] ?? false)) {
      $registrar = Registrar::getInstance($noBase);
      if (!$registrar->hasFeature('is_content')) {
         return;
      }
      $settings = $config['integrations'][$this->service_name] ?? null;
      $settings = $registrar->getIntegrationConfig($this->service_name);
      if (!$settings) {
         return;
      }
@@ -2694,6 +2678,20 @@
      return $this->title;
   }
   public static function title():string
   {
      return static::getInstance()->getTitle();
   }
   public static function icon():string
   {
      return static::getInstance()->getIcon();
   }
   public static function hasExtraOptions():bool
   {
      return static::getInstance()::$hasExtraOptions;
   }
   /*********************************************************************
      RENDERING
    *********************************************************************/
@@ -2934,7 +2932,7 @@
      ?>
      <form id="<?=$this->service_name?>" class="integration <?php echo $is_connected ? 'connected' : 'disconnected'; ?>"
          data-service="<?php echo esc_attr($this->service_name); ?>">
         <div class="header row btw">
         <div class="header row x-btw">
            <h3><?php echo esc_html($this->title); ?></h3>
            <div class="setup">
               <?php if ($is_connected): ?>
@@ -2998,7 +2996,7 @@
                  $config['value'] = $credentials[$name]??'';
                  $config['autocomplete'] = 'off';
                  $config['base'] = $this->service_name.'_';
                  Form::render($name, null, $config);
                  echo Form::render($name, '', $config);
               }
            }
            if ($this->handleWebhooks) {
@@ -3045,7 +3043,7 @@
            }
            ?>
         </div>
         <div class="actions row btw wrap">
         <div class="actions row x-btw wrap">
            <?php
            foreach ($this->buttons as $action => $label) {
               if (!$is_connected && $action !== 'save_credentials') {
@@ -3283,13 +3281,16 @@
            echo Form::render($name, null, $config);
         }
         foreach ($this->syncPostTypes as $type) {
            $config = JVB_CONTENT[$type];
            $registrar = Registrar::getInstance($type);
            $icon = $registrar->getIcon();
            $icon = $icon === '' ? jvbDefaultIcon() : $icon;
            ?>
            <details>
               <summary><?= jvbIcon($config['icon']) ?><?= $config['singular']?> Defaults</summary>
               <summary><?= jvbIcon($icon) ?><?= $registrar->getSingular()?> Defaults</summary>
               <?php
               $fields = new \JVBase\registry\providers\IntegrationFieldProvider();
               $fields = $fields->getIntegrationFields($this->service_name, $config);
               $fields = new AddIntegrationFields($this->service_name);
               $fields = $fields->getIntegrationFields();
               foreach($fields as $name=>$c) {
                  $c['required'] = false;
                  if ($c['type'] === 'number') {
@@ -3326,28 +3327,10 @@
         return [];
      }
      $key = BASE.$this->service_name.'_enabled_content_types';
      $enabled = get_option($key);
      if (!$enabled) {
         $enabled = [];
         foreach (array_merge(JVB_CONTENT, JVB_TAXONOMY) as $content => $config) {
            if (!array_key_exists('integrations', $config)) {
               continue;
            }
            if (!array_key_exists($this->service_name, $config['integrations'])) {
               continue;
            }
            if (!array_key_exists('content_type', $config['integrations'][$this->service_name])) {
               continue;
            }
            $type = $config['integrations'][$this->service_name]['content_type'];
            if (!in_array($type, $enabled)) {
               $enabled[] = $type;
            }
         }
         update_option($key, $enabled);
      }
      return $enabled;
      return array_filter(array_map(function($registrar) {
         $registrar = Registrar::getInstance($registrar);
         return $registrar->getIntegration($this->service_name)->getContentType();
      }, Registrar::withIntegration($this->service_name)));
   }
   protected function getSupportedImage(int $imgID):int
@@ -3516,4 +3499,23 @@
   {
      $this->token_refresh_attempted = false;
   }
   public function getAllowedContent():array
   {
      return $this->allowedContent;
   }
   /**
    * Used by JVBase\registrar\helpers\AddIntegrationFields.php
    * @return array
    */
   public function getAdditionalFields(?string $content_type = null):array
   {
      return [];
   }
   public function getIcon():string
   {
      return $this->icon;
   }
}