From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh

---
 inc/integrations/Integrations.php | 3098 +---------------------------------------------------------
 1 files changed, 98 insertions(+), 3,000 deletions(-)

diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index ef1a135..21cf95f 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -3,15 +3,9 @@
 
 use Exception;
 use JVBase\managers\Cache;
-use JVBase\meta\Form;
-use JVBase\meta\Meta;
-use JVBase\managers\ErrorHandler;
-use JVBase\registrar\helpers\AddIntegrationFields;
+use JVBase\managers\UploadManager;
 use JVBase\registrar\Registrar;
 use WP_Error;
-use WP_Post;
-use WP_REST_Request;
-use WP_REST_Response;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -28,426 +22,80 @@
  */
 abstract class Integrations
 {
-	/**
-	 * 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';
+	use _Base;
+
 	/**
 	 * API Configuration
 	 * These properties define how the integration connects to external services
 	 */
-	protected string $service_name;      // Unique identifier for this service (e.g., 'square', 'facebook')
 	protected string|array $apiBase = ''; // Base URL(s) for API endpoints. Array format: ['base' => '', 'auth' => '']
+	/**
+	 * @var array<APIEndpoint> An array of endpoints objects, with optional create, update, delete, batch<x>, and batchImport
+	 */
 	protected array $apiEndpoints = [];   // Valid endpoint paths for this service
-	protected string $apiVersion = '';     // API version string (e.g., 'v2', '2024-01-01')
 
 	protected int $refresh_interval = 0; //seconds before expiry to refresh tokens. 0 to disable
 
-	/**
-	 * OAuth Configuration
-	 * Set these properties if your service uses OAuth authentication
-	 */
-	protected bool $isOAuthService = false;
-	protected array $oauth = [
-		'authorize' => '',    // OAuth authorization endpoint
-		'token' => '',        // Token exchange endpoint
-		'revoke' => '',       // Token revocation endpoint (optional)
-		'scopes' => [],       // Required OAuth scopes
-		'redirect_uri' => '', // OAuth callback URL
-	];
-
-	/**
-	 * Display Properties
-	 * 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
 
 	/**
 	 * Credentials & State
 	 */
-	protected array $credentials = [];      // Service credentials (API keys, tokens, etc.)
-	protected ?int $userID = null;          // User context for user-specific integrations
-	private bool $token_refresh_attempted = false;  // Circuit breaker for token refresh
-
-	protected array $fields = []; 		// The fields to generate that become credentials
-	protected array $advanced = []; 	// The fields that are optional settings
-	protected array $defaults = [];		// Default overrides
-	protected string $defaultContent = 'post'; //Default integration content type, is none is set. MUST EXIST as array key in $this->>getContentTypes
-	protected array $instructions = []; // Each item in array becomes an item in the list of instructions
-
-	protected bool $supportsWebp = true;
-	/**
-	 * Client Management
-	 * For services that benefit from persistent connections
-	 */
-	protected ?object $client = null;               // Persistent client instance
-
-	protected array $actions = [
-		'save_credentials'	=> 'saveCredentials',
-		'clear_credentials'	=> 'deleteCredentials',
-		'clear_cache'		=> 'clearCache',
-		'test_connection'	=> 'testConnection',
-	];
-
-	protected array $buttons = [
-		'save_credentials' => 'Save Changes',
-		'clear_credentials' => 'Clear Credentials',
-		'clear_cache'=> 'Clear Cache',
-		'test_connection' => 'Test Connection',
-	];
-
 	protected array $allowedContent = [];
 
 	/**
 	 * Caching Configuration
 	 */
-	protected ?string $cacheName = null;
-	protected Cache $cache;
 	protected array $cacheStrategy = [
 		'aggressive' => 3600,  // 1 hour for stable data (e.g., profile info)
 		'moderate' => 300,     // 5 minutes for semi-dynamic data (e.g., posts)
 		'minimal' => 60,       // 1 minute for frequently changing data
 		'none' => 0           // No caching for real-time data
 	];
-	protected int $ttl = 3600; // Default cache TTL in seconds
 
-	/**
-	 * Rate Limiting Configuration
-	 * Override these values based on service-specific limits
-	 */
-	protected array $rate_limits = [
-		'per_second' => 2,
-		'per_minute' => 30,
-		'per_hour' => 1000
-	];
-	protected array $oauth_rate_limits = [
-			'per_second' => 1,    // Max 1 OAuth request per second
-			'per_minute' => 10,   // Max 10 OAuth requests per minute
-			'per_hour' => 100,    // Max 100 OAuth requests per hour
-		];
-	protected array $request_history = [];
-	protected int $max_history_size = 100;
 
-	/**
-	 * Post Syncing Capabilities
-	 * Define what sync operations this integration supports
-	 */
-	protected array $canSync = [
-		'initial' => false,    // Can share new posts to the service
-		'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 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
-	/**
-	 * Error Handling Configuration
-	 */
-	protected array $lastError = [];
-	protected int $maxRetries = 3;
-	protected array $retryDelays = [1, 2, 5]; // Exponential backoff in seconds
-
-	/**
-	 * Validation Rules
-	 * Define validation rules for credentials and data
-	 */
-	protected array $validationRules = [];
-
-	/**
-	 * Error tracking properties
-	 */
-	protected array $error_stats = [
-		'total_errors' => 0,
-		'consecutive_errors' => 0,
-		'last_success' => null,
-		'error_types' => []
-	];
-
-	protected ?ErrorHandler $errorHandler = null;
-	protected int $error_threshold = 5; // Consecutive errors before marking unhealthy
-	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;
-		$this->cache = Cache::for('integrations_' . $this->cacheName, $this->ttl);
+		$this->cache = Cache::for('integrations_' . $this->cacheName);
 
-		// Load error stats from cache
-		$this->loadErrorStats();
 
 		$this->getPostTypes();
 		$this->getTaxonomies();
-		$this->setContentTypes();
+		$this->getUsers();
+
+		if (method_exists($this, 'setContentTypes')) {
+			$this->setContentTypes();
+		}
 		$this->registerHooks();
-		if ($this->isOAuthService) {
-			$this->actions['get_oauth_url'] = 'getOAuthUrlAction';
-			$this->actions['connect_oauth'] = 'handleOAuthConnect';
-            $this->actions['disconnect_oauth'] = 'handleOAuthDisconnect';
-			$this->actions['refresh_token'] = 'refreshOAuthToken';
-			$this->buttons['refresh_token'] = 'Refresh OAuth Connection';
-			$this->buttons['disconnect_oauth'] = 'Disconnect OAuth';
-		}
-		if ($this->handleWebhooks) {
-			$this->advanced['webhook_signature_key'] = [
-				'label' => 'Webhook Signature Key',
-				'type' => 'text',
-				'subtype' => 'password',
-				'placeholder' => 'Enter webhook signature key',
-				'hint' => 'Used to verify webhook authenticity (recommended)'
-			];
+
+		if (method_exists($this, 'setQueueTypes')) {
+			$this->setQueueTypes();
 		}
 
-		add_filter('jvbShouldRenderMeta', [$this, 'checkRenderField'], 10, 4);
-	}
+		$this->initializeRateLimiters();
 
-	protected function setContentTypes():void
-	{
-
-	}
-	public function getContentTypes(string $type):array
-	{
-
-		return (array_key_exists($type, $this->contentTypes)) ? $this->contentTypes[$type] : $this->contentTypes;
-	}
-
-	public function checkRenderField($shouldRender, $name, $type, $objectType):bool
-	{
-		if ($type !== 'form') {
-			return $shouldRender;
+		if (method_exists($this, 'initializeActions')) {
+			$this->initializeActions();
 		}
-
-		if (!$this->isSetUp() && str_contains($name, $this->service_name)) {
-			return false;
+		if (method_exists($this, 'addOAuthActions')) {
+			$this->addOAuthActions();
 		}
-		return $shouldRender;
-	}
-
-	public function handleOAuthConnect(): array
-	{
-		if (!$this->isOAuthService) {
-			return ['success' => false, 'message' => 'Not an OAuth service'];
-		}
-
-		// This would typically redirect to OAuth provider
-		// Child classes can override for specific behavior
-		$auth_url = $this->getOAuthUrl();
-
-		if ($auth_url) {
-			return [
-				'success' => true,
-				'redirect' => $auth_url,
-			];
-		}
-
-		return ['success' => false, 'message' => 'Failed to generate OAuth URL'];
-	}
-
-	/**
-	 * Check if OAuth token is valid and not expired
-	 * @return bool
-	 */
-	public function isOAuthValid(): bool
-	{
-		if (!$this->isOAuthService) {
-			return false;
-		}
-
-		// Check if we have tokens
-		if (empty($this->credentials['access_token'])) {
-			return false;
-		}
-
-		// Check token expiry if stored
-		if (!empty($this->credentials['expires_at'])) {
-			$expires_at = intval($this->credentials['expires_at']);
-			if ($expires_at <= time()) {
-				return false;
-			}
-		}
-
-		// For services without expiry info, do a test API call
-		return true;
-//		return $this->testConnection();
-	}
-
-
-
-	public function getOAuthUrlAction(array $data = []): array
-	{
-		if (!$this->isOAuthService) {
-			return ['success' => false, 'message' => 'Not an OAuth service'];
-		}
-
-		$return_url = $data['return_url'] ?? null;
-		$auth_url = $this->getOAuthUrl($return_url);
-
-		if ($auth_url) {
-			return [
-				'success' => true,
-				'auth_url' => $auth_url,
-				'popup' => true
-			];
-		}
-
-		return ['success' => false, 'message' => 'Failed to generate OAuth URL'];
 	}
 
 	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();
-				}
-			}
+		$this->syncPostTypes = Registrar::withIntegration($this->service_name, 'post');
+	}
 
-			update_option($key, $postTypes);
-		}
-
-		$this->syncPostTypes = $postTypes;
+	protected function getUsers():void
+	{
+		$this->syncUsers = Registrar::withIntegration($this->service_name, 'user');
 	}
 
 	protected function getTaxonomies():void
 	{
-		$key = BASE . $this->service_name . '_sync_taxonomies';
-		$taxonomies = get_option($key, false);
-
-		if (!$taxonomies) {
-			// Combine both content and taxonomy filtering
-			$taxonomies = [];
-			foreach (Registrar::withFeature('is_content', 'term') as $type) {
-				$registrar = Registrar::getInstance($type);
-				if ($registrar->hasIntegration($this->service_name)) {
-					$taxonomies[] = $registrar->getSlug();
-				}
-			}
-
-			update_option($key, $taxonomies);
-		}
-
-		$this->syncTaxonomies = $taxonomies;
-	}
-
-	protected function getRedirectUri(): string
-	{
-//		if (!empty($this->oauth['redirect_uri'])) {
-//			return $this->oauth['redirect_uri'];
-//		}
-		if ($this->isOAuthService) {
-			return admin_url('admin-ajax.php?action=' . BASE . $this->service_name . '_oauth_callback');
-		}
-		// Changed from admin-ajax.php to REST endpoint
-		return rest_url('jvb/v1/oauth/callback?service=' . $this->service_name);
-	}
-
-	/**
-	 * Used by IntegrationsRoutes.php
-	 * @param string $code
-	 * @param string $state
-	 * @return array
-	 */
-	public function handleOAuthCode(string $code, string $state): array
-	{
-		try {
-			$this->loadCredentials();
-			$tokens = $this->exchangeOAuthCode($code);
-
-			if (!$tokens) {
-				return ['success' => false, 'message' => 'Failed to exchange authorization code'];
-			}
-
-			$credentials = array_merge($this->credentials, $tokens);
-			$credentials = $this->addCredentialData($credentials, $tokens);
-			$saved = $this->saveCredentials($credentials, false);
-
-			if ($saved) {
-				return ['success' => true, 'message' => 'Successfully connected'];
-			}
-
-			return ['success' => false, 'message' => 'Failed to save credentials'];
-		} catch (Exception $e) {
-			$this->logError('OAuth code exchange failed', ['error' => $e->getMessage()]);
-			return ['success' => false, 'message' => 'OAuth error: ' . $e->getMessage()];
-		}
-	}
-
-	/**
-	 * @param string $action
-	 * @param mixed|null $data
-	 * @return array [
-	 * 		'success'	=> {bool},
-	 * 		'message'	=> {string} optional message,
-	 *		'data'		=> {mixed} optional data to return
-	 * ]
-	 */
-	public function processAction(string $action, mixed $data = null):array
-	{
-		if (!$this->checkCapabilities($action, $this->userID)) {
-			$this->logError('User cannot perform this action');
-			return [
-				'success'	=> false,
-				'message'	=> 'Insufficient permissions'
-			];
-		}
-
-		if (!array_key_exists($action, $this->actions)) {
-			$this->logError('Attempted action not set up', [
-				'action'	=> $action,
-				'stored'	=> $this->actions,
-				'service'	=> $this->service_name,
-				'method'	=> 'processAction'
-			]);
-			return [
-				'success'	=> false,
-				'message'	=> 'Invalid action'
-			];
-		}
-		if (!method_exists($this, $this->actions[$action])) {
-			$this->logError('Attempted method not set up', [
-				'attempted'	=> $this->actions['action'],
-				'service'	=> $this->service_name,
-				'method'	=> 'processAction'
-			]);
-			return [
-				'success'	=> false,
-				'message'	=> 'Action not set up'
-			];
-		}
-
-		$method = $this->actions[$action];
-		// Log the action for debugging
-		$this->logDebug("Processing action", [
-			'action' => $action,
-			'method' => $method,
-			'data' => $data
-		]);
-		if ($data !== null) {
-			$result = $this->$method($data);
-		} else {
-			$result =  $this->$method();
-		}
-		if (is_wp_error($result)) {
-			return [
-				'success' => false,
-				'message' => $result->get_error_message()
-			];
-		}
-
-		return is_array($result) ? $result : ['success' => true, 'data' => $result];
-
+		$this->syncTaxonomies = Registrar::withIntegration($this->service_name, 'term');
 	}
 
 	/*********************************************************************
@@ -466,22 +114,6 @@
 	 */
 	abstract protected function initialize(): void;
 
-	/**
-	 * Get request headers for API calls
-	 *
-	 * Return an associative array of headers required for API requests.
-	 * Common headers include Authorization, Content-Type, Accept, etc.
-	 *
-	 * Example:
-	 * return [
-	 *     'Authorization' => 'Bearer ' . $this->credentials['access_token'],
-	 *     'Content-Type' => 'application/json',
-	 *     'Accept' => 'application/json',
-	 * ];
-	 *
-	 * @return array Associative array of header names and values
-	 */
-	abstract protected function getRequestHeaders(): array;
 
 	/**
 	 * Render the connection settings form
@@ -504,46 +136,33 @@
 	 * OPTIONAL OVERRIDE METHODS - IMPLEMENT AS NEEDED
 	 *********************************************************************/
 	/**
-	 * Save credentials using CredentialsManager
+	 * Save credentials using Auth
 	 */
 	public function saveCredentials(array $credentials, bool $test = true):array
 	{
 		try {
 			// Process and validate credentials
 			if (!$this->validateCredentials($credentials)) {
-				$this->logError('Invalid credentials');
-				return [
-					'success'	=> false,
-					'message'	=> 'Credentials not formatted correctly'
-				];
+				$this->logError('saveCredentials', 'Invalid credentials');
+				return $this->response(false, 'Credentials not formatted correctly');
 			}
 
+			//Merge the new credentials with the old ones (new stuff overriding any old stuff)
 			$old = $this->getCredentials();
 			$credentials = array_merge($old, $credentials);
 
-			// Temporarily set credentials for testing
-			$this->credentials = $credentials;
-			$this->initialize();
-
 			// Test the connection before saving
 			if ($test) {
 				if (!$this->testConnection(true)) {
-					$this->logError('Connection test failed');
-					// Revert to old credentials
-					$this->credentials = $old;
-					$this->initialize();
+					$this->logError('saveCredentials', 'Connection test failed');
 
-					return [
-						'success' => false,
-						'message' => 'Connection failed. Please check your credentials.',
-						'test_failed' => true
-					];
+					return $this->response(false, 'Connection failed. Please check your credentials');
 				}
 			}
 
 
 			// Connection successful, save credentials
-			$stored = CredentialsManager::getInstance()->storeCredentials(
+			$stored = Auth::getInstance()->storeCredentials(
 				$this->service_name,
 				$credentials,
 				$this->userID
@@ -553,31 +172,18 @@
 				$this->updateLastTestedTime();
 				$this->clearCache();
 
-				return [
-					'success' => true,
-					'message' => 'Credentials validated and saved successfully',
-					'reload' => true
-				];
+				return $this->response(true, 'Credentials validated and saved successfully');
 			}
 
-			return [
-				'success' => false,
-				'message' => 'Failed to save credentials to database'
-			];
+			return $this->response(false, 'Failed to save credentials to database');
 
 		} catch (\Exception $e) {
 			// Revert to old credentials on any error
-			$old = CredentialsManager::getInstance()->getCredentials(
+			$old = Auth::getInstance()->getCredentials(
 				$this->service_name,
 				$this->userID
 			);
-			$this->credentials = $old;
-			$this->initialize();
-
-			return [
-				'success' => false,
-				'message' => 'Validation error: ' . $e->getMessage()
-			];
+			return $this->response(false, 'Validation error: '.print_r($e->getMessage(), true));
 		}
 	}
 
@@ -586,10 +192,8 @@
 	 */
 	public function deleteCredentials():array
 	{
-		$success = CredentialsManager::getInstance()->deleteCredentials($this->service_name, $this->userID);
-		return [
-			'success'	=> $success
-		];
+		$success = Auth::getInstance()->deleteCredentials($this->service_name, $this->userID);
+		return $this->response(true, 'Deleted successfully');
 	}
 	/**
 	 * Validate credentials before storing
@@ -659,7 +263,7 @@
 		}
 
 		try {
-			if ($this->isOAuthService && !$this->hasOAuthCredentials()){
+			if (!$this->hasOAuthCredentials()){
 				//If this is an OAuth service, we might only be saving the app credentials first
 				$result = true;
 			} else {
@@ -678,9 +282,9 @@
 
 	protected function clearCache():array
 	{
-		$success = $this->cache->flush();
+		$this->cache->flush();
 		return [
-			'success'	=> $success,
+			'success'	=> true,
 		];
 	}
 
@@ -716,319 +320,6 @@
 	/******************************************************************
 		POST SYNC
 	 ******************************************************************/
-	/**
-	 * Handle post save for syncing
-	 *
-	 * Override to implement custom sync logic when posts are saved.
-	 * Check the $settings array for post type specific configuration.
-	 *
-	 * @param int $postID The post ID
-	 * @param WP_Post $post The post object
-	 * @param bool $update Whether this is an update
-	 * @param array $settings Post type integration settings
-	 * @return void
-	 */
-	protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void
-	{
-		// Override in child classes that support post syncing
-		// Example implementation:
-		/*
-		$fields = $this->getSyncFields($postID, 'post', ['schedule_' . $this->service_name]);
-		$options = $fields['schedule_' . $this->service_name] !== ''
-			? ['scheduled' => strtotime($fields['schedule_' . $this->service_name])]
-			: [];
-
-		$this->queueOperation(
-			'sync_post',
-			['post_id' => $postID, 'is_update' => $update],
-			$options
-		);
-		*/
-	}
-
-	/*********************************************************************
-	 * API REQUEST METHODS
-	 *********************************************************************/
-
-	/**
-	 * Make an API request with automatic retry and error handling
-	 * @param string $method
-	 * @param string $endpoint
-	 * @param array $data
-	 * @param string|null $baseKey array key for the $this->apiEndpoints
-	 * @param array $options
-	 * @return WP_Error|array
-	 */
-	protected function makeRequest(
-		string $method,
-		string $endpoint,
-		array  $data = [],
-		?string $baseKey = null,
-		array  $options = []
-	): array|WP_Error
-	{
-		if (!$this->is_healthy) {
-			return new WP_Error('unhealthy', 'Connection marked unhealthy. Skipping fetch');
-		}
-		$this->ensureInitialized();
-		if (!$this->isSetUp()){
-			$this->logError('Connection not setup for '.$this->service_name, [
-				'method' => 'makeRequest',
-			]);
-			return new WP_Error('Error', 'Connection is not set up to make request');
-		}
-
-		// Rate limiting check
-		if (!$this->checkRateLimit()) {
-			return new WP_Error('rate_limit', 'Rate limit exceeded. Please try again later.');
-		}
-
-
-		$attempt = 0;
-		$lastError = null;
-
-		while ($attempt < $this->maxRetries) {
-			try {
-//				$this->logDebug('[Integrations] Making request to '.$this->service_name);
-				$result = $this->executeRequest($method, $endpoint, $data, $baseKey, $options);
-				if (!$result) {
-					return new WP_Error('Request Error');
-				}
-				$this->recordRequest($method, $endpoint);
-//				$this->logDebug('[Integrations]', [
-//					'response' => $result
-//				]);
-				// Reset error stats on success
-				$this->resetErrorStats();
-				return $result;
-
-			} catch (Exception $e) {
-				$lastError = $e;
-				$this->recordRequest($method, $endpoint);
-				$attempt++;
-
-				// Retry with backoff for server errors
-				if ($attempt < $this->maxRetries && !$this->isClientError($e)) {
-					$delay = pow(2, $attempt) * 1000000; // 2^attempt seconds in microseconds
-					$jitter = rand(0, $delay * 0.3); // Add 0-30% jitter
-					usleep($delay + $jitter);
-				} else {
-					break;
-				}
-
-			}
-		}
-
-		$this->logError('API request failed after retries', [
-			'method' => $method,
-			'endpoint' => $endpoint,
-			'attempts' => $attempt,
-			'error' => $lastError ? $lastError->getMessage() : 'Unknown error'
-		]);
-
-		return new WP_Error('api_error', $lastError ? $lastError->getMessage() : 'API request failed');
-	}
-
-	/**
-	 * Execute the actual request
-	 */
-	protected function executeRequest(
-		string $method,
-		string $endpoint,
-		array  $data = [],
-		?string $baseKey = null,
-		array  $options = []
-	): ?array
-	{
-		$args = $this->buildRequestArgs($method, $data, $options);
-		$url = $this->getApiUrl($endpoint, $baseKey);
-		if (!$url) {
-			return null;
-		}
-
-//		$this->logDebug("$method request to: $url: ".print_r($args, true));
-
-		// Make the request
-		$response = match($method) {
-			'GET' => wp_remote_get($url, $args),
-			'POST' => wp_remote_post($url, $args),
-			'PUT', 'PATCH', 'DELETE' => wp_remote_request($url, array_merge($args, ['method' => $method])),
-			default => null
-		};
-
-		if (!$response) {
-			$this->logError("Unsupported HTTP method $method for $this->service_name");
-			return null;
-		}
-
-		if (is_wp_error($response)) {
-			$this->logError("Error for: $this->service_name in executeRequest", ['message' => $response->get_error_message()]);
-			return null;
-		}
-
-		$response_code = wp_remote_retrieve_response_code($response);
-		$body = wp_remote_retrieve_body($response);
-
-		// Handle 401 - try to refresh token and retry once
-		if ($response_code === 401 && $this->isOAuthService && !empty($this->credentials['refresh_token'])) {
-			// Avoid infinite retry loop - only retry once
-			static $retry_count = 0;
-
-			if ($retry_count === 0) {
-				$retry_count++;
-
-//				$this->logDebug('Got 401, attempting token refresh...');
-				if ($this->refreshOAuthToken()) {
-//					$this->logDebug('Token refreshed successfully, retrying request...');
-
-					// Rebuild request args with new token
-					$args = $this->buildRequestArgs($method, $data, $options);
-
-					// Retry the request
-					$response = match($method) {
-						'GET' => wp_remote_get($url, $args),
-						'POST' => wp_remote_post($url, $args),
-						'PUT', 'PATCH', 'DELETE' => wp_remote_request($url, array_merge($args, ['method' => $method])),
-						default => null
-					};
-
-					if ($response && !is_wp_error($response)) {
-						$response_code = wp_remote_retrieve_response_code($response);
-						$body = wp_remote_retrieve_body($response);
-					}
-				}
-				$retry_count = 0; // Reset for next request
-			}
-		}
-
-		if ($response_code >= 400) {
-			$this->handleApiError($response_code, $body, $endpoint);
-		}
-
-		$decoded = json_decode($body, true);
-		if (json_last_error() !== JSON_ERROR_NONE) {
-			return ['raw_response' => $body];
-		}
-
-		$this->updateLastTestedTime();
-
-		return $decoded;
-	}
-
-	/**
-	 * GET request with caching
-	 */
-	protected function getRequest(
-		string $endpoint,
-		array  $params = [],
-		?string $baseKey = null,
-		string $cacheStrategy = 'moderate',
-		bool   $force = false
-	): ?array
-	{
-		$cacheKey = $this->buildCacheKey('GET', $endpoint, $params, $baseKey);
-
-		$ttl = is_int($cacheStrategy)
-			? max(0, $cacheStrategy)
-			: ($this->cacheStrategy[$cacheStrategy] ?? $this->ttl);
-
-		if (!$force && $ttl > 0) {
-			$cached = $this->cache->get($cacheKey);
-			if ($cached !== false) {
-
-//				$this->logDebug("Cache hit for: $cacheKey");
-				return $cached;
-			}
-		}
-
-		$result = $this->makeRequest('GET', $endpoint, $params, $baseKey);
-
-		// Only cache successful responses (not WP_Error and not error objects)
-		if ($result && !is_wp_error($result) && !$this->isErrorResponse($result) && $ttl > 0) {
-			$this->cache->set($cacheKey, $result, $ttl);
-		}
-
-		return $result;
-	}
-	/**
-	 * Check if response contains an error
-	 * Override in child classes for service-specific error detection
-	 */
-	protected function isErrorResponse(array $response): bool
-	{
-		// Common error patterns across APIs
-		return isset($response['error'])
-			|| isset($response['errors'])
-			|| isset($response['error_description']);
-	}
-
-	/**
-	 * POST request
-	 */
-	protected function postRequest(string $endpoint, array $data = [], ?string $baseKey = null): ?array
-	{
-		return $this->makeRequest('POST', $endpoint, $data, $baseKey);
-	}
-
-	/**
-	 * PUT request
-	 */
-	protected function putRequest(string $endpoint, array $data = [], ?string $baseKey = null): ?array
-	{
-		return $this->makeRequest('PUT', $endpoint, $data, $baseKey);
-	}
-
-	/**
-	 * DELETE request
-	 */
-	protected function deleteRequest(string $endpoint, array $data = [], ?string $baseKey = null): ?array
-	{
-		return $this->makeRequest('DELETE', $endpoint, $data, $baseKey);
-	}
-
-	/**
-	 * PATCH request
-	 */
-	protected function patchRequest(string $endpoint, array $data = [], ?string $baseKey = null): WP_Error|array
-	{
-		return $this->makeRequest('PATCH', $endpoint, $data, $baseKey);
-	}
-
-
-
-	/**
-	 * Build request arguments
-	 */
-	protected function buildRequestArgs(string $method, array $data, array $options): array
-	{
-		$args = [
-			'method' => $method,
-			'headers' => $this->getRequestHeaders(),
-			'timeout' => $options['timeout'] ?? 30,
-			'sslverify' => $options['sslverify'] ?? true,
-			'redirection' => $options['redirection'] ?? 5,
-			'blocking' => $options['blocking'] ?? true,
-			'compress' => $options['compress'] ?? true,
-			'decompress' => $options['decompress'] ?? true,
-		];
-
-		if ($method === 'GET' && !empty($data)) {
-			// Add query parameters for GET requests
-			$args['body'] = $data;
-		} elseif (!empty($data)) {
-			$content_type = $args['headers']['Content-Type'] ?? 'application/json';
-
-			if (str_contains($content_type, 'application/json')) {
-				$args['body'] = json_encode($data);
-			} elseif (str_contains($content_type, 'application/x-www-form-urlencoded')) {
-				$args['body'] = http_build_query($data);
-			} else {
-				$args['body'] = $data;
-			}
-		}
-
-		return $args;
-	}
 
 	/*********************************************************************
 	 * SYNC METHODS
@@ -1047,13 +338,10 @@
 		array $data,
 		array $options = []
 	):bool {
-		$queue = JVB()->queue();
-
-		// Add service prefix to operation type
-		$operation_type = strtolower($this->service_name) . '_' . $type;
+		$queue = Queue::getInstance();
 
 		$queued =  $queue->queueOperation(
-			$operation_type,
+			$type,
 			$this->userID ?? 0,
 			array_merge($data, ['service' => $this->service_name, 'user' => $this->userID??0]),
 			array_merge([
@@ -1095,118 +383,29 @@
 	 *********************************************************************/
 
 	/**
-	 * Get cache duration based on endpoint
-	 *
-	 * @param string $endpoint The API endpoint
-	 * @return int Cache duration in seconds
-	 */
-	protected function getCacheDuration(string $endpoint): int
-	{
-		// Override in child classes for endpoint-specific caching
-		// Example: user profiles = aggressive, posts = moderate, analytics = minimal
-		return $this->ttl;
-	}
-
-	/**
-	 * Check if we're within rate limits
-	 *
-	 * @return bool True if within limits
-	 */
-	protected function checkRateLimit(): bool
-	{
-		$now = time();
-
-		// Clean old history
-		$this->request_history = array_filter($this->request_history, function($timestamp) use ($now) {
-			return ($now - $timestamp) < 3600; // Keep last hour
-		});
-
-		// Trim to max size
-		if (count($this->request_history) > $this->max_history_size) {
-			$this->request_history = array_slice($this->request_history, -$this->max_history_size);
-		}
-
-		// Check limits
-		$recent_counts = [
-			'second' => 0,
-			'minute' => 0,
-			'hour' => count($this->request_history)
-		];
-
-		foreach ($this->request_history as $timestamp) {
-			if ($now - $timestamp <= 1) $recent_counts['second']++;
-			if ($now - $timestamp <= 60) $recent_counts['minute']++;
-		}
-
-		if ($recent_counts['second'] >= $this->rate_limits['per_second']) return false;
-		if ($recent_counts['minute'] >= $this->rate_limits['per_minute']) return false;
-		if ($recent_counts['hour'] >= $this->rate_limits['per_hour']) return false;
-
-		return true;
-	}
-
-	protected function checkOAuthRateLimit(): bool
-	{
-		$cache_key = 'oauth_rate_limit_' . $this->service_name . '_' . $this->userID;
-		$attempts = get_transient($cache_key) ?: [];
-
-		$now = time();
-
-		// Clean old attempts
-		$attempts = array_filter($attempts, function($timestamp) use ($now) {
-			return ($now - $timestamp) < 3600; // Keep last hour
-		});
-
-		// Check per-minute limit
-		$recent = array_filter($attempts, function($timestamp) use ($now) {
-			return ($now - $timestamp) < 60;
-		});
-
-		if (count($recent) >= 5) { // Max 5 OAuth attempts per minute
-			return false;
-		}
-
-		// Add current attempt
-		$attempts[] = $now;
-
-		// Save updated attempts
-		set_transient($cache_key, $attempts, 3600);
-
-		return true;
-	}
-
-
-	/**
-	 * Record a request for rate limiting
-	 *
-	 * @param string $method HTTP method
-	 * @param string $endpoint API endpoint
-	 * @return void
-	 */
-	protected function recordRequest(string $method, string $endpoint): void
-	{
-		$this->request_history[] = time();
-	}
-
-
-
-	/**
 	 * Register WordPress hooks based on capabilities
 	 */
 	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 (method_exists($this, 'registerOAuthCallbacks')) {
+			$this->registerOAuthCallbacks();
+		}
+		if (method_exists($this, 'registerWebhookEndpoint')) {
 			$this->registerWebhookEndpoint();
 		}
 		// Let child classes register additional hooks if needed
 		$this->registerAdditionalHooks();
 
+
 		if (!empty($this->syncPostTypes)) {
-			add_action('save_post', [$this, 'handleSavePost'], 20, 3);
-			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
+			if (method_exists($this, 'addSavePost')) {
+				$this->addSavePost();
+			}
+
+//			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
 
 			if ($this->canSync['delete']) {
 				add_action('before_delete_post', [$this, 'handleDeletePost'], 10, 1);
@@ -1218,64 +417,16 @@
 				add_action('pre_delete_term', [$this, 'handleDeleteTerm'], 10, 2);
 			}
 		}
+
+		if (method_exists($this, 'registerQueueTypes')) {
+			add_action('init', [$this, 'registerQueueTypes'], 10);
+		}
 	}
 
-	public function handleAjaxResponse()
-	{
-
-		$code = $_GET['code'];
-		$state =  $_GET['state'];
 
 
-		$state_parts = explode('|', $state);
-		$state_key = $state_parts[0] ?? '';
-		$user_id = intval($state_parts[1] ?? 0);
-		$user_id = ($user_id === 0) ? null : $user_id;
-		$return_url = isset($state_parts[2]) ? base64_decode($state_parts[2]) : admin_url('admin.php?page=jvb-integrations');
-
-		$state_data = get_transient('oauth_state_' . $state_key);
-		if (!$state_data || $state_data['service'] !== $this->service_name) {
-			wp_die('Invalid state parameter', 'OAuth Error');
-		}
-
-		// Delete the transient to prevent reuse
-		delete_transient('oauth_state_' . $state_key);
-		// Handle error from OAuth provider
-		if (array_key_exists('error', $_GET)) {
-			$error_description = $_GET['error_description'] ?? 'Authorization denied';
-
-			wp_redirect(add_query_arg([
-				'error' => 'OAuth authorization denied: ' . $error_description
-			], $return_url));
-			exit;
-		}
-
-		// Get integration instance
-		$integration = JVB()->connect($this->service_name, $user_id);
-
-		if (!$integration) {
-			wp_die('Invalid service: ' . esc_html($this->service_name), 'OAuth Error');
-		}
 
 
-		// Exchange code for tokens
-		$result = $integration->handleOAuthCode($code, $state);
-
-		// Redirect back with result
-		if ($result['success']) {
-			wp_redirect(add_query_arg([
-				'success' => 'Successfully connected to ' . $integration->title
-			], $return_url));
-		} else {
-			// Handle failure
-			$error_message = $result['message'] ?? 'Failed to complete OAuth authorization';
-
-			wp_redirect(add_query_arg([
-				'error' => $error_message
-			], $return_url));
-		}
-		exit;
-	}
 	/**
 	 * Handle connection setup and credential storage
 	 *
@@ -1296,11 +447,11 @@
 			}
 
 			// Store credentials
-			$this->credentials = array_merge($this->credentials ?? [], $sanitized);
-			$this->credentials['last_updated'] = time();
+			$credentials = array_merge($this->loadCredentials() ?? [], $sanitized);
+			$credentials['last_updated'] = time();
 
 			// Save to database
-			$saved = $this->saveCredentials($this->credentials);
+			$saved = $this->saveCredentials($credentials);
 
 			if (!$saved) {
 				return [
@@ -1333,699 +484,31 @@
 		}
 	}
 
-	/**
-	 * Load credentials from secure storage
-	 */
-	protected function loadCredentials(): void
-	{
-		$this->credentials = CredentialsManager::getInstance()->getCredentials($this->service_name, $this->userID);
-	}
-
 	public function getCredentials(): array
 	{
-		$this->ensureInitialized();
-		return $this->credentials;
+		return $this->loadCredentials();
 	}
 
-	/**
-	 * Check if integration is properly configured
-	 */
-	public function isSetUp(): bool
+
+
+	public function hasOAuthCredentials(?int $userID = null): bool
 	{
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-		return !empty($this->credentials);
-	}
-
-	public function hasOAuthCredentials(): bool
-	{
-		if (!$this->isOAuthService){
-			return false;
-		}
-
-		$required = ['client_id', 'client_secret', 'access_token'];
-
-		foreach ($required as $field) {
-			if (empty($this->credentials[$field]) || !is_string($this->credentials[$field]) || $this->credentials[$field] === '') {
-				return false;
-			}
-		}
-		return true;
+		return false;
 	}
 
 
 	/**
 	 * Ensure service is initialized
+	 * TODO: I don't think this is necessary anymore.
 	 */
 	protected function ensureInitialized(): void
 	{
 		if (!$this->isSetUp()){
 			return;
 		}
-		if (empty($this->credentials)) {
-			$this->loadCredentials();
-		}
-
-		if (!empty($this->credentials)) {
-			if ($this->isOAuthService && $this->hasOAuthCredentials()) {
-				// Check if token is expired first
-				if (!$this->isOAuthValid()) {
-					// Only attempt refresh once per request
-					if (!$this->token_refresh_attempted) {
-						$this->token_refresh_attempted = true;
-//						$this->logDebug('OAuth token expired, attempting refresh');
-
-						if (!$this->refreshOAuthToken()) {
-							$this->logError('Failed to refresh expired OAuth token - stopping execution');
-							// Token refresh failed - DO NOT continue making API requests
-							return;
-						}
-					} else {
-						// Already attempted refresh in this request
-//						$this->logDebug('Token refresh already attempted, skipping');
-						return;
-					}
-				}
-				// Check if we should proactively refresh (before expiry)
-				elseif ($this->shouldRefreshToken() && !$this->token_refresh_attempted) {
-					$this->token_refresh_attempted = true;
-//					$this->logDebug('OAuth token should be refreshed proactively');
-					if (!$this->refreshOAuthToken()) {
-						$this->logError('Failed to proactively refresh OAuth token');
-						// Not critical - token is still valid, so continue
-					}
-				}
-			}
-			$this->initialize();
-		}
+		$this->initialize();
 	}
 
-
-	/**
-	 * 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
-	 ***************************************************************/
-	/**
-	 * Handle error responses
-	 */
-	protected function handleApiError(int $code, string $body, string $endpoint): void
-	{
-		$message = "API Error ({$code}): ";
-		$decoded = json_decode($body, true);
-
-		// Extract error details
-		$error_details = $this->extractErrorDetails($decoded, $body);
-		$message .= $error_details['message'];
-
-		// Determine error severity based on HTTP code
-		$severity = $this->getErrorSeverity($code);
-
-		// Build comprehensive error context
-		$error_context = [
-			'service' => $this->service_name,
-			'endpoint' => $endpoint,
-			'http_code' => $code,
-			'error_type' => $this->categorizeApiError($code),
-			'error_details' => $error_details,
-			'user_id' => $this->userID,
-			'request_time' => time(),
-			'consecutive_errors' => $this->error_stats['consecutive_errors'],
-			'is_oauth' => $this->isOAuthService,
-			'api_version' => $this->apiVersion,
-			'integration_healthy' => $this->is_healthy
-		];
-
-		// Add rate limit information if present
-		if ($code === 429) {
-			$error_context['rate_limit'] = $this->extractRateLimitInfo($decoded, $body);
-		}
-
-		// Log to ErrorHandler with proper severity
-		$this->logError($message, $error_context, $severity);
-
-		// Update error statistics
-		$this->updateErrorStats($code, $endpoint);
-
-		// Check if integration should be marked unhealthy
-		$this->checkIntegrationHealth();
-
-		// Store last error for debugging
-		$this->lastError = [
-			'code' => $code,
-			'message' => $message,
-			'endpoint' => $endpoint,
-			'timestamp' => time(),
-			'context' => $error_context
-		];
-	}
-
-	/**
-	 * Check if error is a client error (4xx)
-	 */
-	protected function isClientError(Exception $e): bool
-	{
-		$code = $e->getCode();
-		return $code >= 400 && $code < 500;
-	}
-
-	/**
-	 * Get last error details
-	 */
-	public function getLastError(): array
-	{
-		return $this->lastError;
-	}
-
-
-	/*****************************************************************
-		OAUTH
-	 *****************************************************************/
-	/**
-	 * Check user capabilities for specific actions
-	 * Can be overridden by child classes for custom logic
-	 */
-	protected function checkCapabilities(string $action, ?int $userID = null): bool
-	{
-		// Default: require manage_options for admin actions
-		$admin_actions = ['disconnect', 'update_credentials', 'clear_cache'];
-
-		if (in_array($action, $admin_actions)) {
-			return current_user_can('manage_options');
-		}
-
-		// User-specific actions
-		return is_user_logged_in();
-	}
-
-	/*****************************************************************
-		OAUTH
-	 *****************************************************************/
-	protected function initializeOAuth():void
-	{
-		$this->oauth = array_merge([
-			'authorize'	=> '',
-			'token'		=> '',
-			'revoke'	=> '',
-			'scopes'	=> [],
-			'redirect_uri' => admin_url('admin-ajax.php?action=' . $this->service_name . '_oauth_callback')
-		], $this->oauth);
-	}
-
-	/**
-	 * Get OAuth authorization URL
-	 */
-	public function getOAuthUrl(?string $return_url = null): string
-	{
-
-		if (!$this->isOAuthService) {
-			return '';
-		}
-
-		if (empty($this->credentials)) {
-			$this->ensureInitialized();
-		}
-
-		if (empty($this->oauth['authorize'])) {
-			$this->logError('OAuth authorize URL not configured');
-			return '';
-		}
-
-		// Build base parameters
-		$params = [
-			'client_id' => $this->credentials['client_id'] ?? $this->credentials['app_id'] ?? '',
-			'redirect_uri' => $this->getRedirectUri(),
-			'response_type' => 'code',
-			'scope' => implode(' ', $this->oauth['scopes'] ?? [])
-		];
-		$state_key = wp_generate_password(32, false);
-		$user_id = $this->userID??0;
-
-		// Store state data in transient (expires in 10 minutes)
-		set_transient(
-			'oauth_state_' . $state_key,
-			[
-				'service' => $this->service_name,
-				'user_id' => $user_id,
-				'created' => time()
-			],
-			600
-		);
-
-		$state_parts = [
-			$state_key,
-			$user_id
-		];
-
-		// Add return URL if provided
-		if ($return_url) {
-			$state_parts[] = base64_encode($return_url);
-		} else {
-			$state_parts[] = base64_encode(admin_url('admin.php?page=jvb-integrations'));
-		}
-
-		$params['state'] = implode('|', $state_parts);
-
-		// Allow child classes to modify params (they can override/remove as needed)
-		if (method_exists($this, 'addOAuthParams')) {
-			$params = $this->addOAuthParams($params);
-		}
-
-		return $this->oauth['authorize'] . '?' . http_build_query($params);
-	}
-
-	/**
-	 * Add service-specific OAuth parameters
-	 */
-	protected function addOAuthParams(array $params): array
-	{
-		// Override in child classes to add service-specific params
-		// e.g., access_type, prompt, etc.
-		return $params;
-	}
-
-	protected function makeOAuthRequest(string $method, string $url, array $data = []): array|WP_Error
-	{
-		// Check rate limits before making request
-		if (!$this->checkOAuthRateLimit()) {
-			// For OAuth, we might want to be more lenient with rate limits
-			// since these are critical authentication flows
-			$this->logError('OAuth request rate limited, waiting before retry');
-			sleep(2); // Brief pause before critical OAuth request
-
-			// Check again after wait
-			if (!$this->checkRateLimit()) {
-				return new WP_Error(
-					'rate_limit',
-					'OAuth rate limit exceeded. Please try again in a few moments.'
-				);
-			}
-		}
-
-		// Record the request for rate limiting
-		$this->recordRequest($method, $url);
-
-		// Build OAuth-specific headers
-		$headers = [
-			'Content-Type' => 'application/json',
-			'Accept' => 'application/json',
-		];
-
-		// Add API version for services that require it (like Square)
-		if (!empty($this->apiVersion)) {
-			$headers['Square-Version'] = $this->apiVersion;
-		}
-
-		// Build request arguments
-		$args = [
-			'method' => $method,
-			'headers' => $headers,
-			'timeout' => 30,
-			'sslverify' => true,
-			'body' => json_encode($data)
-		];
-
-		// Make the request with retry logic
-		$attempt = 0;
-		$max_attempts = 2; // Fewer retries for OAuth to avoid code expiry
-
-		while ($attempt < $max_attempts) {
-			$attempt++;
-
-			// Make the actual request
-			$response = wp_remote_request($url, $args);
-
-			// Check for WordPress errors
-			if (is_wp_error($response)) {
-				if ($attempt >= $max_attempts) {
-					return $response;
-				}
-
-				// Wait before retry
-				sleep($this->retryDelays[$attempt - 1] ?? 1);
-				continue;
-			}
-
-			// Get response code and body
-			$code = wp_remote_retrieve_response_code($response);
-			$body = wp_remote_retrieve_body($response);
-
-			// Parse JSON response
-			$data = json_decode($body, true);
-
-			// Check for successful response
-			if ($code >= 200 && $code < 300) {
-				// Reset error stats on success
-				$this->resetErrorStats();
-
-				// Return parsed data
-				return $data ?: [];
-			}
-
-			// Handle specific OAuth error codes
-			if ($code === 401) {
-				// Invalid credentials - don't retry
-				$this->handleApiError($code, $body, $url);
-				return new WP_Error('invalid_credentials', 'Invalid OAuth credentials');
-			}
-
-			if ($code === 429) {
-				// Rate limited - wait longer before retry
-				if ($attempt < $max_attempts) {
-					$retry_after = $this->extractRetryAfter($response);
-					sleep($retry_after ?: 5);
-					continue;
-				}
-			}
-
-			// Check if we should retry
-			if ($attempt >= $max_attempts || $code >= 400 && $code < 500) {
-				// Client errors (4xx) typically shouldn't be retried
-				$this->handleApiError($code, $body, $url);
-
-				$error_message = $data['error_description'] ??
-					$data['error'] ??
-					"OAuth request failed with status {$code}";
-
-				return new WP_Error('oauth_error', $error_message, ['code' => $code]);
-			}
-
-			// Server error - retry after delay
-			sleep($this->retryDelays[$attempt - 1] ?? 1);
-		}
-
-		// Shouldn't reach here, but handle it
-		return new WP_Error('oauth_error', 'OAuth request failed after retries');
-	}
-
-	/**
-	 * Extract retry-after header from response
-	 *
-	 * @param array|WP_Error $response WordPress HTTP response
-	 * @return int Seconds to wait before retry
-	 */
-	protected function extractRetryAfter($response): int
-	{
-		if (is_wp_error($response)) {
-			return 5;
-		}
-
-		$headers = wp_remote_retrieve_headers($response);
-
-		if (isset($headers['retry-after'])) {
-			// Could be seconds or HTTP date
-			$retry_after = $headers['retry-after'];
-
-			if (is_numeric($retry_after)) {
-				return (int) $retry_after;
-			}
-
-			// Try to parse as date
-			$timestamp = strtotime($retry_after);
-			if ($timestamp !== false) {
-				return max(0, $timestamp - time());
-			}
-		}
-
-		return 5; // Default wait time
-	}
-
-	/**
-	 * Exchange OAuth code for tokens
-	 */
-	protected function exchangeOAuthCode(string $code): ?array
-	{
-		$this->ensureInitialized();
-
-		// Build request data
-		$request_data = [
-			'client_id' => $this->credentials['client_id'] ?? '',
-			'client_secret' => $this->credentials['client_secret'] ?? '',
-			'code' => $code,
-			'grant_type' => 'authorization_code',
-			'redirect_uri' => $this->getRedirectUri()
-		];
-
-		$oauth_endpoint = $this->oauth['token'];
-		$response = $this->makeOAuthRequest('POST', $oauth_endpoint, $request_data);
-
-		if (is_wp_error($response)) {
-			$this->logError('OAuth token exchange failed', [
-				'error' => $response->get_error_message(),
-				'code' => $response->get_error_code()
-			]);
-			return null;
-		}
-
-		// Parse response
-		if (isset($response['access_token'])) {
-			$expires_in = $response['expires_in'] ?? 2592000; // 30 days default
-
-			return [
-				'access_token' => $response['access_token'],
-				'refresh_token' => $response['refresh_token'] ?? '',
-				'expires_in' => $expires_in,
-				'expires_at' => time() + $expires_in, // Calculate expiry timestamp
-				'token_type' => $response['token_type'] ?? 'Bearer',
-				'merchant_id' => $response['merchant_id'] ?? '',
-				'scope' => $response['scope'] ?? ''
-			];
-		}
-
-		$this->logError('Failed to obtain access token', ['response' => $response]);
-		return null;
-	}
-
-	/**
-	 * Add service-specific credential data
-	 */
-	protected function addCredentialData(array $credentials, array $tokens): array
-	{
-		// Override in child classes to add service-specific data
-		return $credentials;
-	}
-
-	/**
-	 * Check if token should be proactively refreshed
-	 * Different from isOAuthValid() which checks if token is actually expired
-	 */
-	protected function shouldRefreshToken(): bool
-	{
-		if (!$this->isOAuthService || $this->refresh_interval === 0) {
-			return false;
-		}
-
-		// If no expiry info, we can't proactively refresh
-		if (empty($this->credentials['expires_at'])) {
-			return false;
-		}
-
-		$expires_at = intval($this->credentials['expires_at']);
-		$time_until_expiry = $expires_at - time();
-
-		// Refresh if we're within the refresh interval window
-		return $time_until_expiry > 0 && $time_until_expiry <= $this->refresh_interval;
-	}
-	/**
-	 * Get time until token refresh is recommended
-	 * Useful for displaying in admin UI
-	 */
-	public function getTimeUntilRefresh(): ?int
-	{
-		if ($this->refresh_interval === 0 || empty($this->credentials['expires_at'])) {
-			return null;
-		}
-
-		$expires_at = intval($this->credentials['expires_at']);
-		$refresh_at = $expires_at - $this->refresh_interval;
-		$time_until_refresh = $refresh_at - time();
-
-		return max(0, $time_until_refresh);
-	}
-
-	/**
-	 * Get token freshness status
-	 * Returns: 'fresh', 'should_refresh', 'expired', or 'no_expiry_info'
-	 */
-	public function getTokenStatus(): string
-	{
-		if (!$this->isOAuthService) {
-			return 'not_oauth';
-		}
-
-		if (empty($this->credentials['access_token'])) {
-			return 'no_token';
-		}
-
-		if (empty($this->credentials['expires_at'])) {
-			return 'no_expiry_info';
-		}
-
-		$expires_at = intval($this->credentials['expires_at']);
-		$now = time();
-
-		if ($expires_at <= $now) {
-			return 'expired';
-		}
-
-		if ($this->shouldRefreshToken()) {
-			return 'should_refresh';
-		}
-
-		return 'fresh';
-	}
-	/**
-	 * Refresh OAuth token
-	 */
-	protected function refreshOAuthToken(): bool
-	{
-		if (!$this->isOAuthService || empty($this->credentials['refresh_token'])) {
-			return false;
-		}
-
-		$request_data = [
-			'client_id' => $this->credentials['client_id'],
-			'client_secret' => $this->credentials['client_secret'],
-			'refresh_token' => $this->credentials['refresh_token'],
-			'grant_type' => 'refresh_token'
-		];
-
-		$response = $this->makeOAuthRequest('POST', $this->oauth['token'], $request_data);
-
-		if (is_wp_error($response)) {
-			$error_message = $response->get_error_message();
-
-			if (str_contains($error_message, 'invalid_grant')) {
-				$this->logError('OAuth refresh token is invalid - user must re-authorize', [
-					'error' => $error_message
-				], 'critical');
-
-				// Mark unhealthy immediately
-				$this->error_stats['consecutive_errors'] = $this->error_threshold;
-				$this->is_healthy = false;
-				$this->saveErrorStats();
-			}
-
-			$this->logError('Failed to refresh OAuth token for '.$this->service_name, [
-				'error' => $error_message
-			]);
-			return false;
-		}
-
-		if (isset($response['access_token'])) {
-			$this->credentials['access_token'] = $response['access_token'];
-			$this->credentials['expires_at'] = time() + ($response['expires_in'] ?? 2592000); // 30 days
-
-			// Note: Some services return the SAME refresh token
-			if (isset($response['refresh_token'])) {
-				$this->credentials['refresh_token'] = $response['refresh_token'];
-			}
-
-			$this->saveCredentials($this->credentials);
-			return true;
-		}
-
-		return false;
-	}
-
-	/**
-	 * Revoke OAuth access
-	 */
-	public function revokeOAuthAccess(): bool
-	{
-		if (!$this->isOAuthService || empty($this->oauth['revoke'])) {
-			return false;
-		}
-
-		if (!empty($this->credentials['access_token'])) {
-			wp_remote_post($this->oauth['revoke'], [
-				'body' => ['token' => $this->credentials['access_token']]
-			]);
-		}
-
-		return CredentialsManager::getInstance()->deleteCredentials($this->service_name, $this->userID);
-
-	}
-
-	public function handleOAuthDisconnect(): array
-	{
-		try {
-			// Revoke the token with Square using centralized request
-			if (!empty($this->credentials['access_token'])) {
-				$revoke_data = [
-					'client_id' => $this->credentials['client_id'] ?? '',
-					'access_token' => $this->credentials['access_token']
-				];
-
-				// Make revoke request (ignore response as revoke often returns empty)
-				$this->makeOAuthRequest('POST', $this->oauth['revoke'], $revoke_data);
-			}
-
-			// Clear stored credentials (preserve app credentials)
-			$this->credentials = [
-				'client_id' => $this->credentials['client_id'] ?? '',
-				'client_secret' => $this->credentials['client_secret'] ?? '',
-				'environment' => $this->credentials['environment'] ?? 'sandbox'
-			];
-
-			$this->saveCredentials($this->credentials);
-			$this->clearCache();
-
-			return [
-				'success' => true,
-				'message' => 'Successfully disconnected from Square'
-			];
-		} catch (Exception $e) {
-			return [
-				'success' => false,
-				'message' => 'Failed to disconnect: ' . $e->getMessage()
-			];
-		}
-	}
 	/**
 	 * Generate webhook signature key for services that require it
 	 * @return string
@@ -2034,674 +517,13 @@
 	{
 		return wp_generate_password(32, false);
 	}
-	/**
-	 * Generate OAuth state parameter
-	 */
-	protected function generateOAuthState(?int $user_id, ?string $return_url = null): string
-	{
-		$user_id = $user_id ?? 0;
-		$state = wp_create_nonce($this->service_name . '_oauth_' . $user_id) . '|' . $user_id;
 
-		if ($return_url) {
-			$state .= '|' . base64_encode($return_url);
-		}
-
-		return $state;
-	}
-
-	protected function getNonce(): string
-	{
-		 return wp_create_nonce($this->service_name . '_oauth_' . $this->userID);
-	}
-
-	/**
-	 * Determine return URL after OAuth
-	 */
-	protected function determineReturnUrl(?int $user_id): string
-	{
-		if ($user_id > 0) {
-			return home_url('/dash/integrations/#' . $this->service_name);
-		}
-
-		return admin_url('admin.php?page=jvb-integrations');
-	}
-
-
-	/****************************************************************
-		POST SYNC
-	 ****************************************************************/
-	/**
-	 * Get field mapping for a post type
-	 */
-	protected function getFieldMapping(string $post_type): array
-	{
-		// Apply filter for custom mapping
-		return apply_filters(
-			"jvb_{$this->service_name}_field_mapping",
-			[],
-			$post_type,
-			$this
-		);
-	}
-
-
-	/**
-	 * Map WordPress fields to service fields
-	 */
-	protected function mapFieldsToService(int $postID, array $mapping): array
-	{
-		$meta_manager = Meta::forPost($postID);
-		$service_data = [];
-
-		foreach ($mapping as $wp_field => $service_field) {
-			$value = $meta_manager->get($wp_field);
-
-			if ($value !== null && $value !== '') {
-				$this->setNestedValue($service_data, $service_field, $value);
-			}
-		}
-
-		return apply_filters(
-			"jvb_{$this->service_name}_mapped_data",
-			$service_data,
-			$postID,
-			$mapping
-		);
-	}
-
-	/**
-	 * Set nested array value using dot notation
-	 */
-	protected function setNestedValue(array &$array, string $path, $value): void
-	{
-		$keys = explode('.', $path);
-		$current = &$array;
-
-		foreach ($keys as $i => $key) {
-			if ($i === count($keys) - 1) {
-				$current[$key] = $value;
-			} else {
-				if (!isset($current[$key])) {
-					$current[$key] = [];
-				}
-				$current = &$current[$key];
-			}
-		}
-	}
-	/**
-	 * Handle post save
-	 */
-	public function handleSavePost(int $postID, WP_Post $post, bool $update): void
-	{
-		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;
-		}
-
-		$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;
-		}
-
-		$isShared = isset($fields["_{$this->service_name}_item_id"]);
-		if ($update && $isShared && !$fields['_keep_synced_'.$this->service_name]) {
-			return;
-		}
-
-		if ($post->post_status !== 'publish' && !$isShared) {
-			return;
-		}
-		$this->handleTheSavePost($postID, $post, $update, $settings);
-	}
-
-
-	protected function getSyncFields(int $postID, string $type, array $additional = []):array
-	{
-		$meta = new Meta($postID, $type);
-		$fieldsToCheck = [
-			'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",
-			... $additional
-		];
-		return $meta->getAll($fieldsToCheck);
-	}
-
-	/**
-	 * Handle post status transitions
-	 */
-	public function handlePostStatusTransition(string $new_status, string $old_status, WP_Post $post): void
-	{
-		if (empty($this->syncPostTypes)) {
-			return;
-		}
-
-		if (!in_array(jvbNoBase($post->post_type), $this->syncPostTypes)) {
-			return;
-		}
-
-		//Map fields from our custom post types to the fields expected by the integration
-		$mappedFields = $this->getFieldMapping($post->post_type);
-
-		$fields = $this->getSyncFields($post->ID, 'post', $mappedFields);
-
-		// Handle unpublish action
-		if ($old_status === 'publish' && $new_status !== 'publish') {
-
-			if ($fields["_{$this->service_name}_item_id"] && $this->canSync['delete']) {
-				try {
-					$this->queueOperation(
-						'delete_post',
-						[
-							$post,
-							$fields["_{$this->service_name}_item_id"]
-						]
-					);
-				} catch (Exception $e) {
-					$this->logError("Failed to handle unpublish for post {$post->ID}: " . $e->getMessage());
-				}
-			}
-		}
-	}
-
-	/**
-	 * Handle post deletion
-	 */
-	public function handleDeletePost(int $postID): void
-	{
-		if (!$this->canSync['delete']) {
-			return;
-		}
-
-		$post = get_post($postID);
-		if (!$post || !in_array($post->post_type, $this->syncPostTypes)) {
-			return;
-		}
-
-		$fields = $this->getSyncFields($postID, 'post');
-
-		if ($fields["_{$this->service_name}_item_id"] !== '') {
-
-			$this->queueOperation(
-				'delete_post',
-				[
-					'post_id'	=> $post->ID,
-				]
-			);
-		}
-	}
-
-	protected function handleSaveTerm($term_id, $tt_id, $taxonomy, $update, $args): void
-	{
-		$noBase = jvbNoBase($taxonomy);
-		if (!in_array($noBase, $this->syncTaxonomies)) {
-			return;
-		}
-		$registrar = Registrar::getInstance($noBase);
-		if (!$registrar->hasFeature('is_content')) {
-			return;
-		}
-
-
-		$settings = $registrar->getIntegrationConfig($this->service_name);
-		if (!$settings) {
-			return;
-		}
-
-		// Similar sync logic as handleSavePost but for terms
-		$this->handleTheTermSave($term_id, $taxonomy, $update, $settings);
-	}
-
-	protected function handleTheTermSave($term_id, $taxonomy, $update, $settings) {
-
-	}
-
-	protected function getTermFieldMapping(string $taxonomy): array
-	{
-		return apply_filters(
-			"jvb_{$this->service_name}_term_field_mapping",
-			[],
-			$taxonomy,
-			$this
-		);
-	}
-
-	/**
-	 * Share post to external service (override in child classes)
-	 */
-	protected function sharePost(WP_Post $post): ?string
-	{
-		throw new Exception('sharePost must be implemented by child class');
-	}
-
-	/**
-	 * Update shared post on external service (override in child classes)
-	 */
-	protected function updateSharedPost(WP_Post $post, string $external_id): void
-	{
-		throw new Exception('updateSharedPost must be implemented by child class');
-	}
-
-	/**
-	 * Remove post from external service (override in child classes)
-	 */
-	protected function unsharePost(WP_Post $post, string $external_id): void
-	{
-		throw new Exception('unsharePost must be implemented by child class');
-	}
-
-	/**
-	 * Get sync capabilities
-	 */
-	public function getSyncCapabilities(): array
-	{
-		return $this->canSync;
-	}
-
-	/**
-	 * Check if a specific sync capability is supported
-	 */
-	public function supportsSyncCapability(string $capability): bool
-	{
-		return $this->canSync[$capability] ?? false;
-	}
-
-	/**
-	 * Get posts shared to this service
-	 */
-	public function getSharedPosts(array $args = []): array
-	{
-		$defaults = [
-			'post_type' => $this->syncPostTypes,
-			'meta_query' => [
-				[
-					'key' => BASE."_{$this->service_name}_item_id",
-					'compare' => 'EXISTS'
-				]
-			],
-			'posts_per_page' => -1
-		];
-
-		$args = wp_parse_args($args, $defaults);
-		return get_posts($args);
-	}
-
-	/**
-	 * Check if post is shared to this service
-	 */
-	public function isPostShared(int $postID): bool
-	{
-		$external_id = get_post_meta($postID, BASE."_{$this->service_name}_item_id", true);
-		return $external_id !== '';
-	}
-
-	/**
-	 * Get external ID for a post
-	 */
-	public function getPostExternalId(int $postID): ?string
-	{
-		$external_id = get_post_meta($postID, BASE."_{$this->service_name}_item_id", true);
-		return $external_id ?: null;
-	}
 
 	/*******************************************************************
 	 * UTILITIES
 	 *******************************************************************/
 
 	/**
-	 * Get API URL for endpoint
-	 */
-	protected function getApiUrl(string $endpoint, ?string $baseKey = null): string|false
-	{
-		if ($this->isOAuthService && in_array($endpoint, $this->oauth)) {
-			return $endpoint;
-		}
-		if (is_array($this->apiBase)) {
-			if ($baseKey && array_key_exists($baseKey, $this->apiBase)) {
-				$base = $this->apiBase[$baseKey];
-			} else {
-				$base = ($this->apiBase['base'] ?? reset($this->apiBase));
-			}
-		} else {
-			$base = $this->apiBase;
-		}
-
-		if (!$base || $base === '') {
-			$this->logError('API base URL not configured for {$this->>service_name}');
-			return false;
-		}
-
-		// Handle named endpoints
-		if (!$this->isValidEndpoint($endpoint)) {
-			$this->logError("{$endpoint} is not a valid endpoint for {$this->service_name}");
-			return false;
-		}
-
-		// Build full URL
-		$base = rtrim($base, '/');
-		$endpoint = ltrim($endpoint, '/');
-
-		return "{$base}/{$endpoint}";
-	}
-
-	/**
-	 * Check if an endpoint is valid, supporting both exact matches and patterns
-	 *
-	 * @param string $endpoint
-	 * @return bool
-	 */
-	protected function isValidEndpoint(string $endpoint): bool
-	{
-		// Remove query parameters for validation
-		$endpointPath = parse_url($endpoint, PHP_URL_PATH);
-		if ($endpointPath === false) {
-			$endpointPath = $endpoint;
-		}
-
-		foreach ($this->apiEndpoints as $pattern) {
-			// Check for exact match first
-			if ($endpointPath === $pattern) {
-				return true;
-			}
-
-			if (str_starts_with($endpointPath, $pattern)) {
-				return true;
-			}
-
-			// Check if pattern contains wildcards (indicated by square brackets)
-			if (strpos($pattern, '[') !== false) {
-				// Convert the pattern to a regex
-				$regexPattern = '#^' . str_replace(['[^/]+'], ['[^/]+'], $pattern) . '(?:\?.*)?$#';
-				if (preg_match($regexPattern, $endpoint)) {
-					return true;
-				}
-			}
-		}
-
-		return false;
-	}
-
-	/**
-	 * Build cache key
-	 */
-	protected function buildCacheKey(string $method, string $endpoint, array $params = []): string
-	{
-		$key_parts = [
-			$this->service_name,
-			$method,
-			$endpoint
-		];
-
-		if ($this->userID) {
-			$key_parts[] = "user_{$this->userID}";
-		}
-
-		if (!empty($params)) {
-			$key_parts[] = md5(serialize($params));
-		}
-
-		return implode('_', $key_parts);
-	}
-
-	/**
-	 * Log error message
-	 */
-	protected function logError(string $message, array $context = [], ?string $severity = null): void
-	{
-		if (!$this->errorHandler) {
-			$this->errorHandler = JVB()->error();
-		}
-		// Determine severity if not provided
-		if ($severity === null) {
-			$severity = 'error';
-		}
-
-		// Add integration-specific context
-		$context = array_merge($context, [
-			'component' => 'Integration:' . $this->service_name,
-			'user_id' => $this->userID,
-			'service' => $this->service_name,
-			'timestamp' => current_time('mysql'),
-			'memory_usage' => memory_get_usage(true),
-			'peak_memory' => memory_get_peak_usage(true),
-			'integration_health' => [
-				'is_healthy' => $this->is_healthy,
-				'consecutive_errors' => $this->error_stats['consecutive_errors'],
-				'total_errors' => $this->error_stats['total_errors'],
-				'last_success' => $this->error_stats['last_success']
-			]
-		]);
-
-		// Log through ErrorHandler
-		$this->errorHandler->log(
-			'Integration:' . $this->service_name,
-			$message,
-			$context,
-			$severity
-		);
-	}
-
-	/**
-	 * Categorize API errors for better tracking
-	 */
-	protected function categorizeApiError(int $code): string
-	{
-		return match(true) {
-			$code >= 400 && $code < 404 => 'client_error',
-			$code === 404 => 'not_found',
-			$code === 401 => 'authentication',
-			$code === 403 => 'authorization',
-			$code === 429 => 'rate_limit',
-			$code >= 500 && $code < 600 => 'server_error',
-			default => 'unknown'
-		};
-	}
-
-	/**
-	 * Determine error severity based on HTTP code
-	 */
-	protected function getErrorSeverity(int $code): string
-	{
-		return match(true) {
-			$code === 429 => 'warning', // Rate limiting
-			$code >= 400 && $code < 500 => 'error', // Client errors
-			$code >= 500 => 'critical', // Server errors
-			default => 'error'
-		};
-	}
-
-	/**
-	 * Extract error details from response
-	 */
-	protected function extractErrorDetails($decoded, string $body): array
-	{
-		$details = [
-			'message' => 'Unknown error',
-			'code' => null,
-			'details' => null
-		];
-
-		if ($decoded && isset($decoded['error'])) {
-			if (is_array($decoded['error'])) {
-				$details['message'] = $decoded['error']['message'] ?? json_encode($decoded['error']);
-				$details['code'] = $decoded['error']['code'] ?? null;
-				$details['details'] = $decoded['error']['details'] ?? null;
-			} else {
-				$details['message'] = $decoded['error'];
-			}
-		} elseif ($decoded && isset($decoded['message'])) {
-			$details['message'] = $decoded['message'];
-		} elseif (!empty($body)) {
-			$details['message'] = $body;
-		}
-
-		return $details;
-	}
-
-	/**
-	 * Extract rate limit information from response
-	 */
-	protected function extractRateLimitInfo($decoded, string $body): array
-	{
-		$info = [
-			'retry_after' => null,
-			'limit' => null,
-			'remaining' => null,
-			'reset' => null
-		];
-
-		// Try to extract from decoded response
-		if ($decoded) {
-			$info['retry_after'] = $decoded['retry_after'] ?? $decoded['retry-after'] ?? null;
-			$info['limit'] = $decoded['x-rate-limit-limit'] ?? null;
-			$info['remaining'] = $decoded['x-rate-limit-remaining'] ?? null;
-			$info['reset'] = $decoded['x-rate-limit-reset'] ?? null;
-		}
-
-		return $info;
-	}
-
-	/**
-	 * Update error statistics
-	 */
-	protected function updateErrorStats(int $code, string $endpoint): void
-	{
-		$this->error_stats['total_errors']++;
-		$this->error_stats['consecutive_errors']++;
-
-		// Track error types
-		$error_type = $this->categorizeApiError($code);
-		if (!isset($this->error_stats['error_types'][$error_type])) {
-			$this->error_stats['error_types'][$error_type] = 0;
-		}
-		$this->error_stats['error_types'][$error_type]++;
-
-		// Save stats to cache
-		$this->saveErrorStats();
-	}
-
-	/**
-	 * Reset error statistics on successful request
-	 */
-	protected function resetErrorStats(): void
-	{
-		$this->error_stats['consecutive_errors'] = 0;
-		$this->error_stats['last_success'] = time();
-		$this->is_healthy = true;
-
-		$this->saveErrorStats();
-	}
-
-	/**
-	 * Get integration health status
-	 */
-	public function getHealthStatus(): array
-	{
-		return [
-			'is_healthy' => $this->is_healthy,
-			'consecutive_errors' => $this->error_stats['consecutive_errors'],
-			'total_errors' => $this->error_stats['total_errors'],
-			'last_success' => $this->error_stats['last_success'],
-			'error_types' => $this->error_stats['error_types'],
-			'threshold' => $this->error_threshold
-		];
-	}
-
-	/**
-	 * Manually reset integration health
-	 */
-	public function resetHealth(): void
-	{
-		$this->error_stats = [
-			'total_errors' => 0,
-			'consecutive_errors' => 0,
-			'last_success' => time(),
-			'error_types' => []
-		];
-		$this->is_healthy = true;
-		$this->saveErrorStats();
-	}
-
-	/**
-	 * Check integration health based on error patterns
-	 */
-	protected function checkIntegrationHealth(): void
-	{
-		// Mark unhealthy if too many consecutive errors
-		if ($this->error_stats['consecutive_errors'] >= $this->error_threshold) {
-			$this->is_healthy = false;
-
-			// Log critical health issue
-			$this->logError(
-				"Integration marked unhealthy after {$this->error_stats['consecutive_errors']} consecutive errors",
-				[
-					'error_stats' => $this->error_stats,
-					'threshold' => $this->error_threshold
-				],
-				'critical'
-			);
-		}
-	}
-
-	/**
-	 * Load error statistics from cache
-	 */
-	protected function loadErrorStats(): void
-	{
-		$cache_key = "error_stats_{$this->service_name}_{$this->userID}";
-		$cached_stats = $this->cache->get($cache_key);
-
-		if ($cached_stats) {
-			$this->error_stats = array_merge($this->error_stats, $cached_stats);
-
-			// Check if integration was marked unhealthy
-			if ($this->error_stats['consecutive_errors'] >= $this->error_threshold) {
-				$this->is_healthy = false;
-			}
-		}
-	}
-
-	/**
-	 * Save error statistics to cache
-	 */
-	protected function saveErrorStats(): void
-	{
-		$cache_key = "error_stats_{$this->service_name}_{$this->userID}";
-		$this->cache->set($cache_key, $this->error_stats, 86400); // 24 hours
-	}
-
-	protected function logDebug(string $message, array $context = []): void
-	{
-		error_log('[Integration: '.$this->service_name.']'.$message.': '.print_r($context, true));
-	}
-
-	/**
 	 * Get service name
 	 */
 	public function getServiceName(): string
@@ -2714,23 +536,19 @@
 		return $this->title;
 	}
 
-	/*********************************************************************
-		RENDERING
-	 *********************************************************************/
-//	public function renderAdditionalOptions()
-//	{
-//		//Default: nothing.
-//	}
+	public static function title():string
+	{
+		return static::getInstance()->getTitle();
+	}
+	public static function icon():string
+	{
+		return static::getInstance()->getIcon();
+	}
 
-	/**
-	 * Render additional action buttons (optional)
-	 * Override in integration classes that need extra actions
-	 */
-//	public function renderAdditionalActions(): void
-//	{
-//		// Default: no additional actions
-//		// Override in extensions for service-specific actions
-//	}
+	public static function hasExtraOptions():bool
+	{
+		return self::$hasExtraOptions;
+	}
 
 	/**
 	 * Get service description (optional)
@@ -2741,77 +559,6 @@
 		return "Manage your {$this->getServiceName()} integration settings.";
 	}
 
-	/**
-	 * Get service key for consistency
-	 */
-	public function getServiceKey(): string
-	{
-		return strtolower($this->service_name);
-	}
-
-	/**
-	 * Refresh credentials from storage and re-initialize
-	 */
-	public function refreshCredentials(): void
-	{
-		$this->credentials = [];
-		$this->ensureInitialized();
-	}
-
-	/**
-	 * Enhanced webhook handling with common patterns
-	 */
-	public function handleWebhook(array $payload): bool
-	{
-		// Log incoming webhook for debugging
-		$this->logDebug('Webhook received', [
-			'payload_keys' => array_keys($payload),
-			'timestamp' => time()
-		]);
-
-		// Validate webhook signature/authenticity
-		if (!$this->validateWebhook($payload)) {
-			$this->logError('Webhook validation failed', [
-				'webhook_type' => 'validation_error',
-				'payload_sample' => array_slice($payload, 0, 5) // Log partial payload for debugging
-			], 'warning');
-			return false;
-		}
-
-		// Check for duplicate processing (idempotency)
-		if ($this->isWebhookProcessed($payload)) {
-			return true; // Return true to prevent retries
-		}
-
-		try {
-			// Process the webhook
-			$result = $this->processWebhook($payload);
-
-			// Mark as processed if successful
-			if ($result) {
-				$this->markWebhookProcessed($payload);
-				$this->resetErrorStats();
-			} else {
-				$this->logError('Webhook processing returned false', [
-					'webhook_id' => $this->extractWebhookId($payload)
-				], 'warning');
-			}
-
-			return $result;
-
-		} catch (Exception $e) {
-			$this->logError('Webhook processing failed', [
-				'error' => $e->getMessage(),
-				'webhook_id' => $this->extractWebhookId($payload),
-				'trace' => $e->getTraceAsString()
-			], 'critical');
-
-			// Update error stats
-			$this->updateErrorStats($e->getCode() ?: 500, 'webhook');
-			$this->checkIntegrationHealth();
-			return false;
-		}
-	}
 
 	/**
 	 * Validate webhook signature
@@ -2833,526 +580,14 @@
 		return hash_equals($expected, $signature);
 	}
 
-	protected function validateWebhook(array $payload):bool
-	{
-		//Handled by child classes
-		return false;
-	}
-	protected function processWebhook(array $payload):bool
-	{
-		//Handled by child classes
-		return false;
-	}
-
-	/**
-	 * Check if webhook was already processed (prevent duplicates)
-	 */
-	protected function isWebhookProcessed(array $payload): bool
-	{
-		$webhook_id = $this->extractWebhookId($payload);
-		if (!$webhook_id) {
-			return false;
-		}
-
-		$cache_key = "webhook_processed_{$this->service_name}_{$webhook_id}";
-		return $this->cache->get($cache_key) !== null;
-	}
-
-	/**
-	 * Mark webhook as processed
-	 */
-	protected function markWebhookProcessed(array $payload): void
-	{
-		$webhook_id = $this->extractWebhookId($payload);
-		if ($webhook_id) {
-			$cache_key = "webhook_processed_{$this->service_name}_{$webhook_id}";
-			$this->cache->set($cache_key, time(), 86400); // Store for 24 hours
-		}
-	}
-
-	/**
-	 * Extract unique webhook ID (override in child classes)
-	 */
-	protected function extractWebhookId(array $payload): ?string
-	{
-		// Common patterns - child classes can override
-		return $payload['id'] ??
-			$payload['event_id'] ??
-			$payload['webhook_id'] ??
-			md5(serialize($payload));
-	}
-
-	/**
-	 * Register webhook endpoint
-	 */
-	protected function registerWebhookEndpoint(): void
-	{
-		// Register REST API endpoint for webhooks
-		add_action('rest_api_init', function() {
-			register_rest_route('jvb/v1', '/webhooks/' . $this->service_name, [
-				'methods' => 'POST',
-				'callback' => [$this, 'handleWebhookRequest'],
-				'permission_callback' => '__return_true', // Webhooks come from external services
-			]);
-		});
-	}
-
-	/**
-	 * Handle webhook REST API request
-	 */
-	public function handleWebhookRequest(WP_REST_Request $request): WP_REST_Response
-	{
-		$payload = $request->get_params();
-		$headers = $request->get_headers();
-
-		// Include headers in payload for signature validation
-		$payload['_headers'] = $headers;
-
-		$success = $this->handleWebhook($payload);
-
-		return new WP_REST_Response([
-			'success' => $success
-		], $success ? 200 : 400);
-	}
-
-	protected function renderWebhookUrl(): string
-	{
-		if (!$this->handleWebhooks || !$this->isSetUp()) {
-			return '';
-		}
-
-		$webhook_url = rest_url('jvb/v1/webhooks/' . $this->service_name);
-
-		return sprintf(
-			'<div class="webhook-info">
-            <h4>Webhook URL</h4>
-            <code id="webhook-url-%s">%s</code>
-            <p class="hint">Add this URL to your %s webhook settings</p>
-        </div>',
-			esc_attr($this->service_name),
-			esc_html($webhook_url),
-			esc_html($this->title)
-		);
-	}
-
-	public function renderConnection(bool $return = false):string
-	{
-		if ($this->userID && !JVB()->userCanConnect($this->service_name, $this->userID)) {
-			return '';
-		}
-
-		$meta = Meta::forOptions($this->userID.'_integrations');
-		$is_connected = $this->isSetUp();
-		$credentials = $this->getCredentials();
-
-		$admin_only = $this->isOAuthService ? [
-			'client_id',
-			'client_secret',
-		] : [];
-
-		ob_start();
-		?>
-		<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 x-btw">
-				<h3><?php echo esc_html($this->title); ?></h3>
-				<div class="setup">
-					<?php if ($is_connected): ?>
-						<span class="indicator connected">●</span>
-						<span class="text">Set Up</span>
-					<?php else: ?>
-						<span class="indicator disconnected">●</span>
-						<span class="text">Not Set Up</span>
-					<?php endif; ?>
-				</div>
-			</div>
-
-			<?php if ($is_connected && array_key_exists('updated_at', $credentials) && $credentials['updated_at'] > 0): ?>
-				<div class="meta">
-					<small>Last updated: <?php echo human_time_diff($credentials['updated_at']) . ' ago'; ?></small>
-				</div>
-			<?php endif; ?>
-
-			<?php
-			if (!empty($this->instructions)) {
-				?>
-				<details>
-					<summary>
-						Instructions
-					</summary>
-					<ol>
-						<?php
-						foreach ($this->instructions as $instruction) {
-							echo '<li>'.$instruction.'</li>';
-						}
-						?>
-					</ol>
-				</details>
-				<?php
-			}
-?>
-			<details class="initial-setup"<?= $is_connected?'' : ' open'?>>
-				<summary>Initial Setup</summary>
-				<?php
-				foreach ($this->fields as $name => $config) {
-					if ($is_connected && !empty($credentials[$name])) {
-					if (in_array($name, $admin_only) && !current_user_can('manage_options')) {
-						continue;
-					}
-					?>
-					<span class="label"><?=$config['label']?>:</span>
-					<code>
-						<?php
-						if (str_contains($name, 'secret')) {
-							for ($i = 1; $i<=strlen($credentials[$name]) - 8; $i++) {
-								echo '*';
-							}
-							echo substr($credentials[$name], -8);
-						} else {
-							echo $credentials[$name];
-						}
-						?>
-					</code>
-					<?php
-					} else {
-						$config['value'] = $credentials[$name]??'';
-						$config['autocomplete'] = 'off';
-						$config['base'] = $this->service_name.'_';
-						echo Form::render($name, '', $config);
-					}
-				}
-				if ($this->handleWebhooks) {
-					echo $this->renderWebhookUrl();
-				}
-				?>
-			</details>
-			<?php
-
-			if ($this->isOAuthService) {
-				$this->renderConnectedOAuthStatus();
-			}
-
-			?>
-
-			<div class="integration-content">
-
-				<?php
-
-
-
-				if (!empty($this->advanced)) {
-					?>
-					<details>
-						<summary>Advanced Settings</summary>
-						<?php
-						foreach ($this->advanced as $name => $config) {
-							$config['value'] = $credentials[$name]??'';
-							$config['base'] = $this->service_name.'_';
-							$config['autocomplete'] = 'off';
-							Form::render($name,null, $config);
-						}
-						?>
-					</details>
-					<?php
-				}
-				if (!empty($this->defaults)) {
-					?>
-					<a href="<?php echo admin_url('admin.php?page=jvb-integration-' . $this->service_name); ?>"
-					   class="button">
-						More Settings
-					</a>
-					<?php
-				}
-				?>
-			</div>
-			<div class="actions row x-btw wrap">
-				<?php
-				foreach ($this->buttons as $action => $label) {
-					if (!$is_connected && $action !== 'save_credentials') {
-						continue;
-					}
-					$title = $confirm = '';
-					switch ($action) {
-						case 'save_credentials':
-							$title = $label;
-							$label = jvbIcon('floppy-disk');
-							break;
-						case 'clear_credentials':
-							$title = $label;
-							$label = jvbIcon('plugs');
-							$confirm = ' data-confirm="Are you sure you want to delete these credentials?"';
-							break;
-						case 'clear_cache':
-							$title = $label;
-							$label = jvbIcon('arrows-clockwise');
-							break;
-					}
-					$title = $title === '' ? '' : ' title ="'.$title.'"';
-					?>
-					<button type="button" data-action="<?=$action?>"<?=$title?><?=$confirm?>><?=$label?></button>
-					<?php
-				}
-				?>
-			</div>
-		</form>
-		<?php
-		$result = ob_get_clean();
-		if(!$return) {
-			echo $result;
-		}
-		return $result;
-	}
-
-	protected function renderConnectedOAuthStatus(): void
-	{
-		if (!$this->isSetup()) {
-			return;
-		}
-		$credentials = $this->getCredentials();
-		$hasCredentials = $this->hasOAuthCredentials();
-		$returnURL = is_admin() ? admin_url('admin.php?page=jvb-integrations') : (get_the_permalink() ?: home_url());
-		?>
-
-		<details <?= $hasCredentials?' open':''?>>
-			<summary>
-				<?php if ($hasCredentials) { ?>
-					Connected Account
-				<?php } else { ?>
-					<div class="oauth-connect">
-						<a href="<?php echo esc_url($this->getOAuthUrl($returnURL)); ?>"
-						   class="button button-primary jvb-oauth-connect"
-						   data-service="<?php echo esc_attr($this->service_name); ?>">
-							<?php echo jvbIcon($this->icon); ?>
-							Authorize Connection
-						</a>
-					</div>
-				<?php } ?>
-				<div class="connection-status <?= $hasCredentials ? 'connected' : 'disconnected' ?>">
-					<span class="status-indicator">●</span>
-					<span><?= $hasCredentials ? 'Connected' : 'Not Connected' ?></span>
-				</div>
-			</summary>
-			<label>OAuth Redirect URL:</label>
-			<code>
-				<?= $this->getRedirectUri(); ?>
-			</code>
-
-
-		<?php if (!empty($credentials['updated_at'])): ?>
-		<div class="oauth-meta">
-			<small>Token expires: <?php
-				echo isset($credentials['expires_at'])
-					? human_time_diff($credentials['expires_at'])
-					: 'Never';
-				?></small>
-		</div>
-		<?php endif;
-		// Allow child classes to add service-specific connected UI
-		$this->renderOAuthConnectedOptions();
-		?>
-		</details>
-		<?php
-	}
-
-	protected function renderOAuthConnectedOptions():void
-	{
-
-	}
-
-	/**
-	 * Get last tested timestamp - extends your existing pattern
-	 */
-	protected function getLastTestedTime(): ?int
-	{
-		$key = $this->userID
-			? "jvb_integration_{$this->service_name}_last_test_user_{$this->userID}"
-			: "jvb_integration_{$this->service_name}_last_test";
-		return get_option($key, null);
-	}
 
 	/**
 	 * Update last tested time
 	 */
 	public function updateLastTestedTime(): void
 	{
-		$key = $this->userID
-			? "jvb_integration_{$this->service_name}_last_test_user_{$this->userID}"
-			: "jvb_integration_{$this->service_name}_last_test";
-		update_option($key, time());
-	}
-
-	public function handleAdminPost(): void
-	{
-		if (!current_user_can('manage_options')) {
-			wp_die('Insufficient permissions');
-		}
-
-		$service = $this->getServiceName();
-
-		// Verify nonce
-		$nonce_field = 'jvb_integration_nonce_' . $service;
-		$nonce_action = 'jvb_integration_save_' . $service;
-		if (!isset($_POST[$nonce_field]) || !wp_verify_nonce($_POST[$nonce_field], $nonce_action)) {
-			wp_die('Security check failed');
-		}
-
-		// Get the action type
-		$action_type = sanitize_text_field($_POST['action_type'] ?? 'save');
-
-		// Prepare the request in the format handleAjaxRequest expects
-		$_REQUEST['action'] = $action_type;
-		$_REQUEST['service'] = $service;
-
-		// Copy all POST data to REQUEST
-		foreach ($_POST as $key => $value) {
-			$_REQUEST[$key] = $value;
-		}
-
-		// Set up for JSON response capture
-		ob_start();
-
-		try {
-			// Call the existing AJAX handler
-			$this->handleAjaxRequest();
-			$response = ob_get_clean();
-
-			// Parse the JSON response
-			$result = json_decode($response, true);
-
-			if ($result && isset($result['success'])) {
-				if ($result['success']) {
-					$message = $result['message'] ?? ucfirst($service) . ' settings saved successfully!';
-					$this->setAdminNotice($message, 'success');
-				} else {
-					$message = $result['message'] ?? 'Failed to save ' . ucfirst($service) . ' settings.';
-					$this->setAdminNotice($message, 'error');
-				}
-			} else {
-				// If no proper JSON response, check if connection worked
-				if ($action_type === 'test') {
-					$connected = $this->testConnection();
-					$message = $connected ? 'Connection successful!' : 'Connection failed. Please check your credentials.';
-					$this->setAdminNotice($message, $connected ? 'success' : 'error');
-				}
-			}
-		} catch (Exception $e) {
-			ob_end_clean();
-			$this->setAdminNotice('Error: ' . $e->getMessage(), 'error');
-		}
-
-		// Redirect back to integrations page
-		wp_redirect(admin_url('admin.php?page=jvb-integrations'));
-		exit;
-	}
-
-	/**
-	 * Set admin notice using transients for redirect
-	 */
-	protected function setAdminNotice(string $message, string $type = 'info'): void
-	{
-		$notices = get_transient('jvb_admin_notices') ?: [];
-		$notices[] = [
-			'message' => $message,
-			'type' => $type === 'success' ? 'updated' : 'error'
-		];
-		set_transient('jvb_admin_notices', $notices, 30);
-	}
-
-	/**
-	 * Display admin notices from transient
-	 */
-	public static function displayAdminNotices(): void
-	{
-		$notices = get_transient('jvb_admin_notices');
-
-		if ($notices) {
-			foreach ($notices as $notice) {
-				?>
-				<div class="notice notice-<?php echo esc_attr($notice['type']); ?> is-dismissible">
-					<p><?php echo esc_html($notice['message']); ?></p>
-				</div>
-				<?php
-			}
-			delete_transient('jvb_admin_notices');
-		}
-	}
-
-	public function hasDefaults():bool
-	{
-		return !empty($this->defaults);
-	}
-
-	public function renderDefaults():void
-	{
-		$types = $this->enabledContentTypes();
-		if (empty($types)) {
-			return;
-		}
-		$meta = Meta::forOptions($this->userID.'_integrations');
-		?>
-		<form>
-			<h1><?= $this->title?> Defaults:</h1>
-			<p>Find yourself constantly repeating yourself?</p>
-			<p>Set defaults for different content types and <?=$this->title?>!</p>
-			<?php
-			foreach ($this->defaults as $name => $config) {
-				$config['required'] = false;
-
-				$config['base'] = $this->service_name.'_';
-				$config['autocomplete'] = 'off';
-				echo Form::render($name, null, $config);
-			}
-			foreach ($this->syncPostTypes as $type) {
-				$registrar = Registrar::getInstance($type);
-
-				$icon = $registrar->getIcon();
-				$icon = $icon === '' ? jvbDefaultIcon() : $icon;
-				?>
-				<details>
-					<summary><?= jvbIcon($icon) ?><?= $registrar->getSingular()?> Defaults</summary>
-					<?php
-					$fields = new AddIntegrationFields($this->service_name);
-					$fields = $fields->getIntegrationFields();
-					foreach($fields as $name=>$c) {
-						$c['required'] = false;
-						if ($c['type'] === 'number') {
-							$c['type'] = 'text';
-							$c['subtype'] = 'number';
-						}
-						if (array_key_exists('description', $c)) {
-							$c['hint'] = $c['description'];
-							unset($c['description']);
-						}
-						echo Form::render($name, null, $c);
-					}
-					?>
-				</details>
-				<?php
-			}
-			?>
-		</form>
-		<?php
-	}
-
-	public function hasContent():bool
-	{
-		return $this->has_content;
-	}
-	public function getDefaultContentType():string
-	{
-		return $this->defaultContent;
-	}
-
-	public function enabledContentTypes():array
-	{
-		if (!$this->has_content) {
-			return [];
-		}
-
-		return array_filter(array_map(function($registrar) {
-			$registrar = Registrar::getInstance($registrar);
-			return $registrar->getIntegration($this->service_name)->getContentType();
-		}, Registrar::withIntegration($this->service_name)));
+		$cred = Auth::getInstance();
+		$cred->updateTested($this->service_name, $this->userID);
 	}
 
 	protected function getSupportedImage(int $imgID):int
@@ -3372,167 +607,30 @@
 		if ($jpegVersion !== '' && is_int($jpegVersion)) {
 			return $jpegVersion;
 		}
-
-		$converted = $this->convertWebpToJpeg($imgID);
-		if ($converted) {
-			update_post_meta($imgID, BASE . 'jpeg_version', $converted);
-			return $converted;
+		$uploader = UploadManager::getInstance();
+		$converted = $uploader->convertImageTo($imgID, 'jpeg', 80, false);
+		if ($converted && !is_wp_error($converted)) {
+			update_post_meta($imgID, BASE . 'jpeg_version', $converted['attachment_id']);
+			return $converted['attachment_id'];
 		}
 
 		return $imgID;
 	}
 
-	/**
-	 * Convert WebP image to JPEG
-	 *
-	 * @param int $webpAttachmentId
-	 * @return int|false Image ID on success, false on failure.
-	 */
-	protected function convertWebpToJpeg(int $webpAttachmentId):int|false
-	{
-		try {
-			$webpPath = get_attached_file($webpAttachmentId);
-
-			if (!file_exists($webpPath)) {
-				throw new Exception('WebP file not found');
-			}
-
-			// Generate JPEG filename
-			$pathInfo = pathinfo($webpPath);
-			$jpegPath = $pathInfo['dirname'] . '/' . $pathInfo['filename'] . '_jpeg.jpg';
-
-			// Check if already exists (shouldn't happen due to meta check, but safety first)
-			if (file_exists($jpegPath)) {
-				// Try to find existing attachment
-				global $wpdb;
-				$existingId = $wpdb->get_var($wpdb->prepare(
-					"SELECT ID FROM {$wpdb->posts} WHERE guid = %s",
-					str_replace(ABSPATH, site_url('/'), $jpegPath)
-				));
-
-				if ($existingId) {
-					return (int)$existingId;
-				}
-			}
-
-			// Perform conversion based on available libraries
-			if (extension_loaded('imagick')) {
-				$this->convertWebpToJpegImagick($webpPath, $jpegPath);
-			} else {
-				$this->convertWebpToJpegGd($webpPath, $jpegPath);
-			}
-
-			// Create attachment for the JPEG
-			$attachmentData = [
-				'post_mime_type' => 'image/jpeg',
-				'post_title' => get_the_title($webpAttachmentId) . ' (JPEG)',
-				'post_content' => '',
-				'post_status' => 'inherit'
-			];
-
-			$jpegAttachmentId = wp_insert_attachment($attachmentData, $jpegPath);
-
-			if (is_wp_error($jpegAttachmentId)) {
-				throw new Exception('Failed to create JPEG attachment: ' . $jpegAttachmentId->get_error_message());
-			}
-
-			// Generate metadata
-			require_once(ABSPATH . 'wp-admin/includes/image.php');
-			$attachData = wp_generate_attachment_metadata($jpegAttachmentId, $jpegPath);
-			wp_update_attachment_metadata($jpegAttachmentId, $attachData);
-
-			// Copy alt text and other meta
-			$altText = get_post_meta($webpAttachmentId, '_wp_attachment_image_alt', true);
-			if ($altText) {
-				update_post_meta($jpegAttachmentId, '_wp_attachment_image_alt', $altText);
-			}
-
-			return $jpegAttachmentId;
-
-		} catch (Exception $e) {
-			$this->logError('Image conversion failed...', [
-				'method'	=> 'convertWebpToJpeg'
-			]);
-			return false;
-		}
-	}
-
-	/**
-	* Convert WebP to JPEG using Imagick
-	*/
-	protected function convertWebpToJpegImagick(string $source, string $destination): void
-	{
-		$image = new \Imagick($source);
-		$image->setImageFormat('jpeg');
-
-		// Set quality (using 85 for good balance of quality/size)
-		$image->setImageCompressionQuality(85);
-
-		// Remove alpha channel and set white background
-		$image->setImageBackgroundColor('white');
-		$image->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
-		$image->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
-
-		$image->writeImage($destination);
-		$image->clear();
-	}
-
-	/**
-	 * Convert WebP to JPEG using GD
-	 */
-	protected function convertWebpToJpegGd(string $source, string $destination): void
-	{
-		$image = imagecreatefromwebp($source);
-
-		if ($image === false) {
-			throw new Exception('Failed to create image from WebP source');
-		}
-
-		// Get dimensions
-		$width = imagesx($image);
-		$height = imagesy($image);
-
-		// Create new true color image
-		$jpegImage = imagecreatetruecolor($width, $height);
-
-		// Set white background (for transparency)
-		$white = imagecolorallocate($jpegImage, 255, 255, 255);
-		imagefill($jpegImage, 0, 0, $white);
-
-		// Copy WebP image to JPEG
-		imagecopy($jpegImage, $image, 0, 0, 0, 0, $width, $height);
-
-		// Save as JPEG (quality 85)
-		$result = imagejpeg($jpegImage, $destination, 85);
-
-		// Clean up
-		imagedestroy($image);
-		imagedestroy($jpegImage);
-
-		if (!$result) {
-			throw new Exception('Failed to save JPEG image');
-		}
-	}
-	/**
-	 * Reset token refresh attempt flag
-	 * Called automatically when switching users
-	 */
-	protected function resetTokenRefreshFlag(): void
-	{
-		$this->token_refresh_attempted = false;
-	}
-
-	public function getAllowedContent():array
-	{
-		return $this->allowedContent;
-	}
 
 	/**
 	 * Used by JVBase\registrar\helpers\AddIntegrationFields.php
+	 * @param string|null $content_type
 	 * @return array
 	 */
 	public function getAdditionalFields(?string $content_type = null):array
 	{
 		return [];
 	}
+
+	public function getIcon():string
+	{
+		return $this->icon;
+	}
+
 }

--
Gitblit v1.10.0