PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.1
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.1
8.8.1 8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 All 537 releases
chatbot / addons / automator / includes / actions / google-sheets-actions.php

google-sheets-actions.php in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.8.1, at addons/automator/includes/actions/google-sheets-actions.php

657 lines 21.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Google Sheets Actions
4 *
5 * @package WPbot_Automator
6 */
7
8 namespace WPbot_Automator\Actions;
9
10 if ( ! defined( 'ABSPATH' ) ) {
11 exit;
12 }
13
14 /**
15 * Class Google_Sheets_Actions
16 *
17 * Integrates with the Google Sheets API v4 using a Service Account (JWT auth).
18 * No OAuth redirect flow required — users paste their Service Account JSON key directly.
19 */
20 class Google_Sheets_Actions extends Action {
21
22 /**
23 * Constructor
24 */
25 public function __construct() {
26 $this->id = 'google_sheets';
27 $this->group = __( 'Google Sheets', 'wpbot-automator' );
28 }
29
30 /**
31 * Execute the action
32 *
33 * @param array $action_data Configuration for this action from the workflow.
34 * @param array $trigger_data Data passed from the trigger.
35 * @return bool|array
36 */
37 public function execute( $action_data, $trigger_data ) {
38 $action_id = isset( $action_data['actionId'] ) ? $action_data['actionId'] : '';
39
40 if ( empty( $action_id ) ) {
41 return false;
42 }
43
44 switch ( $action_id ) {
45 case 'gs_add_row':
46 case 'add_row':
47 return $this->add_row( $action_data, $trigger_data );
48 case 'gs_update_row':
49 case 'update_row':
50 return $this->update_row( $action_data, $trigger_data );
51 case 'gs_clear_range':
52 case 'clear_range':
53 return $this->clear_range( $action_data, $trigger_data );
54 case 'gs_get_values':
55 case 'get_values':
56 return $this->get_values( $action_data, $trigger_data );
57 default:
58 return false;
59 }
60 }
61
62 // ─────────────────────────────────────────────────────────────────────────
63 // Action Handlers
64 // ─────────────────────────────────────────────────────────────────────────
65
66 /**
67 * Append a new row to a Google Sheet.
68 */
69 private function add_row( $action_data, $trigger_data ) {
70
71 $cfg = isset( $action_data['config'] ) ? $action_data['config'] : array();
72 $spreadsheet_id = trim( $this->parse_tokens( $cfg['spreadsheet_id'] ?? $cfg['sheet_id'] ?? '', $trigger_data ) );
73 $sheet_name_raw = trim( $this->parse_tokens( $cfg['sheet_name'] ?? $cfg['worksheet'] ?? '', $trigger_data ) );
74 $sheet_name = ! empty( $sheet_name_raw ) ? $sheet_name_raw : 'Sheet1';
75 $row_data_temp = $cfg['row_data'] ?? '';
76 $sa_json = trim( $cfg['service_account_json'] ?? '' );
77
78 if ( empty( $sa_json ) ) {
79 $sa_json = get_option( 'wpbot_automator_gs_sa_json', '' );
80 }
81
82 if ( empty( $spreadsheet_id ) || empty( $sa_json ) ) {
83 return array(
84 'success' => false,
85 'message' => 'Spreadsheet ID and Service Account JSON are required.',
86 );
87 }
88
89 $token = $this->get_access_token( $sa_json );
90 if ( is_wp_error( $token ) ) {
91 return array( 'success' => false, 'message' => $token->get_error_message() );
92 }
93
94 // �
95 Validate Sheet Exists First
96 $sheet_check = wp_remote_get(
97 "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}?fields=sheets.properties.title",
98 array(
99 'headers' => array(
100 'Authorization' => 'Bearer ' . $token,
101 ),
102 'timeout' => 20,
103 )
104 );
105
106 if ( is_wp_error( $sheet_check ) ) {
107 return array( 'success' => false, 'message' => $sheet_check->get_error_message() );
108 }
109
110 $sheet_body = json_decode( wp_remote_retrieve_body( $sheet_check ), true );
111
112 if ( empty( $sheet_body['sheets'] ) ) {
113 return array( 'success' => false, 'message' => 'Unable to retrieve sheets from spreadsheet.' );
114 }
115
116 $available_sheets = array_map(
117 function ( $s ) {
118 return $s['properties']['title'];
119 },
120 $sheet_body['sheets']
121 );
122
123 if ( ! in_array( $sheet_name, $available_sheets, true ) ) {
124 $create_missing = $cfg['create_missing_sheet'] ?? true; // Default to true if not set
125 if ( $create_missing ) {
126 $created = $this->create_sheet( $spreadsheet_id, $sheet_name, $sa_json );
127 if ( is_wp_error( $created ) ) {
128 return array( 'success' => false, 'message' => 'Sheet "' . esc_html( $sheet_name ) . '" not found and auto-creation failed: ' . $created->get_error_message() );
129 }
130 } else {
131 return array(
132 'success' => false,
133 'message' => 'Sheet "' . esc_html( $sheet_name ) . '" not found. Available sheets: ' . implode( ', ', $available_sheets ),
134 );
135 }
136 }
137
138 // �
139 Parse row data by splitting the template first to prevent inline commas/newlines from breaking columns
140 if ( ! empty( $row_data_temp ) ) {
141 $values_template = is_array( $row_data_temp ) ? $row_data_temp : preg_split( '/[\n,]+/', $row_data_temp );
142 $values_template = array_map( 'trim', $values_template );
143 $values = array();
144 foreach ( $values_template as $tpl ) {
145 if ( $tpl !== '' ) {
146 $values[] = $this->parse_tokens( $tpl, $trigger_data );
147 }
148 }
149 } else {
150 $values = $this->extract_fields_from_trigger( $trigger_data );
151 }
152
153 if ( empty( $values ) ) {
154 return array(
155 'success' => false,
156 'message' => 'No row data provided.',
157 );
158 }
159
160 // �
161 Properly quote sheet name if it contains spaces or special characters
162 $sheet_name = $this->quote_sheet_name( $sheet_name );
163
164 // �
165 Use safe append range
166 $range = $sheet_name . '!A:Z';
167
168 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}/values/" . rawurlencode( $range ) . ":append?valueInputOption=USER_ENTERED&insertDataOption=INSERT_ROWS";
169
170 $response = wp_remote_post(
171 $url,
172 array(
173 'headers' => array(
174 'Authorization' => 'Bearer ' . $token,
175 'Content-Type' => 'application/json',
176 ),
177 'body' => wp_json_encode(
178 array(
179 'values' => array( $values ),
180 )
181 ),
182 'timeout' => 20,
183 )
184 );
185
186 $result = $this->handle_api_response( $response, 'Add Row', $spreadsheet_id, $sa_json );
187
188 if ( $result['success'] && ! empty( $cfg['service_account_json'] ) ) {
189 update_option( 'wpbot_automator_gs_sa_json', $cfg['service_account_json'] );
190 }
191
192 return $result;
193 }
194
195 /**
196 * Update a specific row in a Google Sheet.
197 */
198 private function update_row( $action_data, $trigger_data ) {
199 $cfg = isset( $action_data['config'] ) ? $action_data['config'] : array();
200 $spreadsheet_id = trim( $this->parse_tokens( $cfg['spreadsheet_id'] ?? $cfg['sheet_id'] ?? '', $trigger_data ) );
201 $sheet_name_raw = trim( $this->parse_tokens( $cfg['sheet_name'] ?? $cfg['worksheet'] ?? '', $trigger_data ) );
202 $sheet_name = ! empty( $sheet_name_raw ) ? $sheet_name_raw : 'Sheet1';
203 $row_number = absint( $this->parse_tokens( $cfg['row_number'] ?? '', $trigger_data ) );
204 $row_data_temp = $cfg['row_data'] ?? '';
205 $sa_json = trim( $cfg['service_account_json'] ?? '' );
206
207 if ( empty( $sa_json ) ) {
208 $sa_json = get_option( 'wpbot_automator_gs_sa_json', '' );
209 }
210
211 if ( empty( $spreadsheet_id ) || empty( $sa_json ) || empty( $row_data_temp ) || empty( $row_number ) ) {
212 return array(
213 'success' => false,
214 'message' => 'Google Sheets Update Row: spreadsheet_id, service_account_json, row_data, and row_number are required.',
215 );
216 }
217
218 $token = $this->get_access_token( $sa_json );
219 if ( is_wp_error( $token ) ) {
220 return array( 'success' => false, 'message' => $token->get_error_message() );
221 }
222
223 $values_template = is_array( $row_data_temp ) ? $row_data_temp : preg_split( '/[\n,]+/', $row_data_temp );
224 $values_template = array_map( 'trim', $values_template );
225 $values = array();
226 foreach ( $values_template as $tpl ) {
227 if ( $tpl !== '' ) {
228 $values[] = $this->parse_tokens( $tpl, $trigger_data );
229 }
230 }
231
232 $range = rawurlencode( $this->quote_sheet_name( "{$sheet_name}!A{$row_number}" ) );
233 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}/values/{$range}?valueInputOption=USER_ENTERED";
234
235 $response = wp_remote_request(
236 $url,
237 array(
238 'method' => 'PUT',
239 'headers' => array(
240 'Authorization' => 'Bearer ' . $token,
241 'Content-Type' => 'application/json',
242 ),
243 'body' => wp_json_encode(
244 array(
245 'range' => $this->quote_sheet_name( "{$sheet_name}!A{$row_number}" ),
246 'values' => array( $values ),
247 )
248 ),
249 'timeout' => 20,
250 )
251 );
252
253 return $this->handle_api_response( $response, 'Update Row', $spreadsheet_id, $sa_json );
254 }
255
256 /**
257 * Clear a range in a Google Sheet.
258 */
259 private function clear_range( $action_data, $trigger_data ) {
260 $cfg = isset( $action_data['config'] ) ? $action_data['config'] : array();
261 $spreadsheet_id = trim( $this->parse_tokens( isset( $cfg['spreadsheet_id'] ) ? $cfg['spreadsheet_id'] : '', $trigger_data ) );
262 $range = trim( $this->parse_tokens( isset( $cfg['range'] ) ? $cfg['range'] : '', $trigger_data ) );
263 $sa_json = trim( isset( $cfg['service_account_json'] ) ? $cfg['service_account_json'] : '' );
264
265 if ( empty( $sa_json ) ) {
266 $sa_json = get_option( 'wpbot_automator_gs_sa_json', '' );
267 }
268
269 if ( empty( $spreadsheet_id ) || empty( $sa_json ) || empty( $range ) ) {
270 return array(
271 'success' => false,
272 'message' => 'Google Sheets Clear Range: spreadsheet_id, service_account_json, and range are required.',
273 );
274 }
275
276 $token = $this->get_access_token( $sa_json );
277 if ( is_wp_error( $token ) ) {
278 return array( 'success' => false, 'message' => $token->get_error_message() );
279 }
280
281 $encoded_range = rawurlencode( $this->quote_sheet_name( $range ) );
282 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}/values/{$encoded_range}:clear";
283
284 $response = wp_remote_post(
285 $url,
286 array(
287 'headers' => array(
288 'Authorization' => 'Bearer ' . $token,
289 'Content-Type' => 'application/json',
290 ),
291 'body' => '{}',
292 'timeout' => 20,
293 )
294 );
295
296 return $this->handle_api_response( $response, 'Clear Range', $spreadsheet_id, $sa_json );
297 }
298
299 /**
300 * Get values from a range in a Google Sheet.
301 */
302 private function get_values( $action_data, $trigger_data ) {
303 $cfg = isset( $action_data['config'] ) ? $action_data['config'] : array();
304 $spreadsheet_id = trim( $this->parse_tokens( isset( $cfg['spreadsheet_id'] ) ? $cfg['spreadsheet_id'] : '', $trigger_data ) );
305 $range = trim( $this->parse_tokens( isset( $cfg['range'] ) ? $cfg['range'] : '', $trigger_data ) );
306 $sa_json = trim( isset( $cfg['service_account_json'] ) ? $cfg['service_account_json'] : '' );
307
308 if ( empty( $spreadsheet_id ) || empty( $sa_json ) || empty( $range ) ) {
309 $hint = '';
310 if ( ! empty( $trigger_data['fields'] ) || ( isset( $trigger_data['form_id'] ) ) ) {
311 $hint = ' Hint: If you want to SAVE form data to a sheet, please use the "Add Row (Save Form Data)" action instead.';
312 }
313 return array(
314 'success' => false,
315 'message' => 'Google Sheets Get Values: spreadsheet_id, service_account_json, and range are required.' . $hint,
316 );
317 }
318
319 $token = $this->get_access_token( $sa_json );
320 if ( is_wp_error( $token ) ) {
321 return array( 'success' => false, 'message' => $token->get_error_message() );
322 }
323
324 $encoded_range = rawurlencode( $range );
325 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}/values/{$encoded_range}";
326
327 $response = wp_remote_get(
328 $url,
329 array(
330 'headers' => array(
331 'Authorization' => 'Bearer ' . $token,
332 ),
333 'timeout' => 20,
334 )
335 );
336
337 if ( is_wp_error( $response ) ) {
338 return array( 'success' => false, 'message' => $response->get_error_message() );
339 }
340
341 $code = wp_remote_retrieve_response_code( $response );
342 $body = json_decode( wp_remote_retrieve_body( $response ), true );
343
344 if ( $code >= 200 && $code < 300 ) {
345 return array(
346 'success' => true,
347 'values' => isset( $body['values'] ) ? $body['values'] : array(),
348 'message' => 'Values retrieved successfully.',
349 );
350 }
351
352 $error = isset( $body['error']['message'] ) ? $body['error']['message'] : 'Unknown API error.';
353 if ( $code === 400 && strpos( $error, 'Unable to parse range' ) !== false ) {
354 $available_sheets = $this->get_spreadsheet_sheets( $spreadsheet_id, $sa_json );
355 $sheet_list = ! empty( $available_sheets ) ? ' Available sheets: ' . implode( ', ', $available_sheets ) : ' (Could not retrieve list of available sheets).';
356 $error .= ' Tip: Ensure the Sheet Name matches exactly (case-sensitive). ' . $sheet_list;
357 }
358 return array( 'success' => false, 'message' => "Google Sheets Get Values failed (HTTP {$code}): {$error}" );
359 }
360
361 /**
362 * Extract fields from trigger data for automated mapping.
363 *
364 * @param array $trigger_data Data passed from the trigger.
365 * @return array List of values.
366 */
367 private function extract_fields_from_trigger( $trigger_data ) {
368 $values = array();
369
370 // Case 1: Fields array (WPForms, Fluent Forms, etc.)
371 if ( ! empty( $trigger_data['fields'] ) && is_array( $trigger_data['fields'] ) ) {
372 foreach ( $trigger_data['fields'] as $field ) {
373 // WPForms structure: fields[id][value] or fields[id] = value
374 if ( is_array( $field ) ) {
375 if ( isset( $field['value'] ) ) {
376 $values[] = $field['value'];
377 } else {
378 // Flatten any other nested arrays or just skip
379 $values[] = wp_json_encode( $field );
380 }
381 }
382 // CF7 / Fluent Forms often just have key => value
383 elseif ( is_scalar( $field ) ) {
384 $values[] = $field;
385 }
386 }
387 }
388
389 // Fallback: If no fields, but we have some core data.
390 if ( empty( $values ) ) {
391 // Some triggers might pass data directly.
392 $exclude_keys = array( 'form_id', 'form_name', 'entry_id', 'sub_id' );
393 foreach ( $trigger_data as $key => $val ) {
394 if ( ! in_array( $key, $exclude_keys ) && ( is_string( $val ) || is_numeric( $val ) ) ) {
395 $values[] = $val;
396 }
397 }
398 }
399
400 return $values;
401 }
402
403 // ─────────────────────────────────────────────────────────────────────────
404 // JWT / Auth Helpers
405 // ─────────────────────────────────────────────────────────────────────────
406
407 /**
408 * Obtain an OAuth2 access token from a Service Account JSON string.
409 *
410 * Builds and signs a JWT, then exchanges it for an access token via
411 * https://oauth2.googleapis.com/token — no external library needed.
412 *
413 * @param string $sa_json Raw service account JSON string.
414 * @return string|\WP_Error Access token string or WP_Error on failure.
415 */
416 private function get_access_token( $sa_json ) {
417 // Attempt to decode the service account JSON.
418 $sa = json_decode( $sa_json, true );
419
420 if ( JSON_ERROR_NONE !== json_last_error() || empty( $sa['private_key'] ) || empty( $sa['client_email'] ) ) {
421 return new \WP_Error(
422 'invalid_sa_json',
423 __( 'Google Sheets: Invalid service account JSON. Check the credentials field.', 'wpbot-automator' )
424 );
425 }
426
427 $now = time();
428 $header = $this->base64url_encode( wp_json_encode( array( 'alg' => 'RS256', 'typ' => 'JWT' ) ) );
429 $claim = $this->base64url_encode(
430 wp_json_encode(
431 array(
432 'iss' => $sa['client_email'],
433 'scope' => 'https://www.googleapis.com/auth/spreadsheets',
434 'aud' => 'https://oauth2.googleapis.com/token',
435 'iat' => $now,
436 'exp' => $now + 3600,
437 )
438 )
439 );
440
441 $to_sign = $header . '.' . $claim;
442 $key = openssl_pkey_get_private( $sa['private_key'] );
443
444 if ( false === $key ) {
445 return new \WP_Error(
446 'invalid_private_key',
447 __( 'Google Sheets: Could not parse private key from service account JSON.', 'wpbot-automator' )
448 );
449 }
450
451 $signature = '';
452 if ( ! openssl_sign( $to_sign, $signature, $key, 'SHA256' ) ) {
453 return new \WP_Error(
454 'sign_failed',
455 __( 'Google Sheets: Failed to sign JWT. Ensure OpenSSL is enabled on your server.', 'wpbot-automator' )
456 );
457 }
458
459 $jwt = $to_sign . '.' . $this->base64url_encode( $signature );
460
461 $response = wp_remote_post(
462 'https://oauth2.googleapis.com/token',
463 array(
464 'body' => array(
465 'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
466 'assertion' => $jwt,
467 ),
468 'timeout' => 20,
469 )
470 );
471
472 if ( is_wp_error( $response ) ) {
473 return $response;
474 }
475
476 $token_data = json_decode( wp_remote_retrieve_body( $response ), true );
477
478 if ( empty( $token_data['access_token'] ) ) {
479 $err = isset( $token_data['error_description'] ) ? $token_data['error_description'] : 'Unknown token error.';
480 return new \WP_Error( 'token_error', "Google Sheets auth failed: {$err}" );
481 }
482
483 return $token_data['access_token'];
484 }
485
486
487 /**
488 * Ensure a sheet name or range is properly quoted for the Google Sheets API.
489 *
490 * @param string $range The range or sheet name.
491 * @return string
492 */
493 private function quote_sheet_name( $range ) {
494 if ( empty( $range ) ) {
495 return $range;
496 }
497
498 // If it contains a '!', we need to quote the part before it.
499 if ( strpos( $range, '!' ) !== false ) {
500 $parts = explode( '!', $range, 2 );
501 $sheet = $parts[0];
502 $cells = $parts[1];
503 // Only quote if not already quoted AND if it needs quoting (contains non-alphanumeric).
504 if ( strpos( $sheet, "'" ) !== 0 && preg_match( '/[^A-Za-z0-9_]/', $sheet ) ) {
505 $sheet = "'" . str_replace( "'", "''", $sheet ) . "'";
506 }
507 return $sheet . '!' . $cells;
508 }
509
510 // If no '!', assume it's a sheet name.
511 // Only quote if not already quoted AND if it needs quoting.
512 if ( strpos( $range, "'" ) !== 0 && preg_match( '/[^A-Za-z0-9_]/', $range ) ) {
513 return "'" . str_replace( "'", "''", $range ) . "'";
514 }
515
516 return $range;
517 }
518
519 /**
520 * Base64URL encode (RFC 4648 §5, no padding).
521 *
522 * @param string $data Data to encode.
523 * @return string
524 */
525 private function base64url_encode( $data ) {
526 return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' );
527 }
528
529 /**
530 * Handle a Google API response uniformly.
531 *
532 * @param array|\WP_Error $response wp_remote_* response.
533 * @param string $context Human-readable action name for error messages.
534 * @return array { success: bool, message: string }
535 */
536 private function handle_api_response( $response, $context, $spreadsheet_id = '', $sa_json = '' ) {
537 if ( is_wp_error( $response ) ) {
538 return array( 'success' => false, 'message' => $response->get_error_message() );
539 }
540
541 $code = wp_remote_retrieve_response_code( $response );
542 $body = json_decode( wp_remote_retrieve_body( $response ), true );
543
544 if ( $code >= 200 && $code < 300 ) {
545 return array( 'success' => true, 'message' => "Google Sheets {$context} completed successfully." );
546 }
547
548 $error = isset( $body['error']['message'] ) ? $body['error']['message'] : 'Unknown API error.';
549
550 // Advanced diagnostics for range parsing errors.
551 if ( $code === 400 && strpos( $error, 'Unable to parse range' ) !== false ) {
552 $available_sheets = $this->get_spreadsheet_sheets( $spreadsheet_id, $sa_json );
553 $sheet_list = ! empty( $available_sheets ) ? ' Available sheets in this spreadsheet: ' . implode( ', ', $available_sheets ) : ' (Could not retrieve list of available sheets).';
554 $error .= ' Tip: Ensure the Sheet Name matches exactly (case-sensitive). ' . $sheet_list;
555 }
556
557 return array( 'success' => false, 'message' => "Google Sheets {$context} failed (HTTP {$code}): {$error}" );
558 }
559
560 /**
561 * Get a list of all sheet titles in a spreadsheet.
562 *
563 * @param string $spreadsheet_id Spreadsheet ID.
564 * @param string $sa_json Service Account JSON.
565 * @return array List of sheet titles.
566 */
567 private function get_spreadsheet_sheets( $spreadsheet_id, $sa_json ) {
568 if ( empty( $spreadsheet_id ) || empty( $sa_json ) ) {
569 return array();
570 }
571
572 $token = $this->get_access_token( $sa_json );
573 if ( is_wp_error( $token ) ) {
574 return array();
575 }
576
577 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}?fields=sheets.properties.title";
578 $response = wp_remote_get(
579 $url,
580 array(
581 'headers' => array(
582 'Authorization' => 'Bearer ' . $token,
583 ),
584 'timeout' => 20,
585 )
586 );
587
588 if ( is_wp_error( $response ) ) {
589 return array();
590 }
591
592 $body = json_decode( wp_remote_retrieve_body( $response ), true );
593 $titles = array();
594 if ( ! empty( $body['sheets'] ) ) {
595 foreach ( $body['sheets'] as $sheet ) {
596 if ( isset( $sheet['properties']['title'] ) ) {
597 $titles[] = $sheet['properties']['title'];
598 }
599 }
600 }
601 return $titles;
602 }
603
604 /**
605 * Create a new sheet in a spreadsheet.
606 *
607 * @param string $spreadsheet_id Spreadsheet ID.
608 * @param string $sheet_name Sheet Name to create.
609 * @param string $sa_json Service Account JSON.
610 * @return bool|\WP_Error
611 */
612 private function create_sheet( $spreadsheet_id, $sheet_name, $sa_json ) {
613 $token = $this->get_access_token( $sa_json );
614 if ( is_wp_error( $token ) ) {
615 return $token;
616 }
617
618 $url = "https://sheets.googleapis.com/v4/spreadsheets/{$spreadsheet_id}:batchUpdate";
619 $response = wp_remote_post(
620 $url,
621 array(
622 'headers' => array(
623 'Authorization' => 'Bearer ' . $token,
624 'Content-Type' => 'application/json',
625 ),
626 'body' => wp_json_encode(
627 array(
628 'requests' => array(
629 array(
630 'addSheet' => array(
631 'properties' => array(
632 'title' => $sheet_name,
633 ),
634 ),
635 ),
636 ),
637 )
638 ),
639 'timeout' => 20,
640 )
641 );
642
643 if ( is_wp_error( $response ) ) {
644 return $response;
645 }
646
647 $code = wp_remote_retrieve_response_code( $response );
648 if ( $code < 200 || $code >= 300 ) {
649 $body = json_decode( wp_remote_retrieve_body( $response ), true );
650 $error = isset( $body['error']['message'] ) ? $body['error']['message'] : 'Unknown error creating sheet.';
651 return new \WP_Error( 'gs_create_sheet_failed', "HTTP {$code}: {$error}" );
652 }
653
654 return true;
655 }
656 }
657