| | |
| | | <?php |
| | | namespace JVBase\integrations\services; |
| | | use Exception; |
| | | use JVBase\integrations\Integrations; |
| | | use JVBase\meta\Meta; |
| | | use WP_Error; |
| | | use WP_Post; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; |
| | |
| | | try { |
| | | $accounts = $this->getAccounts(true); |
| | | return !empty($accounts); |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return false; |
| | | } |
| | | } |
| | |
| | | { |
| | | // Google APIs return errors in this format: |
| | | // {"error": {"code": 401, "message": "...", "status": "UNAUTHENTICATED"}} |
| | | return isset($response['error']) && isset($response['error']['code']); |
| | | return isset($response['error']['code']); |
| | | } |
| | | |
| | | protected function getRequestHeaders(): array |
| | |
| | | return $credentials; |
| | | } |
| | | |
| | | public function handleTheSavePost(int $postID, \WP_Post $post, bool $update, array $settings):void |
| | | public function handleTheSavePost(int $postID, WP_Post $post, bool $update, array $settings):void |
| | | { |
| | | |
| | | if (!$this->hasOAuthCredentials()) { |
| | |
| | | try { |
| | | $gmb = (array_key_exists('user', $data) && $data['user'] !== 0) ? $this->getInstance((int)$data['user']) : $this; |
| | | $gmb->ensureInitialized(); |
| | | switch ($operation->type) { |
| | | case 'gmb_create_menu_item': |
| | | case 'gmb_update_menu_item': |
| | | case 'gmb_menu_update': |
| | | case 'gmb_menu_update_' . $this->userID: |
| | | return $gmb->processMenuUpdate($operation, $data); |
| | | case 'gmb_create_event': |
| | | case 'gmb_update_event': |
| | | return $gmb->syncEventPost($operation, $data); |
| | | case 'gmb_create_offer': |
| | | case 'gmb_update_offer': |
| | | return $gmb->syncOfferPost($operation, $data); |
| | | case 'gmb_create_post': |
| | | case 'gmb_update_post': |
| | | return $gmb->syncStandardPost($operation, $data); |
| | | case 'gmb_specialHours': |
| | | return $gmb->setSpecialHours($data['date'], $data['data']); |
| | | case 'gmb_business_hours': |
| | | return $gmb->updateBusinessHours($data); |
| | | case 'gmb_business_info': |
| | | return $gmb->updateBusinessInfo($data); |
| | | case 'gmb_update_attributes': |
| | | return $gmb->updateAttributes($data); |
| | | default: |
| | | return $result; |
| | | } |
| | | } catch (\Exception $e) { |
| | | return match ($operation->type) { |
| | | 'gmb_create_menu_item', 'gmb_update_menu_item', 'gmb_menu_update', 'gmb_menu_update_' . $this->userID => $gmb->processMenuUpdate($operation, $data), |
| | | 'gmb_create_event', 'gmb_update_event' => $gmb->syncEventPost($operation, $data), |
| | | 'gmb_create_offer', 'gmb_update_offer' => $gmb->syncOfferPost($operation, $data), |
| | | 'gmb_create_post', 'gmb_update_post' => $gmb->syncStandardPost($operation, $data), |
| | | 'gmb_specialHours' => $gmb->setSpecialHours($data['date'], $data['data']), |
| | | 'gmb_business_hours' => $gmb->updateBusinessHours($data), |
| | | 'gmb_business_info' => $gmb->updateBusinessInfo($data), |
| | | 'gmb_update_attributes' => $gmb->updateAttributes($data), |
| | | default => $result, |
| | | }; |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Operation processing error: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | 'result' => (is_array($result)) ? array_merge($result, ['menu'=>$menu_data]) : ['menu'=>$menu_data], |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('GMB Menu Update Error: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | } |
| | | } |
| | | } |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error finding location resource name: ' . $e->getMessage()); |
| | | } |
| | | |
| | |
| | | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | if (empty($patch_data)) { |
| | | throw new \Exception('No valid fields to update'); |
| | | throw new Exception('No valid fields to update'); |
| | | } |
| | | |
| | | // Create updateMask from the fields being updated |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateBusinessInfo' |
| | | ]); |
| | |
| | | * Update business attributes (amenities, services, etc.) |
| | | */ |
| | | /** |
| | | * @param string $location_name |
| | | * @param array $attributes |
| | | * @return array As expected by OperationQueue.php processOperation |
| | | */ |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateAttributes' |
| | | ]); |
| | |
| | | try { |
| | | // Validate required fields |
| | | if (empty($post_data['summary'])) { |
| | | throw new \Exception('Post summary is required'); |
| | | throw new Exception('Post summary is required'); |
| | | } |
| | | |
| | | // Build the post object according to API docs |
| | |
| | | |
| | | return $response; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'createPost' |
| | | ]); |
| | |
| | | |
| | | return !empty($response); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updatePost' |
| | |
| | | try { |
| | | $this->makeRequest('DELETE', "/v1/accounts/{$post_name}", [], 'posts'); |
| | | return true; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'deletePost' |
| | |
| | | * |
| | | * @param array $hours Array of periods with open/close times per day |
| | | * @return array Success status, according to OperationQueue.php |
| | | * @throws Exception |
| | | * @throws Exception |
| | | */ |
| | | public function updateBusinessHours(array $hours): array |
| | | { |
| | |
| | | |
| | | if (empty($location_name)) { |
| | | error_log('[GMB] ERROR: No location selected or found'); |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | |
| | | if (empty($periods)) { |
| | | error_log('[GMB] ERROR: No valid periods found after validation'); |
| | | throw new \Exception('No valid business hours provided'); |
| | | throw new Exception('No valid business hours provided'); |
| | | } |
| | | |
| | | $update_data = [ |
| | |
| | | 'result' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateBusinessHours' |
| | | ]); |
| | |
| | | * @param string $date |
| | | * @param array{open?: float, closed?:float, isClosed?: bool, location?: string} $options |
| | | * @return array Operation Queue [ 'success' => true/false, 'result' => $results] |
| | | * @throws Exception |
| | | * @throws Exception |
| | | */ |
| | | public function setSpecialHours(string $date, array $options = []):array |
| | | { |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | } |
| | | } |
| | | |
| | | if (!empty($options['close'])) { |
| | | $close_time = $this->formatTime($options['close']); |
| | | if (!empty($options['closed'])) { |
| | | $close_time = $this->formatTime($options['closed']); |
| | | if ($close_time) { |
| | | $special_hour_period['closeTime'] = $close_time; |
| | | } |
| | |
| | | 'results' => $response |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'setSpecialHours' |
| | | ]); |
| | |
| | | |
| | | return $response !== null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'clearSpecialHours' |
| | |
| | | ]; |
| | | |
| | | if (!in_array($category, $valid_categories)) { |
| | | throw new \Exception('Invalid photo category: ' . $category); |
| | | throw new Exception('Invalid photo category: ' . $category); |
| | | } |
| | | $mediaKey = $this->getImageMediaKey($imgID); |
| | | if ($mediaKey) { |
| | |
| | | return $mediaKey; |
| | | } |
| | | return null; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'uploadPhotoFromUrl' |
| | | ]); |
| | |
| | | * Get reviews for the current location |
| | | * @param int $page_size Number of reviews to fetch (max 50) |
| | | * @return array|null |
| | | * @throws Exception |
| | | * @throws Exception |
| | | */ |
| | | public function getReviews(int $page_size = 5): ?array |
| | | { |
| | | $this->ensureInitialized(); |
| | | if (!$this->location) { |
| | | throw new \Exception('No location selected'); |
| | | throw new Exception('No location selected'); |
| | | } |
| | | if (!$this->account_id) { |
| | | throw new \Exception('No account configured'); |
| | | throw new Exception('No account configured'); |
| | | } |
| | | |
| | | $location = $this->getSelectedLocationResourceName(); |
| | |
| | | |
| | | return $reviews; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'getReviews' |
| | | ]); |
| | |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get reviews view URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewsViewUrl' |
| | | ]); |
| | |
| | | |
| | | return null; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get review URL: ' . $e->getMessage(), [ |
| | | 'method' => 'getReviewUrl' |
| | | ]); |
| | |
| | | |
| | | try { |
| | | $response = $this->getRequest("/v1/{$account_name}/locations", $params,'accounts', $ttl, $force); |
| | | return isset($response['locations']) ? $response['locations'] : []; |
| | | return $response['locations'] ?? []; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error getting locations for ' . $account_name . ': ' . $e->getMessage()); |
| | | return []; |
| | | } |
| | |
| | | 'message' => 'No accounts found' |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error refreshing stored data: ' . $e->getMessage()); |
| | | return [ |
| | | 'success' => false, |
| | |
| | | ?> |
| | | </div> |
| | | <?php |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | echo '<p>Error loading business locations.</p>'; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | if (!$selected_account) { |
| | | throw new \Exception('Invalid account selected'); |
| | | throw new Exception('Invalid account selected'); |
| | | } |
| | | |
| | | // Update credentials with selected account |
| | |
| | | 'reload' => true |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to select account', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | } |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected'); |
| | | throw new Exception('No location selected'); |
| | | } |
| | | |
| | | // Verify this is a valid location for the selected account |
| | | if (empty($this->credentials['account'])) { |
| | | throw new \Exception('No account selected. Please select an account first.'); |
| | | throw new Exception('No account selected. Please select an account first.'); |
| | | } |
| | | |
| | | $locations = $this->getLocations($this->credentials['account']); |
| | |
| | | } |
| | | |
| | | if (!$valid_location) { |
| | | throw new \Exception('Invalid location selected'); |
| | | throw new Exception('Invalid location selected'); |
| | | } |
| | | |
| | | // Update credentials with selected location |
| | |
| | | 'reload' => false // No need to reload for location selection |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to select location', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | 'has_refresh_token' => !empty($this->credentials['refresh_token']) |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return [ |
| | | 'success' => false, |
| | | 'valid' => false, |
| | |
| | | public function getFoodMenus(): ?array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | $response = $this->getRequest( |
| | |
| | | public function updateFoodMenus(array $menus): bool |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | ]); |
| | | return !empty($response); |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'updateFoodMenus' |
| | | ]); |
| | |
| | | { |
| | | // Extract mediaKey from format: accounts/{accountId}/locations/{locationId}/media/{mediaKey} |
| | | $parts = explode('/media/', $mediaResourceName); |
| | | return isset($parts[1]) ? $parts[1] : ''; |
| | | return $parts[1] ?? ''; |
| | | } |
| | | |
| | | /** |
| | |
| | | public function getLocationMedia(): array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | |
| | | ); |
| | | |
| | | return $response['mediaItems'] ?? []; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to get location media: ' . $e->getMessage()); |
| | | return []; |
| | | } |
| | |
| | | 'cached' => !$force |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | return new WP_Error('failure', 'Something went wrong: '.$e->getMessage()); |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | if (empty($selected_account)) { |
| | | throw new \Exception('No account selected'); |
| | | throw new Exception('No account selected'); |
| | | } |
| | | |
| | | // Save the selected account |
| | |
| | | 'reload' => true // Trigger page refresh to show location dropdown |
| | | ]; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | $this->logError('Failed to update location', [ |
| | | 'error' => $e->getMessage(), |
| | | 'data' => $data |
| | |
| | | $this->cache->flush(); |
| | | return true; |
| | | |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | error_log('[GMB] Error clearing stored data: ' . $e->getMessage()); |
| | | return false; |
| | | } |
| | |
| | | public function getPriceLists(): ?array |
| | | { |
| | | if (!$this->credentials['location']) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | try { |
| | | $location_data = $this->getLocation($this->credentials['location']); |
| | | return $location_data['priceLists'] ?? []; |
| | | } catch (\Exception $e) { |
| | | } catch (Exception $e) { |
| | | |
| | | $this->logError($e->getMessage(), [ |
| | | 'method' => 'getPriceLists' |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | $post_data = [ |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | return $this->updateBusinessInfo($location_name, $updates); |
| | |
| | | $location_name = $this->getSelectedLocationResourceName(); |
| | | |
| | | if (empty($location_name)) { |
| | | throw new \Exception('No location selected. Please select a location first.'); |
| | | throw new Exception('No location selected. Please select a location first.'); |
| | | } |
| | | |
| | | return $this->clearSpecialHours($location_name); |
| | |
| | | return [$gmb_menu]; // GMB expects an array of menus |
| | | } |
| | | |
| | | protected function buildMenuItem(\WP_Post $item, array $defaults):array |
| | | protected function buildMenuItem(WP_Post $item, array $defaults):array |
| | | { |
| | | $meta = Meta::forPost($item->ID); |
| | | $fields = $this->mappedMenuFields($item->post_type); |