PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.8.4
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.8.4
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / classes / class-auth-zoho.php
commercebird / includes / classes Last commit date
apis 2 months ago purchase-orders 3 months ago quotes 2 months ago zoho-crm 7 months ago zoho-inventory 2 months ago class-api-handler-zoho.php 7 months ago class-auth-zoho.php 3 months ago class-common.php 2 months ago class-plugin.php 2 months ago class-wc-api.php 5 months ago index.php 1 year ago
class-auth-zoho.php
269 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6 /**
7 * Handles Zoho authentication for CommerceBird.
8 *
9 * Provides methods for obtaining and refreshing Zoho access tokens.
10 *
11 * @since 1.0.0
12 */
13 class CMBIRD_Auth_Zoho {
14
15 /**
16 * Configuration options for Zoho authentication.
17 *
18 * @var array|array[]
19 */
20 private array $config;
21
22 /**
23 * Given a raw domain portion from settings, return the normalized value.
24 *
25 * This ensures the rest of the code only needs to deal with lowercase,
26 * trimmed domain strings. It also protects against unexpected data from
27 * wp_options (users typing spaces or upper‑case characters).
28 *
29 * @param string $domain Raw option value.
30 * @return string Normalized domain (lowercase, trimmed).
31 */
32 public static function normalize_domain( string $domain ): string {
33 return strtolower( trim( $domain ) );
34 }
35
36 /**
37 * Accounts base URL for a given Zoho domain.
38 *
39 * Handles the special "ca" (Canada) case which must use the
40 * zohocloud.ca host instead of the normal zoho.{domain} pattern.
41 *
42 * @param string $domain Domain portion (any case / whitespace allowed).
43 * @return string Base accounts URL without trailing slash.
44 */
45 public static function get_accounts_base_url( string $domain ): string {
46 $norm = self::normalize_domain( $domain );
47 if ( 'ca' === $norm ) {
48 return 'https://accounts.zohocloud.ca';
49 }
50 return 'https://accounts.zoho.' . $norm;
51 }
52
53 /**
54 * Full token endpoint for a given domain.
55 *
56 * @param string $domain Domain portion (any case / whitespace allowed).
57 * @return string OAuth2 token URL.
58 */
59 public static function get_auth_url_for_domain( string $domain ): string {
60 return self::get_accounts_base_url( $domain ) . '/oauth/v2/token';
61 }
62
63 /**
64 * Full authorization redirect endpoint for a given domain.
65 *
66 * @param string $domain Domain portion (any case / whitespace allowed).
67 * @return string OAuth2 auth URL.
68 */
69 public static function get_auth_redirect_url_for_domain( string $domain ): string {
70 return self::get_accounts_base_url( $domain ) . '/oauth/v2/auth';
71 }
72
73 /**
74 * Constructor for the CMBIRD_Auth_Zoho class.
75 *
76 * Sets up the object properties from the WordPress options table.
77 *
78 * @since 1.0.0
79 */
80 public function __construct() {
81 // domain options are normalised via helper so the logic lives in one.
82 // place and tests can cover it directly.
83 $zi_domain = self::normalize_domain( (string) get_option( 'cmbird_zoho_inventory_domain', '' ) );
84 $zcrm_domain = self::normalize_domain( (string) get_option( 'cmbird_zoho_crm_domain', '' ) );
85
86 $config = array(
87
88 'ServiceZI' => array(
89 'OID' => get_option( 'cmbird_zoho_inventory_oid' ),
90 'CLIENTSECRET' => get_option( 'cmbird_zoho_inventory_cs' ),
91 'CLIENTID' => get_option( 'cmbird_zoho_inventory_cid' ),
92 'REDIRECTURL' => get_option( 'cmbird_authorization_redirect_uri' ),
93 'APIURL' => get_option( 'cmbird_zoho_inventory_url' ),
94 'DOMAINNAME' => $zi_domain,
95 'SCOPE' => 'ZohoInventory.FullAccess.all',
96 // 'STATE' => wp_create_nonce('redirect_url'),.
97 'AUTHURL' => self::get_auth_url_for_domain( $zi_domain ),
98
99 ),
100 'ServiceZCRM' => array(
101 'CLIENTSECRET' => get_option( 'cmbird_zoho_crm_cs' ),
102 'CLIENTID' => get_option( 'cmbird_zoho_crm_cid' ),
103 'REDIRECTURL' => get_option( 'cmbird_authorization_redirect_uri' ),
104 'APIURL' => get_option( 'cmbird_zoho_crm_url' ),
105 'DOMAINNAME' => $zcrm_domain,
106 'SCOPE' => 'ZohoCRM.users.ALL,ZohoCRM.bulk.ALL,ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.org.ALL,profile.userphoto.READ,ZohoFiles.files.CREATE',
107 // 'STATE' => wp_create_nonce('redirect_url'),.
108 'AUTHURL' => self::get_auth_url_for_domain( $zcrm_domain ),
109 ),
110
111 );
112
113 $this->config = $config;
114 }
115
116 /**
117 * Expose configuration for testing or debugging.
118 *
119 * @return array The internal Zoho auth configuration.
120 */
121 public function get_config(): array {
122 return $this->config;
123 }
124
125 /**
126 * Retrieves the access token for the given Zoho application.
127 *
128 * @param string $code The authorization code from Zoho.
129 * @param string $app_name The name of the Zoho application (e.g. 'zoho_inventory', 'zoho_crm').
130 *
131 * @return array|\WP_Error The decoded JSON response from the Zoho API, or a WP_Error object on failure.
132 */
133 public function get_zoho_access_token( $code, $app_name ) {
134
135 $headers = array( 'Content-Type: application/x-www-form-urlencoded' );
136 switch ( $app_name ) {
137 case 'zoho_inventory':
138 $params = array(
139 'code' => $code,
140 'client_id' => $this->config['ServiceZI']['CLIENTID'],
141 'client_secret' => $this->config['ServiceZI']['CLIENTSECRET'],
142 'redirect_uri' => $this->config['ServiceZI']['REDIRECTURL'],
143 'scope' => $this->config['ServiceZI']['SCOPE'],
144 'grant_type' => 'authorization_code',
145 );
146 $url = $this->config['ServiceZI']['AUTHURL'];
147 break;
148 default:
149 $params = array(
150 'code' => $code,
151 'client_id' => $this->config['ServiceZCRM']['CLIENTID'],
152 'client_secret' => $this->config['ServiceZCRM']['CLIENTSECRET'],
153 'redirect_uri' => $this->config['ServiceZCRM']['REDIRECTURL'],
154 'scope' => $this->config['ServiceZCRM']['SCOPE'],
155 'grant_type' => 'authorization_code',
156 );
157 $url = $this->config['ServiceZCRM']['AUTHURL'];
158 break;
159 }
160
161 // Set up the request arguments.
162 $args = array(
163 'headers' => $headers,
164 'body' => $params,
165 'method' => 'POST',
166 );
167
168 // Debug: Log the domain and auth URL being used.
169 if ( function_exists( 'wc_get_logger' ) ) {
170 $logger = wc_get_logger();
171 $domain_label = 'zoho_inventory' === $app_name ? $this->config['ServiceZI']['DOMAINNAME'] : $this->config['ServiceZCRM']['DOMAINNAME'];
172 $logger->debug( 'CommerceBird Auth Debug - App: ' . $app_name . ', Domain: ' . $domain_label . ', Auth URL: ' . $url, array( 'source' => 'commercebird' ) );
173 }
174
175 // Make the request using wp_remote_post().
176 $response = wp_remote_post( $url, $args );
177
178 if ( is_wp_error( $response ) ) {
179 if ( function_exists( 'wc_get_logger' ) ) {
180 $logger = wc_get_logger();
181 $logger->error( 'CommerceBird Access Token Error - App: ' . $app_name . ', Error: ' . $response->get_error_message(), array( 'source' => 'commercebird' ) );
182 }
183
184 return $response;
185 }
186
187 $body = wp_remote_retrieve_body( $response );
188 $status_code = wp_remote_retrieve_response_code( $response );
189 $decoded_body = json_decode( $body, true );
190
191 if ( 200 === $status_code ) {
192 if ( is_array( $decoded_body ) ) {
193 return $decoded_body;
194 }
195
196 return new \WP_Error( 'zoho_access_token_error', 'Invalid Zoho token response received.' );
197 }
198
199 $error_message = 'Unknown error.';
200 if ( is_array( $decoded_body ) ) {
201 $error_message = $decoded_body['error_description'] ?? $decoded_body['message'] ?? $decoded_body['error'] ?? $error_message;
202 }
203
204 if ( function_exists( 'wc_get_logger' ) ) {
205 $logger = wc_get_logger();
206 $logger->error( 'CommerceBird Access Token Error - App: ' . $app_name . ', Error: ' . $error_message, array( 'source' => 'commercebird' ) );
207 }
208
209 return new \WP_Error( 'zoho_access_token_error', $error_message, array( 'status' => $status_code ) );
210 }
211
212
213 /**
214 * Retrieves a new access token for the given app using the refresh token.
215 *
216 * @param string $refresh_token The refresh token to use for the request.
217 * @param string $app_name The name of the app to retrieve the access token for (e.g. 'zoho_inventory', 'zoho_crm').
218 *
219 * @return object|\WP_Error The JSON response from the request, or an error message if there was an error.
220 */
221 public function get_zoho_refresh_token( $refresh_token, $app_name ) {
222 $headers = array( 'Content-Type: application/x-www-form-urlencoded' );
223
224 $client_id = 'zoho_inventory' === $app_name ? $this->config['ServiceZI']['CLIENTID'] : $this->config['ServiceZCRM']['CLIENTID'];
225 $client_sec = 'zoho_inventory' === $app_name ? $this->config['ServiceZI']['CLIENTSECRET'] : $this->config['ServiceZCRM']['CLIENTSECRET'];
226 $params = array(
227 'refresh_token' => $refresh_token,
228 'grant_type' => 'refresh_token',
229 'client_id' => $client_id,
230 'client_secret' => $client_sec,
231 );
232 $url = 'zoho_inventory' === $app_name ? $this->config['ServiceZI']['AUTHURL'] : $this->config['ServiceZCRM']['AUTHURL'];
233 // Set up the request arguments.
234 $args = array(
235 'headers' => $headers,
236 'body' => $params,
237 'method' => 'POST',
238 );
239
240 // Debug: Log the domain and auth URL being used for refresh token.
241 if ( function_exists( 'wc_get_logger' ) ) {
242 $logger = wc_get_logger();
243 $domain_label = 'zoho_inventory' === $app_name ? $this->config['ServiceZI']['DOMAINNAME'] : $this->config['ServiceZCRM']['DOMAINNAME'];
244 $logger->debug( 'CommerceBird Refresh Token Debug - App: ' . $app_name . ', Domain: ' . $domain_label . ', Auth URL: ' . $url, array( 'source' => 'commercebird' ) );
245 }
246
247 // Make the request using wp_remote_post().
248 $response = wp_remote_post( $url, $args );
249 // Check if the request was successful.
250 if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 ) {
251 // If successful, get the body of the response.
252 $body = wp_remote_retrieve_body( $response );
253 // Decode JSON response.
254 return json_decode( $body, true );
255 } else {
256 // If there was an error, handle it.
257 $error_message = is_wp_error( $response ) ? $response->get_error_message() : 'Unknown error.';
258 if ( function_exists( 'wc_get_logger' ) ) {
259 $logger = wc_get_logger();
260 $logger->error( 'CommerceBird Refresh Token Error - App: ' . $app_name . ', Error: ' . $error_message, array( 'source' => 'commercebird' ) );
261 }
262 // return the WP Error object.
263 return new \WP_Error( 'zoho_refresh_token_error', $error_message );
264 }
265 }
266 }
267
268 $cmbird_handlefunction = new CMBIRD_Auth_Zoho();
269