| | |
| | | */ |
| | | |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Admin; |
| | | use JVBase\integrations\Instance; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Post; |
| | | use JVBase\integrations\OAuth; |
| | | use JVBase\integrations\OAuthCredentials; |
| | | use JVBase\integrations\OAuthURLs; |
| | | use JVBase\integrations\Requests; |
| | | use JVBase\integrations\SyncDefaults; |
| | | use JVBase\integrations\SyncFrom; |
| | | use JVBase\integrations\SyncTo; |
| | | use JVBase\integrations\UserConnection; |
| | | use WP_Error; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | |
| | | |
| | | class Facebook extends Integrations |
| | | { |
| | | // Facebook-specific properties |
| | | protected array $allowedContent = [ |
| | | 'post', |
| | | 'photo', |
| | | 'video', |
| | | 'event', |
| | | 'offer', |
| | | 'note', |
| | | 'milestone' |
| | | ]; |
| | | private string $page_id = ''; |
| | | private string $page_access_token = ''; |
| | | private array $permissions = []; |
| | | private array $pages = []; |
| | | use Admin, |
| | | Instance, |
| | | OAuth, |
| | | UserConnection, |
| | | Requests, |
| | | SyncFrom, |
| | | SyncTo, |
| | | SyncDefaults; |
| | | |
| | | // Post type mapping for Facebook |
| | | private const FB_POST_TYPES = [ |
| | | 'post' => 'feed', |
| | | 'photo' => 'photos', |
| | | 'video' => 'videos', |
| | | 'event' => 'events', |
| | | 'offer' => 'offers', |
| | | 'note' => 'notes', |
| | | 'milestone' => 'milestones' |
| | | ]; |
| | | |
| | | protected static array $instances = []; |
| | | public static function getInstance(?int $userID = null):self |
| | | { |
| | | $key = is_null($userID) ? 'base' : $userID; |
| | | if (!array_key_exists($key, self::$instances)) { |
| | | self::$instances[$key] = new self($userID); |
| | | } |
| | | return self::$instances[$key]; |
| | | } |
| | | |
| | | protected function __construct(?int $userID = null) |
| | | protected function __construct() |
| | | { |
| | | $this->service_name = 'facebook'; |
| | | $this->title = 'Facebook'; |
| | | $this->icon = 'facebook-logo'; |
| | | |
| | | // API Configuration |
| | | $this->apiBase = [ |
| | | 'graph' => 'https://graph.facebook.com/v18.0', |
| | | 'auth' => 'https://www.facebook.com/v18.0' |
| | | ]; |
| | | $this->apiVersion = 'v25.0'; |
| | | |
| | | $this->apiVersion = 'v18.0'; |
| | | |
| | | $this->isOAuthService = true; |
| | | // OAuth Configuration |
| | | $this->oauth = [ |
| | | 'authorize' => 'https://www.facebook.com/v18.0/dialog/oauth', |
| | | 'token' => 'https://graph.facebook.com/v18.0/oauth/access_token', |
| | | 'revoke' => 'https://graph.facebook.com/v18.0/me/permissions', |
| | | 'scopes' => [ |
| | | 'pages_show_list', |
| | | 'pages_read_engagement', |
| | | 'pages_manage_posts', |
| | | 'pages_manage_metadata', |
| | | 'pages_manage_engagement', |
| | | 'pages_read_user_content', |
| | | 'business_management', |
| | | 'instagram_basic', |
| | | 'instagram_content_publish' |
| | | ] |
| | | ]; |
| | | |
| | | // Sync capabilities |
| | | $this->canSync = [ |
| | | 'initial' => true, |
| | | 'update' => true, |
| | | 'delete' => true |
| | | 'create' => true, |
| | | 'update' => true, |
| | | 'delete' => true |
| | | ]; |
| | | |
| | | // Rate limits for Facebook Graph API |
| | | $this->rate_limits = [ |
| | | 'per_second' => 10, |
| | | 'per_minute' => 600, |
| | | 'per_hour' => 200000 |
| | | ]; |
| | | $this->defineFields(); |
| | | $this->defineInstructions(); |
| | | $this->defineOAuth(); |
| | | $this->defineActions(); |
| | | |
| | | $this->fields = [ |
| | | 'client_id' => [ |
| | | 'type' => 'text', |
| | | 'placeholder' => 'Enter Facebook App ID', |
| | | 'hint' => 'Your Facebook App ID from developers.facebook.com', |
| | | 'label' => 'App ID', |
| | | 'required' => true, |
| | | ], |
| | | 'client_secret' => [ |
| | | 'type' => 'text', |
| | | 'subtype'=> 'password', |
| | | 'label' => 'App Secret', |
| | | 'required' => true, |
| | | 'hint' => 'Your Facebook App Secret (keep this secure!)' |
| | | ], |
| | | ]; |
| | | |
| | | $this->advanced = [ |
| | | |
| | | ]; |
| | | |
| | | $this->instructions = [ |
| | | |
| | | ]; |
| | | |
| | | $this->defaults = [ |
| | | |
| | | ]; |
| | | $this->hasWebhooks = true; |
| | | $this->baseUrl = 'https://graph.facebook.com/'.$this->apiVersion; |
| | | $this->baseOAuthUrl = 'https://www.facebook.com/'.$this->apiVersion; |
| | | |
| | | parent::__construct(); |
| | | |
| | | $this->actions = array_merge($this->actions, [ |
| | | 'sync_pages' => 'getUserPages' |
| | | ]); |
| | | } |
| | | |
| | | /** |
| | | * Initialize Facebook-specific properties |
| | | */ |
| | | protected function initialize(): void |
| | | { |
| | | $this->page_id = $this->credentials['page_id'] ?? ''; |
| | | $this->page_access_token = $this->credentials['page_access_token'] ?? ''; |
| | | $this->permissions = $this->credentials['permissions'] ?? []; |
| | | protected function defineFields():void |
| | | { |
| | | $client_id = $this->addField( |
| | | 'client_id', |
| | | 'App ID', |
| | | 'text', |
| | | 'manage_options' |
| | | ); |
| | | $client_id->setRequired(); |
| | | $client_id->setHint('Your Facebook App ID from developers.facebook.com'); |
| | | |
| | | // Build dynamic endpoints based on page |
| | | if ($this->page_id) { |
| | | $this->apiEndpoints = [ |
| | | "/{$this->page_id}/feed", |
| | | "/{$this->page_id}/photos", |
| | | "/{$this->page_id}/videos", |
| | | "/{$this->page_id}/events", |
| | | "/{$this->page_id}/insights", |
| | | "/{$this->page_id}/conversations", |
| | | "/{$this->page_id}", |
| | | "/me/accounts" |
| | | $client_secret = $this->addField( |
| | | 'client_secret', |
| | | 'App Secret', |
| | | 'text', |
| | | 'manage_options' |
| | | ); |
| | | $client_secret->setRequired(); |
| | | $client_secret->setSubType('password'); |
| | | |
| | | $pages = $this->addField('pages', 'Pages', 'select', [$this, 'testUserCanConnect']); |
| | | $pages->setHidden(); |
| | | $pages->setOptions($this->getPageOptions()); |
| | | |
| | | $page = $this->addField('page_id', 'Connect to Page', 'select', [$this, 'testUserCanConnect']); |
| | | $page->setOptions($this->loadCredentials()['pages']??[]); |
| | | $page->setHint('Select the page you want to connect this site to.'); |
| | | } |
| | | public function testUserCanConnect():bool |
| | | { |
| | | $user = get_current_user_id(); |
| | | return $this->userCanConnect($user); |
| | | } |
| | | protected function getPageOptions():array |
| | | { |
| | | return $this->loadCredentials()['pages']??[]; |
| | | } |
| | | protected function defineInstructions():void |
| | | { |
| | | |
| | | } |
| | | protected function defineActions():void |
| | | { |
| | | $this->addAction( |
| | | 'Select Page', |
| | | [$this, 'handleSelectPage'], |
| | | 'select_page', |
| | | 'plugs-connected' |
| | | ); |
| | | |
| | | $this->addAction( |
| | | 'Fetch Pages', |
| | | [$this, 'getPages'], |
| | | 'fetch_pages', |
| | | 'download-simple' |
| | | ); |
| | | } |
| | | public function defineOAuth():void |
| | | { |
| | | $this->oauth = new OAuthURLs( |
| | | 'https://www.facebook.com/'.$this->apiVersion.'/dialog/oauth', |
| | | 'https://graph.facebook.com/'.$this->apiVersion.'/oauth/access_token', |
| | | 'https://graph.facebook.com/'.$this->apiVersion.'/me/permissions' |
| | | ); |
| | | $this->oathScopes = [ |
| | | 'pages_show_list', |
| | | 'business_management', |
| | | 'pages_manage_metadata', |
| | | 'pages_manage_posts' |
| | | ]; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get service description |
| | | */ |
| | | public function getServiceDescription(): string |
| | | public function getServiceDescription():string |
| | | { |
| | | return 'Connect your Facebook Page to share posts, events, and engage with your audience.'; |
| | | } |
| | | protected function initialize(): void |
| | | { |
| | | return "Connect your Facebook Page to share posts, events, and engage with your audience."; |
| | | // TODO: Implement initialize() method. |
| | | } |
| | | |
| | | /** |
| | | * Get request headers for Facebook API |
| | | */ |
| | | protected function refreshAccessToken(OAuthCredentials $credentials): array |
| | | { |
| | | return $this->response(false, 'Not set up yet'); |
| | | } |
| | | |
| | | protected function getRequestHeaders(): array |
| | | { |
| | | $headers = [ |
| | | return [ |
| | | 'Content-Type' => 'application/json', |
| | | 'Accept' => 'application/json' |
| | | ]; |
| | | |
| | | // Use page access token if available, otherwise user token |
| | | if (!empty($this->page_access_token)) { |
| | | // Token is sent in URL params for Facebook, not headers |
| | | } |
| | | |
| | | return $headers; |
| | | } |
| | | |
| | | /** |
| | | * Add Facebook-specific OAuth parameters |
| | | */ |
| | | protected function addOAuthParams(array $params): array |
| | | |
| | | protected function handleResponse(\WP_Error|array $response, string $method, string $endpoint): array |
| | | { |
| | | $params['display'] = 'popup'; |
| | | $params['auth_type'] = 'rerequest'; |
| | | $params['return_scopes'] = 'true'; |
| | | return $params; |
| | | } |
| | | |
| | | /** |
| | | * Add Facebook-specific credential data after OAuth |
| | | */ |
| | | protected function addCredentialData(array $credentials, array $tokens): array |
| | | { |
| | | // Get user's pages after OAuth |
| | | try { |
| | | $pages = $this->getUserPages($tokens['access_token']); |
| | | if (!empty($pages)) { |
| | | $credentials['pages'] = $pages; |
| | | // Auto-select first page if not already selected |
| | | if (empty($credentials['page_id']) && !empty($pages[0])) { |
| | | $credentials['page_id'] = $pages[0]['id']; |
| | | $credentials['page_access_token'] = $pages[0]['access_token']; |
| | | $credentials['page_name'] = $pages[0]['name']; |
| | | } |
| | | } |
| | | |
| | | // Store permissions granted |
| | | $permissions = $this->getGrantedPermissions($tokens['access_token']); |
| | | $credentials['permissions'] = $permissions; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to fetch Facebook pages', ['error' => $e->getMessage()]); |
| | | if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') { |
| | | return $this->response(false, 'Rate limit exceeded'); |
| | | } |
| | | |
| | | return $credentials; |
| | | $endpoint = str_replace($this->baseUrl, '', $endpoint); |
| | | return match ($endpoint) { |
| | | '/me/accounts?access_token='.$this->getAccessToken() => $this->handleGetPagesResponse($response, $method), |
| | | default => $this->response(false, 'Unhandled Response for ' . $endpoint . ' endpoint, with method: ' . $method), |
| | | }; |
| | | } |
| | | /************************************************************ |
| | | * UTILITY METHODS |
| | | ************************************************************/ |
| | | protected function getPageID():string|false |
| | | { |
| | | return $this->loadCredentials()['page_id']??false; |
| | | } |
| | | protected function getClientID():string|false |
| | | { |
| | | return $this->loadCredentials()['client_id']??false; |
| | | } |
| | | protected function getClientSecret():string|false |
| | | { |
| | | return $this->loadCredentials()['client_secret']??false; |
| | | } |
| | | |
| | | /** |
| | | * Test Facebook connection |
| | | */ |
| | | protected function performConnectionTest(): bool |
| | | protected function getFBUserID():string|false |
| | | { |
| | | if (empty($this->credentials['access_token'])) { |
| | | return false; |
| | | } |
| | | |
| | | try { |
| | | // Test by fetching user info |
| | | $response = wp_remote_get( |
| | | $this->apiBase['graph'] . '/me?access_token=' . $this->credentials['access_token'] |
| | | $cred = $this->loadCredentials(); |
| | | if ($this->isSetUp() && !array_key_exists('user_id', $cred)) { |
| | | $resp = $this->getRequest( |
| | | $this->baseUrl.'/me?access_token='.$cred['access_token'], |
| | | ); |
| | | if ($resp && !is_wp_error($resp)) { |
| | | $tmp['user_id'] = $resp['id']; |
| | | $tmp['user_name'] = $resp['name']; |
| | | |
| | | if (is_wp_error($response)) { |
| | | return false; |
| | | $this->saveCredentials($tmp, false); |
| | | } |
| | | |
| | | $code = wp_remote_retrieve_response_code($response); |
| | | return $code === 200; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('Connection test failed', ['error' => $e->getMessage()]); |
| | | return false; |
| | | $cred = array_merge($cred, $tmp); |
| | | } |
| | | return $cred['user_id']??false; |
| | | } |
| | | |
| | | /** |
| | | * Render Additional OAuth settings |
| | | */ |
| | | protected function renderOAuthConnectedOptions(): void |
| | | public function getPages():array|false |
| | | { |
| | | // Page selection dropdown |
| | | if (!empty($this->credentials['pages'])) { |
| | | ?> |
| | | <div class="form-field"> |
| | | <label for="fb_page">Active Facebook Page</label> |
| | | <select name="credentials[page_id]" id="fb_page"> |
| | | <option value="">Select a page...</option> |
| | | <?php foreach ($this->credentials['pages'] as $page): ?> |
| | | <option value="<?php echo esc_attr($page['id']); ?>" |
| | | <?php selected($this->credentials['page_id'] ?? '', $page['id']); ?>> |
| | | <?php echo esc_html($page['name']); ?> |
| | | </option> |
| | | <?php endforeach; ?> |
| | | </select> |
| | | </div> |
| | | <?php |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Validate webhook from Facebook |
| | | */ |
| | | protected function validateWebhook(array $payload): bool |
| | | { |
| | | // Handle webhook verification challenge |
| | | if (isset($payload['hub_mode']) && $payload['hub_mode'] === 'subscribe') { |
| | | $verify_token = $this->credentials['webhook_verify_token'] ?? ''; |
| | | if ($payload['hub_verify_token'] === $verify_token) { |
| | | // Echo the challenge for verification |
| | | echo $payload['hub_challenge']; |
| | | return true; |
| | | } |
| | | if (!$this->getAccessToken()) { |
| | | return false; |
| | | } |
| | | |
| | | // Validate webhook signature for actual events |
| | | $headers = $payload['_headers'] ?? []; |
| | | $signature = $headers['x-hub-signature-256'] ?? ''; |
| | | |
| | | if (empty($signature)) { |
| | | $this->logError('Missing webhook signature'); |
| | | return false; |
| | | } |
| | | |
| | | // Calculate expected signature |
| | | $app_secret = $this->credentials['client_secret'] ?? ''; |
| | | $expected = 'sha256=' . hash_hmac('sha256', json_encode($payload), $app_secret); |
| | | |
| | | return hash_equals($expected, $signature); |
| | | } |
| | | |
| | | /** |
| | | * Process webhook from Facebook |
| | | */ |
| | | protected function processWebhook(array $payload): bool |
| | | { |
| | | try { |
| | | $object = $payload['object'] ?? ''; |
| | | $entry = $payload['entry'] ?? []; |
| | | |
| | | foreach ($entry as $item) { |
| | | $changes = $item['changes'] ?? []; |
| | | |
| | | foreach ($changes as $change) { |
| | | $field = $change['field'] ?? ''; |
| | | $value = $change['value'] ?? []; |
| | | |
| | | switch ($field) { |
| | | case 'feed': |
| | | $this->handleFeedUpdate($value); |
| | | break; |
| | | |
| | | case 'conversations': |
| | | $this->handleMessageUpdate($value); |
| | | break; |
| | | |
| | | case 'photos': |
| | | $this->handlePhotoUpdate($value); |
| | | break; |
| | | |
| | | default: |
| | | $this->logDebug('Unhandled webhook field', ['field' => $field]); |
| | | } |
| | | return $this->getRequest( |
| | | $this->apiBase.'/me/accounts?access_token='.$this->getAccessToken(), |
| | | ); |
| | | } catch (Exception $e) { |
| | | $this->logError( |
| | | 'getPages', |
| | | 'Failed to load pages', |
| | | [ |
| | | 'error' => $e->getMessage(), |
| | | ] |
| | | ); |
| | | } |
| | | return []; |
| | | } |
| | | protected function handleGetPagesResponse(WP_Error|array $response, string $method):array |
| | | { |
| | | $options = []; |
| | | foreach ($response as $page) { |
| | | if (array_key_exists('id', $page) && array_key_exists('name', $page)) { |
| | | $options[$page['id']] = $page['name']; |
| | | } |
| | | } |
| | | |
| | | return true; |
| | | |
| | | } catch (Exception $e) { |
| | | $this->logError('Webhook processing failed', [ |
| | | 'error' => $e->getMessage(), |
| | | 'payload' => $payload |
| | | ]); |
| | | return false; |
| | | if (!empty($options)) { |
| | | $this->saveCredentials(['pages' => $options]); |
| | | } |
| | | return $this->response(true, 'Found pages', $options); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Handle post save for Facebook sync |
| | | */ |
| | | protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void |
| | | /************************************************************ |
| | | * ACTIONS |
| | | ************************************************************/ |
| | | public function handleSelectPage(array $data):array |
| | | { |
| | | |
| | | if (!$this->hasOAuthCredentials()) { |
| | | error_log('OAuth Not set up for '.$this->service_name); |
| | | return; |
| | | if (empty($data['page_id']??'')) { |
| | | return $this->response(false, 'No page selected.'); |
| | | } |
| | | $this->queueOperation(self::$syncTo, [ |
| | | 'items' => [$postID], |
| | | 'user' => user_can($post->post_author, 'manage_options') ? null : $post->post_author |
| | | ], [ |
| | | 'delay' => 60 |
| | | ]); |
| | | Meta::forPost($postID)->set('_'.$this->service_name.'_sync_status', 'queued'); |
| | | |
| | | $cred = [ |
| | | 'page_id' => sanitize_text_field($data['page_id']), |
| | | ]; |
| | | return $this->saveCredentials($cred, false); |
| | | } |
| | | |
| | | /** |
| | | * Process queued operations |
| | | */ |
| | | public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|array |
| | | public function handleFetchPages():array |
| | | { |
| | | try { |
| | | $fb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this; |
| | | switch ($operation->type) { |
| | | case 'facebook_create_post': |
| | | return $fb->createFacebookPost($data); |
| | | |
| | | case 'facebook_update_post': |
| | | return $fb->updateFacebookPost($data); |
| | | |
| | | case 'facebook_delete_post': |
| | | return $fb->deleteFacebookPost($data); |
| | | |
| | | case 'facebook_create_event': |
| | | return $fb->createFacebookEvent($data); |
| | | |
| | | default: |
| | | return $result; |
| | | $pages = $this->getPages(); |
| | | if (!$pages) { |
| | | return $this->response(false, 'No pages found'); |
| | | } |
| | | |
| | | return $this->response( |
| | | true, |
| | | sprintf('Found %d page(s)', count($pages)), |
| | | $pages |
| | | ); |
| | | } catch (Exception $e) { |
| | | $this->logError('Operation failed', [ |
| | | 'type' => $operation->type, |
| | | 'error' => $e->getMessage() |
| | | ]); |
| | | return new WP_Error('operation_failed', $e->getMessage()); |
| | | return $this->response(false, 'Failed to fetch pages: '.$e->getMessage()); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Create a Facebook post |
| | | */ |
| | | private function createFacebookPost(array $data): array |
| | | protected function setContentTypes(): void |
| | | { |
| | | $post = get_post($data['post_id']); |
| | | if (!$post) { |
| | | return ['success' => false, 'error' => 'Post not found']; |
| | | } |
| | | |
| | | // Build Facebook post data |
| | | $fb_data = [ |
| | | 'message' => $this->formatPostContent($post), |
| | | 'access_token' => $this->page_access_token |
| | | ]; |
| | | |
| | | // Add link if present |
| | | $link = get_post_meta($post->ID, 'link_url', true); |
| | | if ($link) { |
| | | $fb_data['link'] = $link; |
| | | } |
| | | |
| | | // Handle featured image |
| | | if (has_post_thumbnail($post->ID)) { |
| | | $image_url = get_the_post_thumbnail_url($post->ID, 'large'); |
| | | $fb_data['picture'] = $image_url; |
| | | } |
| | | |
| | | // Determine endpoint |
| | | $endpoint = $data['endpoint'] ?? 'feed'; |
| | | $url = $this->apiBase['graph'] . '/' . $this->page_id . '/' . $endpoint; |
| | | |
| | | // Make the API request |
| | | $response = wp_remote_post($url, [ |
| | | 'body' => $fb_data, |
| | | 'timeout' => 30 |
| | | ]); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return ['success' => false, 'error' => $response->get_error_message()]; |
| | | } |
| | | |
| | | $body = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | if (isset($body['id'])) { |
| | | // Store Facebook post ID |
| | | update_post_meta($post->ID, BASE . '_facebook_post_id', $body['id']); |
| | | update_post_meta($post->ID, BASE . '_facebook_sync_time', time()); |
| | | |
| | | return [ |
| | | 'success' => true, |
| | | 'facebook_id' => $body['id'], |
| | | 'message' => 'Posted to Facebook successfully' |
| | | ]; |
| | | } |
| | | |
| | | return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error']; |
| | | // TODO: Implement setContentTypes() method. |
| | | } |
| | | |
| | | /** |
| | | * Update a Facebook post |
| | | */ |
| | | private function updateFacebookPost(array $data): array |
| | | protected function formatForService(int $itemID, string $type = 'post'): array |
| | | { |
| | | $post = get_post($data['post_id']); |
| | | $fb_post_id = get_post_meta($post->ID, BASE . '_facebook_post_id', true); |
| | | |
| | | if (!$fb_post_id) { |
| | | // No existing post, create new one |
| | | return $this->createFacebookPost($data); |
| | | } |
| | | |
| | | // Facebook doesn't allow editing most post types after creation |
| | | // We can only update certain fields like message |
| | | $fb_data = [ |
| | | 'message' => $this->formatPostContent($post), |
| | | 'access_token' => $this->page_access_token |
| | | ]; |
| | | |
| | | $url = $this->apiBase['graph'] . '/' . $fb_post_id; |
| | | |
| | | $response = wp_remote_request($url, [ |
| | | 'method' => 'POST', |
| | | 'body' => $fb_data |
| | | ]); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return ['success' => false, 'error' => $response->get_error_message()]; |
| | | } |
| | | |
| | | update_post_meta($post->ID, BASE . '_facebook_sync_time', time()); |
| | | |
| | | return ['success' => true, 'message' => 'Updated on Facebook']; |
| | | } |
| | | |
| | | /** |
| | | * Delete a Facebook post |
| | | */ |
| | | private function deleteFacebookPost(array $data): array |
| | | { |
| | | $fb_post_id = get_post_meta($data['post_id'], BASE . '_facebook_post_id', true); |
| | | |
| | | if (!$fb_post_id) { |
| | | return ['success' => true, 'message' => 'No Facebook post to delete']; |
| | | } |
| | | |
| | | $url = $this->apiBase['graph'] . '/' . $fb_post_id . '?access_token=' . $this->page_access_token; |
| | | |
| | | $response = wp_remote_request($url, ['method' => 'DELETE']); |
| | | |
| | | if (!is_wp_error($response)) { |
| | | delete_post_meta($data['post_id'], BASE . '_facebook_post_id'); |
| | | delete_post_meta($data['post_id'], BASE . '_facebook_sync_time'); |
| | | } |
| | | |
| | | return ['success' => true, 'message' => 'Deleted from Facebook']; |
| | | } |
| | | |
| | | /** |
| | | * Create a Facebook event |
| | | */ |
| | | private function createFacebookEvent(array $data): array |
| | | { |
| | | $post = get_post($data['post_id']); |
| | | $meta = Meta::forPost($post->ID); |
| | | |
| | | $event_data = [ |
| | | 'name' => $post->post_title, |
| | | 'description' => $this->formatPostContent($post), |
| | | 'start_time' => $meta->get('event_start_date'), |
| | | 'end_time' => $meta->get('event_end_date'), |
| | | 'access_token' => $this->page_access_token |
| | | ]; |
| | | |
| | | // Add location if available |
| | | $location = $meta->get('event_location'); |
| | | if ($location) { |
| | | $event_data['location'] = $location; |
| | | } |
| | | |
| | | $url = $this->apiBase['graph'] . '/' . $this->page_id . '/events'; |
| | | |
| | | $response = wp_remote_post($url, [ |
| | | 'body' => $event_data |
| | | ]); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return ['success' => false, 'error' => $response->get_error_message()]; |
| | | } |
| | | |
| | | $body = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | if (isset($body['id'])) { |
| | | update_post_meta($post->ID, BASE . '_facebook_event_id', $body['id']); |
| | | return ['success' => true, 'event_id' => $body['id']]; |
| | | } |
| | | |
| | | return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error']; |
| | | } |
| | | |
| | | /** |
| | | * Helper: Get user's Facebook pages |
| | | */ |
| | | private function getUserPages(?string $access_token = null): array |
| | | { |
| | | $token = $access_token ?: $this->credentials['access_token']; |
| | | |
| | | $url = $this->apiBase['graph'] . '/me/accounts?access_token=' . $token; |
| | | |
| | | $response = wp_remote_get($url); |
| | | |
| | | if (is_wp_error($response)) { |
| | | throw new Exception($response->get_error_message()); |
| | | } |
| | | |
| | | $body = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | if (isset($body['error'])) { |
| | | throw new Exception($body['error']['message']); |
| | | } |
| | | |
| | | return $body['data'] ?? []; |
| | | } |
| | | |
| | | /** |
| | | * Helper: Get granted permissions |
| | | */ |
| | | private function getGrantedPermissions(string $access_token): array |
| | | { |
| | | $url = $this->apiBase['graph'] . '/me/permissions?access_token=' . $access_token; |
| | | |
| | | $response = wp_remote_get($url); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return []; |
| | | } |
| | | |
| | | $body = json_decode(wp_remote_retrieve_body($response), true); |
| | | $permissions = []; |
| | | |
| | | foreach (($body['data'] ?? []) as $permission) { |
| | | if ($permission['status'] === 'granted') { |
| | | $permissions[] = $permission['permission']; |
| | | } |
| | | } |
| | | |
| | | return $permissions; |
| | | } |
| | | |
| | | /** |
| | | * Helper: Format post content for Facebook |
| | | */ |
| | | private function formatPostContent(WP_Post $post): string |
| | | { |
| | | $content = $post->post_content; |
| | | |
| | | // Strip shortcodes |
| | | $content = strip_shortcodes($content); |
| | | |
| | | // Convert to plain text |
| | | $content = wp_strip_all_tags($content); |
| | | |
| | | // Add link to full post |
| | | $permalink = get_permalink($post->ID); |
| | | $content .= "\n\nRead more: " . $permalink; |
| | | |
| | | // Trim to Facebook's character limit (63,206 characters) |
| | | if (strlen($content) > 63000) { |
| | | $content = substr($content, 0, 63000) . '...'; |
| | | } |
| | | |
| | | return $content; |
| | | } |
| | | |
| | | /** |
| | | * Helper: Handle feed updates from webhook |
| | | */ |
| | | private function handleFeedUpdate(array $data): void |
| | | { |
| | | // Process incoming feed updates |
| | | // Could sync comments, reactions, etc. back to WordPress |
| | | $this->logDebug('Feed update received', $data); |
| | | } |
| | | |
| | | /** |
| | | * Helper: Handle message updates from webhook |
| | | */ |
| | | private function handleMessageUpdate(array $data): void |
| | | { |
| | | // Process incoming messages |
| | | // Could create notifications or queue responses |
| | | $this->logDebug('Message received', $data); |
| | | } |
| | | |
| | | /** |
| | | * Helper: Handle photo updates from webhook |
| | | */ |
| | | private function handlePhotoUpdate(array $data): void |
| | | { |
| | | // Process photo updates |
| | | $this->logDebug('Photo update received', $data); |
| | | } |
| | | |
| | | /** |
| | | * Send test post to Facebook |
| | | */ |
| | | private function sendTestPost(): array |
| | | { |
| | | $test_data = [ |
| | | 'message' => 'Test post from ' . get_bloginfo('name') . ' at ' . current_time('mysql'), |
| | | 'access_token' => $this->page_access_token |
| | | ]; |
| | | |
| | | $url = $this->apiBase['graph'] . '/' . $this->page_id . '/feed'; |
| | | |
| | | $response = wp_remote_post($url, [ |
| | | 'body' => $test_data |
| | | ]); |
| | | |
| | | if (is_wp_error($response)) { |
| | | return ['success' => false, 'error' => $response->get_error_message()]; |
| | | } |
| | | |
| | | $body = json_decode(wp_remote_retrieve_body($response), true); |
| | | |
| | | if (isset($body['id'])) { |
| | | return ['success' => true, 'post_id' => $body['id']]; |
| | | } |
| | | |
| | | return ['success' => false, 'error' => $body['error']['message'] ?? 'Unknown error']; |
| | | return []; |
| | | } |
| | | } |