From ba1e1ccf869b818f7a7a897264dfea05563a7796 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 07 Jun 2026 20:10:20 +0000
Subject: [PATCH] =Major overhaul of Integrations. Playing around with adding fields to post types through Registrar from an integrations' class file.
---
inc/integrations/GoogleMyBusiness.php | 290 ++++++++++++++++++++++++++++++++++++++-------------------
1 files changed, 191 insertions(+), 99 deletions(-)
diff --git a/inc/integrations/GoogleMyBusiness.php b/inc/integrations/GoogleMyBusiness.php
index 37a6821..5893851 100644
--- a/inc/integrations/GoogleMyBusiness.php
+++ b/inc/integrations/GoogleMyBusiness.php
@@ -1,8 +1,7 @@
<?php
namespace JVBase\integrations;
-use JVBase\meta\MetaManager;
-use JVBase\managers\CacheManager;
+use JVBase\meta\Meta;
use WP_Error;
if (!defined('ABSPATH')) {
exit;
@@ -10,9 +9,17 @@
class GoogleMyBusiness extends Integrations
{
- private string $access_token;
+ protected array $allowedContent = [
+ 'menu_item',
+ 'post',
+ 'event',
+ ];
+ private ?string $access_token = null;
protected string $readMask = 'name,title,storefrontAddress,metadata,openInfo,storeCode,categories,phoneNumbers,labels,specialHours';
private ?string $location = null;
+ private ?string $refresh_token = null;
+ private ?string $client_id = null;
+ private ?string $client_secret = null;
private ?string $account_id = null;
public function __construct(?int $userID = null)
@@ -35,6 +42,7 @@
];
$this->apiEndpoints = [
+ '/accounts/[^/]+/locations/[^/]+/reviews',
'/accounts/[^/]+/locations/[^/]+/foodMenus',
'/v4/accounts/[^/]+/locations/[^/]+/media',
'/v4/accounts/[^/]+/locations/[^/]+/localPosts',
@@ -81,18 +89,18 @@
'label' => 'OAuth Client Secret',
'required' => true,
],
- 'access_token' => [
- 'type' => 'text',
- 'subtype' => 'password',
- 'label' => 'Access Token',
- 'hint' => 'Generated automagically after OAuth authorization.'
- ],
- 'refresh_token' => [
- 'type' => 'text',
- 'subtype' => 'password',
- 'label' => 'Refresh Token',
- 'hint' => 'Generated automagically after OAuth authorization.'
- ]
+// 'access_token' => [
+// 'type' => 'text',
+// 'subtype' => 'password',
+// 'label' => 'Access Token',
+// 'hint' => 'Generated automagically after OAuth authorization.'
+// ],
+// 'refresh_token' => [
+// 'type' => 'text',
+// 'subtype' => 'password',
+// 'label' => 'Refresh Token',
+// 'hint' => 'Generated automagically after OAuth authorization.'
+// ]
];
$this->advanced = [
@@ -124,6 +132,10 @@
'check_oauth_status' => 'Check OAuth Status'
]
);
+
+ if (JVB_TESTING) {
+ $this->cache->flush();
+ }
}
protected function initialize(): void
@@ -131,12 +143,12 @@
if (empty($this->credentials)) {
$this->loadCredentials();
}
- $this->access_token = $this->credentials['access_token'] ?? '';
- $this->refresh_token = $this->credentials['refresh_token'] ?? '';
- $this->client_id = $this->credentials['client_id'] ?? '';
- $this->client_secret = $this->credentials['client_secret'] ?? '';
- $this->location = $this->credentials['location'] ?? null;
- $this->account_id = $this->credentials['account'] ?? null;
+ $this->access_token = (array_key_exists('access_token', $this->credentials)) ? $this->credentials['access_token'] : null;
+ $this->refresh_token = (array_key_exists('refresh_token', $this->credentials)) ? $this->credentials['refresh_token'] : null;
+ $this->client_id = (array_key_exists('client_id', $this->credentials)) ? $this->credentials['client_id'] : null;
+ $this->client_secret = (array_key_exists('client_secret', $this->credentials)) ? $this->credentials['client_secret'] : null;
+ $this->location = (array_key_exists('location', $this->credentials)) ? $this->credentials['location'] : null;
+ $this->account_id = (array_key_exists('account', $this->credentials)) ? $this->credentials['account'] : null;
if ($this->account_id) {
$this->apiEndpoints[] = "/v1/{$this->account_id}/locations";
@@ -157,6 +169,16 @@
}
}
+ /**
+ * Check if response contains an error - Google-specific
+ */
+ protected function isErrorResponse(array $response): bool
+ {
+ // Google APIs return errors in this format:
+ // {"error": {"code": 401, "message": "...", "status": "UNAUTHENTICATED"}}
+ return isset($response['error']) && isset($response['error']['code']);
+ }
+
protected function getRequestHeaders(): array
{
return [
@@ -187,7 +209,6 @@
$type = $settings['content_type']??'post'; //can be 'post', 'offer', 'event', 'hours', 'info',
$initial = $settings['initial']?? false;
$syncOnUpdate = $settings['update']??false;
- error_log('Handling GMB Save Post with settings: '.print_r($settings, true));
if ($update && !$syncOnUpdate) {
return;
}
@@ -195,7 +216,6 @@
return;
}
- error_log('Continuing on...');
$options = $data = [];
switch ($type) {
case 'menu_item':
@@ -207,8 +227,7 @@
}
$data['post_id'] = $postID;
$operation = ($update) ? 'update_' : 'create_';
- error_log('[GMB]Queuing sync to service:'.print_r($data, true));
- error_log('Options: '.print_r($options, true));
+
$this->queueOperation(
$operation.$type,
$data,
@@ -268,7 +287,7 @@
}
$postID = $data['post_id'];
- $meta = new MetaManager($postID, 'post');
+ $meta = Meta::forPost($postID);
$fields = [
'start_date',
'end_date',
@@ -304,7 +323,7 @@
$result = $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
} else {
$result = $this->createPost($data);
- $meta->updateValue("_{$this->service_name}_item_id", $result['name']);
+ $meta->set("_{$this->service_name}_item_id", $result['name']);
}
return [
@@ -325,7 +344,7 @@
}
$postID = $data['post_id'];
- $meta = new MetaManager($postID, 'post');
+ $meta = Meta::forPost($postID);
$fields = [
'post_excerpt',
'post_title',
@@ -357,7 +376,7 @@
$result = $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
} else {
$result = $this->createPost($data);
- $meta->updateValue("_{$this->service_name}_item_id", $result['name']);
+ $meta->set("_{$this->service_name}_item_id", $result['name']);
}
return [
@@ -379,7 +398,7 @@
}
$postID = $data['post_id'];
- $meta = new MetaManager($postID, 'post');
+ $meta = Meta::forPost($postID);
$fields = [
'post_excerpt',
'post_title',
@@ -408,7 +427,7 @@
$result = $this->updatePost($fields["_{$this->service_name}_item_id"], $data);
} else {
$result = $this->createPost($data);
- $meta->updateValue("_{$this->service_name}_item_id", $result['name']);
+ $meta->set("_{$this->service_name}_item_id", $result['name']);
}
return [
@@ -457,8 +476,6 @@
// Build the complete menu structure
$menu_data = $gmb->collectMenu($menu_items);
- error_log('Menu Data: '.print_r($menu_data, true));
-
// Send to Google My Business API
$result = $this->updateFoodMenus($menu_data);
return [
@@ -496,7 +513,6 @@
$locations = $this->getLocations($account['name']);
foreach ($locations as $location) {
- error_log('Fetched Location: '.print_r($location, true));
if ($location['storeCode'] === $this->credentials['location']) {
// Auto-migrate: update stored location to use full resource name
$this->setSelectedLocation($location['name']);
@@ -683,7 +699,6 @@
// Filter to only allowed fields
$patch_data = array_intersect_key($updates,$allowed_fields);
- error_log('Updates: '.print_r($updates, true));
$location_name = $this->getSelectedLocationResourceName();
if (empty($patch_data)) {
@@ -1028,7 +1043,6 @@
if(!$this->isSetUp()) {
return [];
}
- error_log('[GMB] updateBusinessHours called with hours: ' . print_r($hours, true));
$location_name = $this->credentials['location'];
if (empty($location_name)) {
@@ -1051,11 +1065,7 @@
]
];
- error_log('[GMB] Complete update data: ' . print_r($update_data, true));
-
$endpoint = "/v1/{$location_name}?updateMask=regularHours";
- error_log('[GMB] API endpoint: ' . $endpoint);
- error_log('[GMB] Using API base: ' . 'base');
// Make the API request
$response = $this->makeRequest(
@@ -1065,18 +1075,15 @@
'base'
);
- error_log('[GMB] API response: ' . print_r($response, true));
-
$success = $response !== null;
- error_log('[GMB] updateBusinessHours result: ' . ($success ? 'SUCCESS' : 'FAILED'));
// Additional validation - check if the response contains the updated hours
if ($success && $response) {
if (isset($response['regularHours'])) {
- error_log('[GMB] SUCCESS: Updated hours confirmed in response: ' . print_r($response['regularHours'], true));
+// error_log('[GMB] SUCCESS: Updated hours confirmed in response: ' . print_r($response['regularHours'], true));
} else {
- error_log('[GMB] WARNING: No regularHours in response, but API call succeeded');
- error_log('[GMB] Full response keys: ' . implode(', ', array_keys($response)));
+// error_log('[GMB] WARNING: No regularHours in response, but API call succeeded');
+// error_log('[GMB] Full response keys: ' . implode(', ', array_keys($response)));
}
}
@@ -1086,9 +1093,6 @@
];
} catch (\Exception $e) {
- error_log('[GMB] Exception in updateBusinessHours: ' . $e->getMessage());
- error_log('[GMB] Exception trace: ' . $e->getTraceAsString());
-
$this->logError($e->getMessage(), [
'method' => 'updateBusinessHours'
]);
@@ -1101,8 +1105,6 @@
private function validateAndFormatHours(array $hours): array
{
- error_log('[GMB] Validating hours format...');
-
$valid_days = ['MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', 'SUNDAY'];
$periods = [];
@@ -1132,8 +1134,6 @@
continue;
}
- error_log('[GMB] Processing ' . $formatted_day . ' - times data: ' . print_r($times, true));
-
// Check if day is open with flexible comparison
$is_open = false;
if (isset($times['open'])) {
@@ -1165,10 +1165,7 @@
];
$periods[] = $period;
- error_log('[GMB] Valid period for ' . $formatted_day . ': ' . print_r($period, true));
}
-
- error_log('[GMB] Total valid periods: ' . count($periods));
return $periods;
}
@@ -1247,8 +1244,6 @@
]
];
- error_log('[GMB] Updating special hours with corrected structure: ' . json_encode($update_data, JSON_PRETTY_PRINT));
-
// Try the PATCH request
$response = $this->makeRequest(
'PATCH',
@@ -1263,8 +1258,6 @@
];
} catch (\Exception $e) {
- error_log('[GMB] setSpecialHours Exception: ' . $e->getMessage());
-
$this->logError($e->getMessage(), [
'method' => 'setSpecialHours'
]);
@@ -1416,9 +1409,6 @@
$ttl = 7 * 24 * 60 * 60; // week in seconds
$response = $this->getRequest('/v1/accounts', [], 'accounts', $ttl, $force)??[];
- // Log the raw response for debugging
- error_log('[GMB] Raw accounts response: ' . print_r($response, true));
-
if (isset($response['accounts']) && is_array($response['accounts'])) {
return $response['accounts'];
}
@@ -1431,6 +1421,127 @@
}
/**
+ * Get reviews for the current location
+ * @param int $page_size Number of reviews to fetch (max 50)
+ * @return array|null
+ */
+ public function getReviews(int $page_size = 5): ?array
+ {
+ $this->ensureInitialized();
+ if (!$this->location) {
+ throw new \Exception('No location selected');
+ }
+ if (!$this->account_id) {
+ throw new \Exception('No account configured');
+ }
+
+ $location = $this->getSelectedLocationResourceName();
+ $account = $this->account_id;
+
+
+ // Check cache first (weekly refresh = 604800 seconds)
+ $cache_key = ['reviews', $location, $page_size];
+ $cached = $this->cache->get($cache_key);
+ if ($cached !== false) {
+ return $cached;
+ }
+
+ try {
+ // Reviews endpoint from My Business Account Management API
+ $response = $this->getRequest(
+ "/{$account}/{$location}/reviews",
+ [
+ 'orderBy' => 'updateTime desc'
+ ],
+ 'v4'
+ );
+
+ $reviews = $response ?? [];
+
+ // Cache for 1 week (604800 seconds)
+ $this->cache->set($cache_key, $reviews, WEEK_IN_SECONDS);
+
+ return $reviews;
+
+ } catch (\Exception $e) {
+ $this->logError($e->getMessage(), [
+ 'method' => 'getReviews'
+ ]);
+ return null;
+ }
+ }
+
+ /**
+ * Get the URL to view all Google reviews for the current location
+ * @return string|null The reviews viewing URL or null if not available
+ */
+ public function getReviewsViewUrl(): ?string
+ {
+ $this->ensureInitialized();
+ try {
+ $location = $this->getLocation();
+
+ if (empty($location)) {
+ return null;
+ }
+
+ // Prefer maps URL as it shows all reviews directly
+ if (!empty($location['metadata']['mapsUrl'])) {
+ return $location['metadata']['mapsUrl'];
+ }
+
+ // Fallback: construct from Place ID
+ if (!empty($location['metadata']['placeId'])) {
+ return 'https://search.google.com/local/reviews?placeid=' .
+ urlencode($location['metadata']['placeId']);
+ }
+
+ return null;
+
+ } catch (\Exception $e) {
+ $this->logError('Failed to get reviews view URL: ' . $e->getMessage(), [
+ 'method' => 'getReviewsViewUrl'
+ ]);
+ return null;
+ }
+ }
+
+ /**
+ * Get the URL to leave a review for the current location
+ * @return string|null The review URL or null if not available
+ */
+ public function getReviewUrl(): ?string
+ {
+ $this->ensureInitialized();
+ try {
+ $location = $this->getLocation();
+
+ if (empty($location)) {
+ return null;
+ }
+
+ // Try to use Place ID for write review
+ if (!empty($location['metadata']['placeId'])) {
+ return 'https://search.google.com/local/writereview?placeid=' .
+ urlencode($location['metadata']['placeId']);
+ }
+
+ // Fallback to maps URL
+ if (!empty($location['metadata']['mapsUrl'])) {
+ return $location['metadata']['mapsUrl'] . '/reviews';
+ }
+
+ return null;
+
+ } catch (\Exception $e) {
+ $this->logError('Failed to get review URL: ' . $e->getMessage(), [
+ 'method' => 'getReviewUrl'
+ ]);
+ return null;
+ }
+ }
+
+ /**
* Get locations for an account (with persistent storage)
* Allowed Fields: https://developers.google.com/my-business/content/location-data#list_of_all_supported_filter_fields
*/
@@ -1445,7 +1556,6 @@
if (empty($account_name) || !str_starts_with($account_name, 'accounts/')) {
return [];
}
- error_log('[GMB] getLocations() called for: ' . $account_name . ' (force: ' . ($force ? 'true' : 'false') . ')');
$params = ['readMask' => $this->readMask];
$ttl = 7 * 24 * 60 * 60; // Week in seconds
@@ -1475,8 +1585,6 @@
*/
public function refreshStoredData(): array
{
- error_log('[GMB] Manually refreshing accounts and locations data');
-
try {
// Fetch fresh accounts data from API
$accounts = $this->getAccounts(true);
@@ -1536,7 +1644,6 @@
];
$endpoint = "/v1/{$location_name}?".http_build_query($params);
- error_log('[GMB] Fetching location: ' . $location_name);
$location = $this->getRequest($endpoint, [], 'base', 'moderate', $force);
return $location??null;
@@ -1636,9 +1743,7 @@
$text = preg_replace('/\s+/', ' ', $text);
// Trim
- $text = trim($text);
-
- return $text;
+ return trim($text);
}
/**
* Format date for GMB API
@@ -1679,13 +1784,10 @@
// Validate hour and minute ranges
if ($hour >= 0 && $hour <= 23 && $minute >= 0 && $minute <= 59) {
- $result = [
+ return [
'hours' => $hour,
'minutes' => $minute
];
-
- error_log('[GMB] Converted time "' . $time . '" to Google format: ' . print_r($result, true));
- return $result;
}
}
@@ -1975,9 +2077,7 @@
[],
'posts'
);
- $result = $response['foodMenus'] ?? [];
-
- return $result;
+ return $response['foodMenus'] ?? [];
}
/**
@@ -2099,8 +2199,6 @@
public function handleGetAllLocations():WP_Error|array
{
try {
- error_log('[GMB] AJAX getAllLocations called');
-
// Check if we should force refresh
$force = isset($_POST['force']) && $_POST['force'] === 'true';
@@ -2114,7 +2212,6 @@
$all_locations = array_merge($all_locations, $locations);
}
- error_log('[GMB] AJAX returning ' . count($all_locations) . ' locations');
return [
'success' => true,
'locations' => $all_locations,
@@ -2122,7 +2219,6 @@
];
} catch (\Exception $e) {
- error_log('[GMB] AJAX getAllLocations exception: ' . $e->getMessage());
return new WP_Error('failure', 'Something went wrong: '.$e->getMessage());
}
}
@@ -2136,15 +2232,12 @@
// Handle both AJAX and REST API calls
$selected_account = null;
- error_log('handle Update Location: '.print_r($data, true));
if (is_array($data)) {
$selected_account = $data['account'] ?? $data['location'] ?? null;
} elseif (isset($_POST['account'])) {
$selected_account = sanitize_text_field($_POST['account']);
}
- error_log('[GMB] updateLocation received: ' . print_r($selected_account, true));
-
if (empty($selected_account)) {
throw new \Exception('No account selected');
}
@@ -2187,9 +2280,7 @@
{
try {
// Use the static method to clear the entire cache group
- CacheManager::invalidateGroup('integrations_'.$this->cacheName);
-
- error_log('[GMB] Cleared all stored data for cache group: ' . $this->cacheName);
+ $this->cache->flush();
return true;
} catch (\Exception $e) {
@@ -2261,12 +2352,11 @@
'dailyRange.endDate.day' => date('j', strtotime($end_date))
];
- $response = $this->getRequest(
- "/v1/{$location_name}:fetchMultiDailyMetricsTimeSeries?" . http_build_query($params),
- $params,
- 'performance'
- );
- return $response;
+ return $this->getRequest(
+ "/v1/{$location_name}:fetchMultiDailyMetricsTimeSeries?" . http_build_query($params),
+ $params,
+ 'performance'
+ );
}
/**
@@ -2385,7 +2475,7 @@
protected function collectMenu(array $menu_items): array
{
- $defaultMeta = new MetaManager($this->userID, 'integrations');
+ $defaultMeta = Meta::forOptions($this->userID.'_integrations');
$defaults = ['menu_name', 'menu_description', 'default_section', 'cuisines', 'source_url', 'language', 'default_currency'];
$defaults = $defaultMeta->getAll($defaults);
@@ -2446,7 +2536,7 @@
protected function buildMenuItem(\WP_Post $item, array $defaults):array
{
- $meta = new MetaManager($item->ID, 'post');
+ $meta = Meta::forPost($item->ID);
$fields = $this->mappedMenuFields($item->post_type);
$values = $meta->getAll(array_values($fields));
@@ -2575,8 +2665,8 @@
protected function getMenuSectionsOrder(array $sections_map):array
{
- $optionsMeta = new MetaManager(null, 'options');
- $sectionOrder = $optionsMeta->getValue('menu_section_order');
+ $optionsMeta = Meta::forOptions('options');
+ $sectionOrder = $optionsMeta->get('menu_section_order');
// Build final GMB menu structure
$ordered = [];
@@ -2612,8 +2702,8 @@
// Collect cuisines from individual items if specified
foreach ($menu_items as $item) {
- $meta = new MetaManager($item->ID, 'post');
- $item_cuisines = $meta->getValue('cuisines');
+ $meta = Meta::forPost($item->ID);
+ $item_cuisines = $meta->get('cuisines');
if (!empty($item_cuisines)) {
$item_cuisines = is_array($item_cuisines) ?
@@ -2884,4 +2974,6 @@
]
];
}
+
+
}
--
Gitblit v1.10.0