From 0dfe1d8afafc59c4a5559c498342668d5a58d6ef Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 23 Jul 2026 22:41:41 +0000
Subject: [PATCH] =Still working away at the Integrations overhaul

---
 inc/integrations/Integrations.php | 3088 +---------------------------------------------------------
 1 files changed, 96 insertions(+), 2,992 deletions(-)

diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index 9510af5..f055894 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -2,14 +2,10 @@
 namespace JVBase\integrations;
 
 use Exception;
-use JVBase\managers\CacheManager;
+use JVBase\managers\Cache;
 use JVBase\managers\UploadManager;
-use JVBase\meta\MetaManager;
-use JVBase\managers\ErrorHandler;
+use JVBase\registrar\Registrar;
 use WP_Error;
-use WP_Post;
-use WP_REST_Request;
-use WP_REST_Response;
 
 if (!defined('ABSPATH')) {
 	exit;
@@ -26,424 +22,79 @@
  */
 abstract class Integrations
 {
+	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
-
-
-	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 CacheManager $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 querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
-	protected array $syncTaxonomies = []; // Post types that can be synced (e.g., ['artwork', 'tattoo']): usually built by querying the global JVB_CONTENT if the integration name exists as a key in [ 'integrations' => []]
-	protected array $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 = CacheManager::for('integrations_' . $this->cacheName, $this->ttl);
-
-		// Load error stats from cache
-		$this->loadErrorStats();
+		$this->cache = Cache::for('integrations_' . $this->cacheName);
 
 		$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()) {
-				// Token expired, try to refresh
-				if (!empty($this->credentials['refresh_token'])) {
-					return $this->refreshOAuthToken();
-				}
-				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 = [];
+		$this->syncPostTypes = Registrar::withIntegration($this->service_name, 'post');
+	}
 
-			// Get from JVB_CONTENT
-
-			foreach (JVB_CONTENT as $type => $config) {
-				if (array_key_exists('integrations', $config) && array_key_exists($this->service_name, $config['integrations'])) {
-					$postTypes[] = $type;
-				}
-			}
-
-			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 = [];
-			// Get from JVB_TAXONOMY (content taxonomies)
-			foreach (JVB_TAXONOMY as $type => $config) {
-				if (jvbCheck('is_content', $config) &&
-					isset($config['integrations'][$this->service_name])) {
-					$taxonomies[] = $type;
-				}
-			}
-
-			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();
-		}
-		error_log('Action result: '.print_r($result, true));
-		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');
 	}
 
 	/*********************************************************************
@@ -462,22 +113,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
@@ -500,46 +135,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
@@ -549,31 +171,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));
 		}
 	}
 
@@ -582,10 +191,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
@@ -655,9 +262,7 @@
 		}
 
 		try {
-			error_log('Credentials to save: '.print_r($this->credentials, true));
 			if ($this->isOAuthService && !$this->hasOAuthCredentials()){
-				error_log('Just saving credentials, we don\'t have OAuth setup yet...');
 				//If this is an OAuth service, we might only be saving the app credentials first
 				$result = true;
 			} else {
@@ -676,9 +281,9 @@
 
 	protected function clearCache():array
 	{
-		$success = $this->cache->clear();
+		$this->cache->flush();
 		return [
-			'success'	=> $success,
+			'success'	=> true,
 		];
 	}
 
@@ -714,316 +319,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
-	{
-		$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.');
-		}
-
-		// Debug: Check if credentials are loaded
-		error_log('['.$this->service_name.'] Make Request - Credentials loaded: ' . (!empty($this->credentials) ? 'Yes' : 'No'));
-		error_log('With Credentials: '.print_r($this->credentials, true));
-
-		$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);
-		$ttl = $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
@@ -1044,11 +339,8 @@
 	):bool {
 		$queue = JVB()->queue();
 
-		// Add service prefix to operation type
-		$operation_type = strtolower($this->service_name) . '_' . $type;
-
 		$queued =  $queue->queueOperation(
-			$operation_type,
+			$type,
 			$this->userID ?? 0,
 			array_merge($data, ['service' => $this->service_name, 'user' => $this->userID??0]),
 			array_merge([
@@ -1090,117 +382,25 @@
 	 *********************************************************************/
 
 	/**
-	 * 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);
+			$this->addSavePost();
 			add_action('transition_post_status', [$this, 'handlePostStatusTransition'], 10, 3);
 
 			if ($this->canSync['delete']) {
@@ -1213,71 +413,16 @@
 				add_action('pre_delete_term', [$this, 'handleDeleteTerm'], 10, 2);
 			}
 		}
+
+		if (method_exists($this, 'registerQueueTypes')) {
+			add_action('init', [$this, 'registerQueueTypes'], 10);
+		}
 	}
 
-	public function handleAjaxResponse()
-	{
-		error_log('Ajax Response: '.print_r($_GET, true));
-
-		$code = $_GET['code'];
-		$state =  $_GET['state'];
-
-		error_log('OAuth Callback - Code: ' . $code);
-		error_log('OAuth Callback - State: ' . $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');
-
-		error_log('Service: '.print_r($this->service_name, true));
-		$state_data = get_transient('oauth_state_' . $state_key);
-		error_log('State Data: '.print_r($state_data, true));
-		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);
-		error_log('Return URL: '.print_r($return_url, true));
-		// 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
 	 *
@@ -1298,11 +443,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 [
@@ -1335,680 +480,34 @@
 		}
 	}
 
-	/**
-	 * 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();
+		if ($userID !== $this->userID) {
+			$this->switchUser($userID);
 		}
-		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 $this->hasOAuth && $this->hasValidOAuth();
 	}
 
 
 	/**
 	 * 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()) {
-					$this->logDebug('OAuth token expired, attempting refresh');
-					if (!$this->refreshOAuthToken()) {
-						$this->logError('Failed to refresh expired OAuth token');
-					}
-				}
-				// Check if we should proactively refresh (before expiry)
-				elseif ($this->shouldRefreshToken()) {
-					$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
-					}
-				}
-			}
-			$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->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);
-		}
-
-		$auth_url = $this->oauth['authorize'] . '?' . http_build_query($params);
-
-		// Debug log for troubleshooting
-		error_log("Generated OAuth URL for {$this->service_name}: " . $auth_url);
-
-		return $auth_url;
-	}
-
-	/**
-	 * 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;
-		}
-
-		// Build refresh request data
-		$request_data = [
-			'client_id' => $this->credentials['client_id'],
-			'client_secret' => $this->credentials['client_secret'],
-			'refresh_token' => $this->credentials['refresh_token'],
-			'grant_type' => 'refresh_token'
-		];
-
-		// Use centralized OAuth request method
-		$response = $this->makeOAuthRequest('POST', $this->oauth['token'], $request_data);
-
-		if (is_wp_error($response)) {
-			$this->logError('Failed to refresh Square token', [
-				'error' => $response->get_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: Square returns 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
@@ -2017,686 +516,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 = new MetaManager($postID, 'post');
-		$post = get_post($postID);
-		$service_data = [];
-
-		foreach ($mapping as $wp_field => $service_field) {
-			$value = null;
-
-			// Check if it's a post field
-			if (property_exists($post, $wp_field)) {
-				$value = $post->$wp_field;
-			} else {
-				// It's a meta field
-				$value = $meta_manager->getValue($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
-	{
-		error_log('Testing For Save Post');
-		if (jvbNoSaveIt($postID, $post)) {
-			error_log('Excluded by jvbNoSaveIt');
-			return;
-		}
-		if (empty($this->syncPostTypes)) {
-			error_log('No Syncable post types');
-			return;
-		}
-
-		$config = JVB_CONTENT[jvbNoBase($post->post_type)]??null;
-		if (!$config) {
-			error_log('No Config set');
-			return;
-		}
-
-		$settings = $config['integrations'][$this->service_name]??null;
-		if (!$settings) {
-			error_log('No settings');
-			return;
-		}
-
-		$fields = $this->getSyncFields($postID, 'post', ['schedule_'.$this->service_name]);
-		error_log('Fields to check: '.print_r($fields, true));
-		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]) {
-			error_log('Do not keep synced, not syncing with '.$this->service_name);
-			return;
-		}
-
-		if ($post->post_status !== 'publish' && !$isShared) {
-			error_log('Not published and not already shared');
-			return;
-		}
-		error_log('Sending to integration for processing...');
-		$this->handleTheSavePost($postID, $post, $update, $settings);
-	}
-
-
-	protected function getSyncFields(int $postID, string $type, array $additional = []):array
-	{
-		$meta = new MetaManager($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;
-		}
-		// Check if taxonomy is content-type
-		$config = JVB_TAXONOMY[$noBase] ?? null;
-		if (!$config || !($config['is_content'] ?? false)) {
-			return;
-		}
-
-		$settings = $config['integrations'][$this->service_name] ?? null;
-		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();
-
-		$this->logDebug('Integration health manually reset', [
-			'reset_by' => get_current_user_id(),
-			'reset_time' => time()
-		]);
-	}
-
-	/**
-	 * 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
@@ -2709,23 +535,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 static::getInstance()::$hasExtraOptions;
+	}
 
 	/**
 	 * Get service description (optional)
@@ -2736,80 +558,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)) {
-			$this->logDebug('Webhook already processed', [
-				'webhook_id' => $this->extractWebhookId($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
@@ -2831,542 +579,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 = new MetaManager($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 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.'_';
-						$meta->render('form', $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';
-							$meta->render('form', $name, $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 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;
-		error_log('handleAdminPost: '.print_r($_POST, true));
-		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 = new MetaManager($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';
-				$meta->render('form', $name, $config);
-			}
-			foreach ($this->syncPostTypes as $type) {
-				$config = JVB_CONTENT[$type];
-				?>
-				<details>
-					<summary><?= jvbIcon($config['icon']) ?><?= $config['singular']?> Defaults</summary>
-					<?php
-					$fields = new \JVBase\registry\providers\IntegrationFieldProvider();
-					$fields = $fields->getIntegrationFields($this->service_name, $config);
-					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']);
-						}
-						$meta->render('form', $name, $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 [];
-		}
-
-		$key = BASE.$this->service_name.'_enabled_content_types';
-		$enabled = get_option($key);
-		if (!$enabled) {
-			$enabled = [];
-			foreach (array_merge(JVB_CONTENT, JVB_TAXONOMY) as $content => $config) {
-				if (!array_key_exists('integrations', $config)) {
-					continue;
-				}
-				if (!array_key_exists($this->service_name, $config['integrations'])) {
-					continue;
-				}
-				if (!array_key_exists('content_type', $config['integrations'][$this->service_name])) {
-					continue;
-				}
-				$type = $config['integrations'][$this->service_name]['content_type'];
-				if (!in_array($type, $enabled)) {
-					$enabled[] = $type;
-				}
-			}
-			update_option($key, $enabled);
-		}
-		return $enabled;
+		$cred = Auth::getInstance();
+		$cred->updateTested($this->service_name, $this->userID);
 	}
 
 	protected function getSupportedImage(int $imgID):int
@@ -3386,145 +606,29 @@
 		if ($jpegVersion !== '' && is_int($jpegVersion)) {
 			return $jpegVersion;
 		}
-
-		$converted = $this->convertWebpToJpeg($imgID);
-		if ($converted) {
-			update_post_meta($imgID, BASE . 'jpeg_version', $converted);
-			return $converted;
+		$uploader = new UploadManager();
+		$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.
+	 * Used by JVBase\registrar\helpers\AddIntegrationFields.php
+	 * @return array
 	 */
-	protected function convertWebpToJpeg(int $webpAttachmentId):int|false
+	public function getAdditionalFields(?string $content_type = null):array
 	{
-		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;
-		}
+		return [];
 	}
 
-	/**
-	* Convert WebP to JPEG using Imagick
-	*/
-	protected function convertWebpToJpegImagick(string $source, string $destination): void
+	public function getIcon():string
 	{
-		$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();
+		return $this->icon;
 	}
 
-	/**
-	 * 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');
-		}
-	}
 }

--
Gitblit v1.10.0