PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
← All changes | backend/src/Services/GoogleSheets/GoogleSheetsService.php +149 -2 1.4 → 1.5 View file →
@@ -15,8 +15,11 @@
15 15 {
16 16 private const GOOGLE_USERINFO_URL = 'https://www.googleapis.com/oauth2/v2/userinfo';
17 17 private const DRIVE_FILES_URL = 'https://www.googleapis.com/drive/v3/files';
18 18 private const SHEETS_API_URL = 'https://sheets.googleapis.com/v4/spreadsheets';
19 + private const PICKER_CONFIG_TRANSIENT = 'ivyforms_google_sheets_picker_config';
20 + public const PICKER_NOT_CONFIGURED_CODE = 'google_sheets_picker_not_configured';
21 + public const PICKER_ERROR_CODE = 'google_sheets_picker_error';
19 22
20 23 private SettingsService $settingsService;
21 24
22 25 public function __construct(SettingsService $settingsService)
@@ -237,9 +240,14 @@
237 240 ]);
238 241 }
239 242
240 243 /**
244 + * Legacy Drive listing. Spreadsheet selection uses the Google Picker, which
245 + * only grants drive.file access to files the user picks. This query does not
246 + * see those files and often returns an empty list. Kept for older clients.
247 + *
241 248 * @return array<array{id: string, name: string}>
249 + * @deprecated Use the Picker flow (picker-config) instead of listing Drive files.
242 250 */
243 251 public function listSpreadsheets(): array
244 252 {
245 253 $url = add_query_arg(
@@ -320,10 +328,12 @@
320 328 if (!is_array($values)) {
321 329 return [];
322 330 }
323 331
324 - return array_values(array_filter(array_map('strval', $values), static function ($value) {
325 - return trim($value) !== '';
332 + return array_values(array_filter(array_map(static function ($value) {
333 + return trim((string) $value);
334 + }, $values), static function ($value) {
335 + return $value !== '';
326 336 }));
327 337 }
328 338
329 339 /**
@@ -545,8 +555,145 @@
545 555
546 556 $filtered = apply_filters('ivyforms/google_sheets/middleware_url', '');
547 557
548 558 return is_string($filtered) ? rtrim($filtered, '/') : '';
559 + }
560 +
561 + /**
562 + * Picker credentials from middleware (cached) plus a short-lived access token
563 + * for the Google Picker widget.
564 + *
565 + * @return array{
566 + * success: bool,
567 + * code?: string,
568 + * message?: string,
569 + * developerKey?: string,
570 + * appId?: string,
571 + * accessToken?: string
572 + * }
573 + */
574 + public function getPickerConfig(): array
575 + {
576 + $accessToken = $this->getValidAccessToken();
577 + if ($accessToken === null) {
578 + return [
579 + 'success' => false,
580 + 'code' => self::PICKER_ERROR_CODE,
581 + 'message' => BackendStrings::getIntegrationsStrings()['google_sheets_not_connected'],
582 + ];
583 + }
584 +
585 + $pickerKeys = $this->getCachedOrFetchedPickerKeys();
586 + if (empty($pickerKeys['success'])) {
587 + $code = (string) ($pickerKeys['code'] ?? self::PICKER_ERROR_CODE);
588 +
589 + return [
590 + 'success' => false,
591 + 'code' => $code,
592 + 'message' => $code === self::PICKER_NOT_CONFIGURED_CODE
593 + ? BackendStrings::getIntegrationsStrings()['google_sheets_picker_not_configured']
594 + : BackendStrings::getIntegrationsStrings()['google_sheets_picker_error'],
595 + ];
596 + }
597 +
598 + return [
599 + 'success' => true,
600 + 'developerKey' => $pickerKeys['developerKey'],
601 + 'appId' => $pickerKeys['appId'],
602 + 'accessToken' => $accessToken,
603 + ];
604 + }
605 +
606 + /**
607 + * @return array{success: true, developerKey: string, appId: string}|array{success: false, code: string}
608 + */
609 + private function getCachedOrFetchedPickerKeys(): array
610 + {
611 + $cached = get_transient(self::PICKER_CONFIG_TRANSIENT);
612 + if (is_array($cached)) {
613 + $developerKey = trim((string) ($cached['developerKey'] ?? ''));
614 + $appId = trim((string) ($cached['appId'] ?? ''));
615 + if ($developerKey !== '' && $appId !== '') {
616 + return [
617 + 'success' => true,
618 + 'developerKey' => $developerKey,
619 + 'appId' => $appId,
620 + ];
621 + }
622 + }
623 +
624 + $fetched = $this->fetchMiddlewarePickerConfig();
625 + if (!empty($fetched['success'])) {
626 + set_transient(self::PICKER_CONFIG_TRANSIENT, [
627 + 'developerKey' => $fetched['developerKey'],
628 + 'appId' => $fetched['appId'],
629 + ], DAY_IN_SECONDS);
630 + }
631 +
632 + return $fetched;
633 + }
634 +
635 + /**
636 + * @return array{success: true, developerKey: string, appId: string}|array{success: false, code: string}
637 + */
638 + private function fetchMiddlewarePickerConfig(): array
639 + {
640 + $baseUrl = $this->getMiddlewareApiBaseUrl();
641 + if ($baseUrl === '') {
642 + return [
643 + 'success' => false,
644 + 'code' => self::PICKER_ERROR_CODE,
645 + ];
646 + }
647 +
648 + $response = wp_remote_get(
649 + $baseUrl . '/google-sheets/picker-config',
650 + ['timeout' => 15]
651 + );
652 +
653 + if (is_wp_error($response)) {
654 + return [
655 + 'success' => false,
656 + 'code' => self::PICKER_ERROR_CODE,
657 + ];
658 + }
659 +
660 + $statusCode = (int) wp_remote_retrieve_response_code($response);
661 + $body = json_decode((string) wp_remote_retrieve_body($response), true);
662 + $developerKey = '';
663 + $appId = '';
664 + if (is_array($body)) {
665 + $developerKey = trim((string) ($body['developerKey'] ?? ''));
666 + $appId = trim((string) ($body['appId'] ?? ''));
667 + }
668 +
669 + $keysMissing = $developerKey === '' || $appId === '';
670 + if ($statusCode === 400 && $keysMissing) {
671 + return [
672 + 'success' => false,
673 + 'code' => self::PICKER_NOT_CONFIGURED_CODE,
674 + ];
675 + }
676 +
677 + if ($statusCode < 200 || $statusCode >= 300 || !is_array($body)) {
678 + return [
679 + 'success' => false,
680 + 'code' => self::PICKER_ERROR_CODE,
681 + ];
682 + }
683 +
684 + if ($keysMissing) {
685 + return [
686 + 'success' => false,
687 + 'code' => self::PICKER_NOT_CONFIGURED_CODE,
688 + ];
689 + }
690 +
691 + return [
692 + 'success' => true,
693 + 'developerKey' => $developerKey,
694 + 'appId' => $appId,
695 + ];
549 696 }
550 697
551 698 private function fetchMiddlewareAuthorizationUrl(string $state): string
552 699 {