From a239ef33ec489ce6d0f25ac2f256df0ac54a7e50 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 16 Sep 2026 00:07:34 +0000
Subject: [PATCH] =Tackling the integrations again. This time Helcim, with some minnor edits to GMB and Square
---
inc/integrations/services/Facebook.php | 1194 +++++++++++++++++++++++++++-------------------------------
1 files changed, 555 insertions(+), 639 deletions(-)
diff --git a/inc/integrations/services/Facebook.php b/inc/integrations/services/Facebook.php
index 842cc96..3c54f91 100644
--- a/inc/integrations/services/Facebook.php
+++ b/inc/integrations/services/Facebook.php
@@ -5,9 +5,19 @@
*/
namespace JVBase\integrations\services;
+use Exception;
+use JVBase\integrations\Admin;
+use JVBase\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 JVBase\meta\Sanitizer;
use WP_Error;
if (!defined('ABSPATH')) {
@@ -16,686 +26,592 @@
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 = 'v26.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' => false,
+ 'delete' => false
];
- // 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->defineContentTypes();
- $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->baseUrl = 'https://graph.facebook.com/'.$this->apiVersion;
+ $this->baseOAuthUrl = 'https://www.facebook.com/'.$this->apiVersion;
- $this->advanced = [
-
- ];
-
- $this->instructions = [
-
- ];
-
- $this->defaults = [
-
- ];
- $this->hasWebhooks = true;
+ $this->supportsWebp = false;
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.');
}
- }
-
- /**
- * Get service description
- */
- public function getServiceDescription(): string
- {
- return "Connect your Facebook Page to share posts, events, and engage with your audience.";
- }
-
- /**
- * Get request headers for Facebook API
- */
- protected function getRequestHeaders(): array
- {
- $headers = [
- '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
- {
- $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'];
- }
+ 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
+ {
- // 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()]);
}
-
- return $credentials;
- }
-
- /**
- * Test Facebook connection
- */
- protected function performConnectionTest(): bool
- {
- 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']
+ protected function defineActions():void
+ {
+ $this->addAction(
+ 'Select Page',
+ [$this, 'handleSelectPage'],
+ 'select_page',
+ 'plugs-connected'
);
- if (is_wp_error($response)) {
+ $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'
+ ];
+ }
+ public function getServiceDescription():string
+ {
+ return 'Connect your Facebook Page to share posts, events, and engage with your audience.';
+ }
+
+ protected function defineContentTypes():void
+ {
+ $base = $this->getBaseFields();
+ $this->contentTypes['post'] = array_merge($base, $this->contentTypePostFields());
+ $this->contentTypes['image'] = array_merge($base, $this->contentTypeImageFields());
+ //Looks like product requires some major heavy lifting. Best to skip for now
+// $this->contentTypes['product'] = array_merge($base, $this->contentTypeProductFields());
+
+
+ }
+ protected function getBaseFields():array
+ {
+ return [
+
+ ];
+ }
+ protected function contentTypePostFields():array
+ {
+ return [];
+ }
+ protected function contentTypeImageFields():array
+ {
+ return [];
+ }
+ protected function initialize(): void
+ {
+ // TODO: Implement initialize() method.
+ }
+
+ protected function refreshAccessToken(OAuthCredentials $credentials): array
+ {
+ return $this->response(false, 'Not set up yet');
+ }
+
+ protected function getRequestHeaders(): array
+ {
+ return [
+ 'Content-Type' => 'application/json',
+ 'Accept' => 'application/json',
+ ];
+ }
+
+
+ protected function handleResponse(WP_Error|array $response, string $method, string $endpoint): array
+ {
+ if (is_wp_error($response) && $response->get_error_message() === 'rate_limit_exceeded') {
+ return $this->response(false, 'Rate limit exceeded');
+ }
+
+ $errors = $this->checkErrors($response);
+ if (!empty($errors)) {
+ return $errors;
+ }
+
+ $endpoint = str_replace($this->baseUrl, '', $endpoint);
+ return match ($endpoint) {
+ '/me/accounts?access_token='.$this->getAccessToken() => $this->handleGetPagesResponse($response, $method),
+ default => $response
+ };
+ }
+ protected function checkErrors(WP_Error|array $response):array|false
+ {
+ if (!isset($response['error'])) {
return false;
}
-
- $code = wp_remote_retrieve_response_code($response);
- return $code === 200;
-
- } catch (Exception $e) {
- $this->logError('Connection test failed', ['error' => $e->getMessage()]);
- return false;
- }
- }
-
- /**
- * Render Additional OAuth settings
- */
- protected function renderOAuthConnectedOptions(): void
- {
- // 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;
+ $error = (string)$response['error']['code'];
+ switch ($error) {
+ //Invalid parameter
+ case '100':
+ //Too many calls by this account. Wait for a bit
+ case '80001':
+ //Permissions error
+ case '200':
+ //Action has been deemed abusive, or otherwise disallowed
+ case '368':
+ //Missing or invalid image file
+ case '324':
+ //Invalid OAuth 2.0 Access Token
+ case '190':
+ //Incorrect Signature
+ case '104':
+ //Desktop applications cannot call this function for other users
+ case '240':
+ //Requires additional permissions
+ case '283':
+ //Session is invalid; user has been checkpointed
+ case '459':
+ default:
+ $this->logError(
+ 'handleResponse',
+ 'Something went wrong: '.$response['Message']??'',
+ $response
+ );
+ return $this->response(false, 'Something went wrong: '.$response['Message']??'');
}
- 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);
+ /************************************************************
+ * 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;
}
- /**
- * Process webhook from Facebook
- */
- protected function processWebhook(array $payload): bool
+ protected function getFBUserID():string|false
+ {
+ $cred = $this->loadCredentials();
+ if ($this->isSetUp() && !array_key_exists('user_id', $cred)) {
+ $resp = $this->getRequest(
+ $this->baseUrl.'/me?access_token='.$cred['access_token'],
+ );
+ $tmp = [];
+ if ($resp && !is_wp_error($resp)) {
+ $tmp['user_id'] = $resp['id'];
+ $tmp['user_name'] = $resp['name'];
+
+ $this->saveCredentials($tmp, false);
+ }
+ $cred = array_merge($cred, $tmp);
+ }
+ return $cred['user_id']??false;
+ }
+ public function getPages():array|false
+ {
+ if (!$this->getAccessToken()) {
+ return false;
+ }
+
+ try {
+ 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'];
+ }
+ }
+ if (!empty($options)) {
+ $this->saveCredentials(['pages' => $options]);
+ }
+ return $this->response(true, 'Found pages', $options);
+ }
+
+ /************************************************************
+ * ACTIONS
+ ************************************************************/
+ public function handleSelectPage(array $data):array
+ {
+ if (empty($data['page_id']??'')) {
+ return $this->response(false, 'No page selected.');
+ }
+
+ $cred = [
+ 'page_id' => sanitize_text_field($data['page_id']),
+ ];
+ return $this->saveCredentials($cred, false);
+ }
+
+ public function handleFetchPages():array
{
try {
- $object = $payload['object'] ?? '';
- $entry = $payload['entry'] ?? [];
+ $pages = $this->getPages();
+ if (!$pages) {
+ return $this->response(false, 'No pages found');
+ }
- foreach ($entry as $item) {
- $changes = $item['changes'] ?? [];
+ return $this->response(
+ true,
+ sprintf('Found %d page(s)', count($pages)),
+ $pages
+ );
+ } catch (Exception $e) {
+ return $this->response(false, 'Failed to fetch pages: '.$e->getMessage());
+ }
+ }
- foreach ($changes as $change) {
- $field = $change['field'] ?? '';
- $value = $change['value'] ?? [];
+ protected function setContentTypes(): void
+ {
+ // TODO: Implement setContentTypes() method.
+ }
- switch ($field) {
- case 'feed':
- $this->handleFeedUpdate($value);
- break;
+ protected function formatForService(int $itemID, string $type = 'post'): array
+ {
+ return [];
+ }
- case 'conversations':
- $this->handleMessageUpdate($value);
- break;
+ public function getOrCreateImage(int $imgID, string $caption = '', bool $publish = true, bool $returnResponse = false):string|array|false
+ {
+ $imgID = $this->getSupportedImage($imgID);
- case 'photos':
- $this->handlePhotoUpdate($value);
- break;
+ $id = $this->getServiceItemID($imgID);
+ if (!empty($id)) {
+ return $id;
+ }
- default:
- $this->logDebug('Unhandled webhook field', ['field' => $field]);
- }
+ $url = wp_get_attachment_url($imgID);
+ if (!file_exists($url)) {
+ return false;
+ }
+
+ $args = [
+ 'url' => $url
+ ];
+ if (!empty($caption)) {
+ $caption = html_entity_decode(wp_strip_all_tags($caption));
+ if (!empty($caption)) {
+ $args['caption'] = $caption;
+ }
+ }
+
+ $alt_text = get_post_meta($imgID, '_wp_attachment_image_alt', true);
+ if (!empty($alt_text)) {
+ $args['alt_text_custom'] = esc_html($alt_text);
+ }
+
+ if (!$publish) {
+ $args['published'] = false;
+ }
+
+ $args['access_token'] = $this->getAccessToken();
+
+ $response = $this->postRequest(
+ sprintf(
+ '%s/%s/photos',
+ $this->baseUrl,
+ $this->getPageID(),
+ ),
+ $args
+ );
+
+ if (isset($response['id'])) {
+ $this->setServiceItemID($imgID, $response['id']);
+ }
+
+ return $returnResponse ? $response : $response['id'];
+
+
+ }
+
+ /**********************************************************
+ * POSTING UTILITIES
+ **********************************************************/
+ public function post(string $message):array
+ {
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up.');
+ }
+ $message = html_entity_decode(wp_strip_all_tags($message));
+ if (empty($message)) {
+ return $this->response(false, 'Message cannot be empty.');
+ }
+ $args = [
+ 'message' => $message,
+ ];
+
+ $args['access_token'] = $this->getAccessToken();
+
+ return $this->postRequest(
+ sprintf(
+ '%s/%s/feed',
+ $this->baseUrl,
+ $this->getPageID(),
+ ),
+ $args
+ );
+ }
+ /**
+ * From https://developers.facebook.com/docs/graph-api/reference/photo/#u_0_o_gP
+ *
+ * @param int $imgID
+ * @param string $caption
+ * @return array
+ */
+ public function postSingleImage(int $imgID, string $caption):array
+ {
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up yet');
+ }
+
+ $response = $this->getOrCreateImage($imgID, $caption);
+
+ return $this->response(true, 'Posted Image', $response);
+ }
+
+ public function postGalleryImages(array $imgIDs, string $caption, int $postID = 0):array
+ {
+ if (!$this->isSetUp()) {
+ return $this->response(false, 'Not set up yet');
+ }
+
+ $post = [];
+ $i = 0;
+ foreach ($imgIDs as $imgID) {
+ $id = $this->getOrCreateImage($imgID, '', false);
+ if ($id) {
+ $post['attached_media['.$i.']'] = json_encode(['media_fbid' => $id]);
+ $i++;
+ }
+ }
+
+ if (empty($post)) {
+ return $this->response(false, 'No images posted');
+ }
+
+ $post['access_token'] = $this->getAccessToken();
+
+ $caption = html_entity_decode(wp_strip_all_tags($caption));
+ if (!empty($caption)) {
+ $post['message'] = $caption;
+ }
+
+ $response = $this->postRequest(
+ sprintf(
+ '%s/%s/feed',
+ $this->baseUrl,
+ $this->getPageID(),
+ ),
+ $post
+ );
+
+ if ($postID > 0 && isset($response['id'])) {
+ $this->setServiceItemID($postID, $response['id']);
+ }
+
+ return $this->response(true, 'Successfully posted to Facebook', $response);
+ }
+
+ public function getReviews():array
+ {
+ if (!$this->isSetUp() || !$this->getPageID()) {
+ return [];
+ }
+ $ratings = $this->getRequest(
+ sprintf(
+ '%s/%s/ratings',
+ $this->baseUrl,
+ $this->getPageID(),
+ )
+ );
+ if (!isset($ratings['data'])) {
+ return [];
+ }
+ return $ratings['data'];
+ }
+
+ /**
+ * https://developers.facebook.com/docs/graph-api/reference/page/#parameters-3
+ * @param array $data allowed keys are: about, description, phone, website, emails, location {json object with street, city, state, zip, country}
+ * @return array
+ * TODO: Cover photo? (cover)
+ * TODO: Food style (food_styles)
+ */
+ public function updateBusiness(array $data):array
+ {
+ if (!$this->isSetUp() || !$this->getPageID()) {
+ return $this->response(false, 'Not set up yet');
+ }
+ $data = array_filter($data, function($item) use ($data) {
+ if (!in_array($item, ['about', 'description', 'phone', 'website', 'emails', 'location', 'hours'])) {
+ $this->logError('updateBusiness', 'Attempted to set property that is not allowed: '.$item.' with value: '.print_r($data[$item], true));
+ return false;
+ }
+ return true;
+ }, ARRAY_FILTER_USE_KEY);
+ $out = [];
+ foreach ($data as $prop => $val) {
+ $out[$prop] = match($prop) {
+ 'about', 'description' => sanitize_text_field($val),
+ 'phone' => Sanitizer::sanitizePhone($val),
+ 'emails' => $this->sanitizeEmails($val),
+ 'website' => sanitize_url($val),
+ 'location' => $this->sanitizeLocation($val),
+ 'hours' => $this->format_hours($val),
+ default => null
+ };
+ }
+ $out = array_filter($out);
+
+ if (empty($out)) {
+ return $this->response(false, 'No data to update');
+ }
+
+ $out['access_token'] = $this->getAccessToken();
+
+ $response = $this->postRequest(
+ sprintf(
+ '%s/%s',
+ $this->baseUrl,
+ $this->getPageID()
+ ),
+ $out
+ );
+
+ $success = $response['success']??false;
+ return $this->response($success, $success ? 'Successfully updated information' : 'Failed to update information');
+ }
+ protected function format_hours(array $days):array|null
+ {
+ //FB expects it to be:
+ // - first 3 characters of day of week (mon)
+ // - either 1 or 2, to allow for two set hours (ex: noon break)
+ // - open or close
+ //example: mon_1_open: 11:00, mon_1_close: 18:00
+ //To accomplish this, we will assume the hours are formatted as with the SEO schema
+ $days = array_filter($days, function ($day) {
+ return in_array($day, ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']);
+ }, ARRAY_FILTER_USE_KEY);
+
+ $out = [];
+ foreach ($days as $day => $conf) {
+ if (array_key_exists('isOpen', $conf) && $conf['isOpen'] === 1
+ && array_key_exists('opens', $conf) && !empty($conf['opens'])
+ && array_key_exists('closes', $conf) && !empty($conf['closes'])
+ ) {
+ $short = substr($day, 0, 3);
+
+ $out[$short.'_1_open'] = $conf['opens']; //Should be stored in 24hr time already
+ $out[$short.'_1_close'] = $conf['closes'];
}
}
- return true;
-
- } catch (Exception $e) {
- $this->logError('Webhook processing failed', [
- 'error' => $e->getMessage(),
- 'payload' => $payload
- ]);
- return false;
+ return empty($out) ? null : $out;
}
- }
-
- /**
- * Handle post save for Facebook sync
- */
- protected function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings): void
- {
-
- if (!$this->hasOAuthCredentials()) {
- error_log('OAuth Not set up for '.$this->service_name);
- return;
- }
- $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');
- }
-
- /**
- * Process queued operations
- */
- public function processOperation(WP_Error|array $result, object $operation, array $data): WP_Error|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;
+ protected function sanitizeLocation(array $location):array|null
+ {
+ if (array_key_exists('province', $location)) {
+ $location['state'] = $location['province'];
+ unset($location['province']);
}
- } catch (Exception $e) {
- $this->logError('Operation failed', [
- 'type' => $operation->type,
- 'error' => $e->getMessage()
- ]);
- return new WP_Error('operation_failed', $e->getMessage());
- }
- }
-
- /**
- * Create a Facebook post
- */
- private function createFacebookPost(array $data): array
- {
- $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'];
- }
-
- /**
- * Update a Facebook post
- */
- private function updateFacebookPost(array $data): 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'];
+ if (array_key_exists('postal_code', $location)) {
+ $location['zip'] = $location['postal_code'];
+ unset($location['postal_code']);
}
+ $location = array_filter($location, function ($item) use ($location) {
+ if (!in_array($item, ['street', 'city', 'state', 'zip', 'country'])) {
+ $this->logError('sanitizeLocation', 'Attempted to set property that is not allowed: '.$item.', with value: '.print_r($location[$item], true));
+ return false;
+ }
+ if (!is_string($location[$item])) {
+ $this->logError('sanitizeLocation', 'Unexpected value for '.$item.': '.print_r($location[$item], true));
+ return false;
+ }
+ return true;
+ }, ARRAY_FILTER_USE_KEY);
+ return empty($location) ? null : $location;
}
-
- 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) . '...';
+ protected function sanitizeEmails(array $emails):array|null
+ {
+ $emails = array_filter($emails, function($item) {
+ return (bool)filter_var($item, FILTER_VALIDATE_EMAIL);
+ });
+ return empty($emails) ? null : $emails;
}
-
- 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'];
- }
}
--
Gitblit v1.10.0