<?php

/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
namespace ForminatorGoogleAddon\Google;

use BadMethodCallException;
use DomainException;
use ForminatorGoogleAddon\Google\AccessToken\Revoke;
use ForminatorGoogleAddon\Google\AccessToken\Verify;
use ForminatorGoogleAddon\Google\Auth\ApplicationDefaultCredentials;
use ForminatorGoogleAddon\Google\Auth\Cache\MemoryCacheItemPool;
use ForminatorGoogleAddon\Google\Auth\Credentials\ServiceAccountCredentials;
use ForminatorGoogleAddon\Google\Auth\Credentials\UserRefreshCredentials;
use ForminatorGoogleAddon\Google\Auth\CredentialsLoader;
use ForminatorGoogleAddon\Google\Auth\FetchAuthTokenCache;
use ForminatorGoogleAddon\Google\Auth\GetUniverseDomainInterface;
use ForminatorGoogleAddon\Google\Auth\HttpHandler\HttpHandlerFactory;
use ForminatorGoogleAddon\Google\Auth\OAuth2;
use ForminatorGoogleAddon\Google\AuthHandler\AuthHandlerFactory;
use ForminatorGoogleAddon\Google\Http\REST;
use ForminatorGoogleAddon\GuzzleHttp\Client as GuzzleClient;
use ForminatorGoogleAddon\GuzzleHttp\ClientInterface;
use ForminatorGoogleAddon\GuzzleHttp\Ring\Client\StreamHandler;
use InvalidArgumentException;
use LogicException;
use ForminatorGoogleAddon\Monolog\Handler\StreamHandler as MonologStreamHandler;
use ForminatorGoogleAddon\Monolog\Handler\SyslogHandler as MonologSyslogHandler;
use ForminatorGoogleAddon\Monolog\Logger;
use ForminatorGoogleAddon\Psr\Cache\CacheItemPoolInterface;
use ForminatorGoogleAddon\Psr\Http\Message\RequestInterface;
use ForminatorGoogleAddon\Psr\Http\Message\ResponseInterface;
use ForminatorGoogleAddon\Psr\Log\LoggerInterface;
use UnexpectedValueException;
/**
 * The Google API Client
 * https://github.com/google/google-api-php-client
 */
class Client
{
    const LIBVER = "2.12.6";
    const USER_AGENT_SUFFIX = "google-api-php-client/";
    const OAUTH2_REVOKE_URI = 'https://oauth2.googleapis.com/revoke';
    const OAUTH2_TOKEN_URI = 'https://oauth2.googleapis.com/token';
    const OAUTH2_AUTH_URL = 'https://accounts.google.com/o/oauth2/v2/auth';
    const API_BASE_PATH = 'https://www.googleapis.com';
    /**
     * @var ?OAuth2 $auth
     */
    private $auth;
    /**
     * @var ClientInterface $http
     */
    private $http;
    /**
     * @var ?CacheItemPoolInterface $cache
     */
    private $cache;
    /**
     * @var array access token
     */
    private $token;
    /**
     * @var array $config
     */
    private $config;
    /**
     * @var ?LoggerInterface $logger
     */
    private $logger;
    /**
     * @var ?CredentialsLoader $credentials
     */
    private $credentials;
    /**
     * @var boolean $deferExecution
     */
    private $deferExecution = \false;
    /** @var array $scopes */
    // Scopes requested by the client
    protected $requestedScopes = [];
    /**
     * Construct the Google Client.
     *
     * @param array $config {
     *     An array of required and optional arguments.
     *
     *     @type string $application_name
     *           The name of your application
     *     @type string $base_path
     *           The base URL for the service. This is only accounted for when calling
     *           {@see Client::authorize()} directly.
     *     @type string $client_id
     *           Your Google Cloud client ID found in https://developers.google.com/console
     *     @type string $client_secret
     *           Your Google Cloud client secret found in https://developers.google.com/console
     *     @type string|array|CredentialsLoader $credentials
     *           Can be a path to JSON credentials or an array representing those
     *           credentials (@see Google\Client::setAuthConfig), or an instance of
     *           {@see CredentialsLoader}.
     *     @type string|array $scopes
     *           {@see Google\Client::setScopes}
     *     @type string $quota_project
     *           Sets X-Goog-User-Project, which specifies a user project to bill
     *           for access charges associated with the request.
     *     @type string $redirect_uri
     *     @type string $state
     *     @type string $developer_key
     *           Simple API access key, also from the API console. Ensure you get
     *           a Server key, and not a Browser key.
     *           **NOTE:** The universe domain is assumed to be "googleapis.com" unless
     *           explicitly set. When setting an API ley directly via this option, there
     *           is no way to verify the universe domain. Be sure to set the
     *           "universe_domain" option if "googleapis.com" is not intended.
     *     @type bool $use_application_default_credentials
     *           For use with Google Cloud Platform
     *           fetch the ApplicationDefaultCredentials, if applicable
     *           {@see https://developers.google.com/identity/protocols/application-default-credentials}
     *     @type string $signing_key
     *     @type string $signing_algorithm
     *     @type string $subject
     *     @type string $hd
     *     @type string $prompt
     *     @type string $openid
     *     @type bool $include_granted_scopes
     *     @type string $login_hint
     *     @type string $request_visible_actions
     *     @type string $access_type
     *     @type string $approval_prompt
     *     @type array $retry
     *           Task Runner retry configuration
     *           {@see \Google\Task\Runner}
     *     @type array $retry_map
     *     @type CacheItemPoolInterface $cache
     *           Cache class implementing {@see CacheItemPoolInterface}. Defaults
     *           to {@see MemoryCacheItemPool}.
     *     @type array $cache_config
     *           Cache config for downstream auth caching.
     *     @type callable $token_callback
     *           Function to be called when an access token is fetched. Follows
     *           the signature `function (string $cacheKey, string $accessToken)`.
     *     @type \Firebase\JWT $jwt
     *           Service class used in {@see Client::verifyIdToken()}. Explicitly
     *           pass this in to avoid setting {@see \Firebase\JWT::$leeway}
     *     @type bool $api_format_v2
     *           Setting api_format_v2 will return more detailed error messages
     *           from certain APIs.
     *     @type string $universe_domain
     *           Setting the universe domain will change the default rootUrl of the service.
     *           If not set explicitly, the universe domain will be the value provided in the
     *.          "GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable, or "googleapis.com".
     *  }
     */
    public function __construct(array $config = [])
    {
        $this->config = \array_merge(['application_name' => '', 'base_path' => self::API_BASE_PATH, 'client_id' => '', 'client_secret' => '', 'credentials' => null, 'scopes' => null, 'quota_project' => null, 'redirect_uri' => null, 'state' => null, 'developer_key' => '', 'use_application_default_credentials' => \false, 'signing_key' => null, 'signing_algorithm' => null, 'subject' => null, 'hd' => '', 'prompt' => '', 'openid.realm' => '', 'include_granted_scopes' => null, 'login_hint' => '', 'request_visible_actions' => '', 'access_type' => 'online', 'approval_prompt' => 'auto', 'retry' => [], 'retry_map' => null, 'cache' => null, 'cache_config' => [], 'token_callback' => null, 'jwt' => null, 'api_format_v2' => \false, 'universe_domain' => \getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN') ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN], $config);
        if (!\is_null($this->config['credentials'])) {
            if ($this->config['credentials'] instanceof CredentialsLoader) {
                $this->credentials = $this->config['credentials'];
            } else {
                $this->setAuthConfig($this->config['credentials']);
            }
            unset($this->config['credentials']);
        }
        if (!\is_null($this->config['scopes'])) {
            $this->setScopes($this->config['scopes']);
            unset($this->config['scopes']);
        }
        // Set a default token callback to update the in-memory access token
        if (\is_null($this->config['token_callback'])) {
            $this->config['token_callback'] = function ($cacheKey, $newAccessToken) {
                $this->setAccessToken([
                    'access_token' => $newAccessToken,
                    'expires_in' => 3600,
                    // Google default
                    'created' => \time(),
                ]);
            };
        }
        if (!\is_null($this->config['cache'])) {
            $this->setCache($this->config['cache']);
            unset($this->config['cache']);
        }
    }
    /**
     * Get a string containing the version of the library.
     *
     * @return string
     */
    public function getLibraryVersion()
    {
        return self::LIBVER;
    }
    /**
     * For backwards compatibility
     * alias for fetchAccessTokenWithAuthCode
     *
     * @param string $code string code from accounts.google.com
     * @return array access token
     * @deprecated
     */
    public function authenticate($code)
    {
        return $this->fetchAccessTokenWithAuthCode($code);
    }
    /**
     * Attempt to exchange a code for an valid authentication token.
     * Helper wrapped around the OAuth 2.0 implementation.
     *
     * @param string $code code from accounts.google.com
     * @param string $codeVerifier the code verifier used for PKCE (if applicable)
     * @return array access token
     */
    public function fetchAccessTokenWithAuthCode($code, $codeVerifier = null)
    {
        if (\strlen($code) == 0) {
            throw new InvalidArgumentException("Invalid code");
        }
        $auth = $this->getOAuth2Service();
        $auth->setCode($code);
        $auth->setRedirectUri($this->getRedirectUri());
        if ($codeVerifier) {
            $auth->setCodeVerifier($codeVerifier);
        }
        $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
        $creds = $auth->fetchAuthToken($httpHandler);
        if ($creds && isset($creds['access_token'])) {
            $creds['created'] = \time();
            $this->setAccessToken($creds);
        }
        return $creds;
    }
    /**
     * For backwards compatibility
     * alias for fetchAccessTokenWithAssertion
     *
     * @return array access token
     * @deprecated
     */
    public function refreshTokenWithAssertion()
    {
        return $this->fetchAccessTokenWithAssertion();
    }
    /**
     * Fetches a fresh access token with a given assertion token.
     * @param ClientInterface $authHttp optional.
     * @return array access token
     */
    public function fetchAccessTokenWithAssertion(ClientInterface $authHttp = null)
    {
        if (!$this->isUsingApplicationDefaultCredentials()) {
            throw new DomainException('set the JSON service account credentials using' . ' Google\\Client::setAuthConfig or set the path to your JSON file' . ' with the "GOOGLE_APPLICATION_CREDENTIALS" environment variable' . ' and call Google\\Client::useApplicationDefaultCredentials to' . ' refresh a token with assertion.');
        }
        $this->getLogger()->log('info', 'OAuth2 access token refresh with Signed JWT assertion grants.');
        $credentials = $this->createApplicationDefaultCredentials();
        $httpHandler = HttpHandlerFactory::build($authHttp);
        $creds = $credentials->fetchAuthToken($httpHandler);
        if ($creds && isset($creds['access_token'])) {
            $creds['created'] = \time();
            $this->setAccessToken($creds);
        }
        return $creds;
    }
    /**
     * For backwards compatibility
     * alias for fetchAccessTokenWithRefreshToken
     *
     * @param string $refreshToken
     * @return array access token
     */
    public function refreshToken($refreshToken)
    {
        return $this->fetchAccessTokenWithRefreshToken($refreshToken);
    }
    /**
     * Fetches a fresh OAuth 2.0 access token with the given refresh token.
     * @param string $refreshToken
     * @return array access token
     */
    public function fetchAccessTokenWithRefreshToken($refreshToken = null)
    {
        if (null === $refreshToken) {
            if (!isset($this->token['refresh_token'])) {
                throw new LogicException('refresh token must be passed in or set as part of setAccessToken');
            }
            $refreshToken = $this->token['refresh_token'];
        }
        $this->getLogger()->info('OAuth2 access token refresh');
        $auth = $this->getOAuth2Service();
        $auth->setRefreshToken($refreshToken);
        $httpHandler = HttpHandlerFactory::build($this->getHttpClient());
        $creds = $auth->fetchAuthToken($httpHandler);
        if ($creds && isset($creds['access_token'])) {
            $creds['created'] = \time();
            if (!isset($creds['refresh_token'])) {
                $creds['refresh_token'] = $refreshToken;
            }
            $this->setAccessToken($creds);
        }
        return $creds;
    }
    /**
     * Create a URL to obtain user authorization.
     * The authorization endpoint allows the user to first
     * authenticate, and then grant/deny the access request.
     * @param string|array $scope The scope is expressed as an array or list of space-delimited strings.
     * @param array $queryParams Querystring params to add to the authorization URL.
     * @return string
     */
    public function createAuthUrl($scope = null, array $queryParams = [])
    {
        if (empty($scope)) {
            $scope = $this->prepareScopes();
        }
        if (\is_array($scope)) {
            $scope = \implode(' ', $scope);
        }
        // only accept one of prompt or approval_prompt
        $approvalPrompt = $this->config['prompt'] ? null : $this->config['approval_prompt'];
        // include_granted_scopes should be string "true", string "false", or null
        $includeGrantedScopes = $this->config['include_granted_scopes'] === null ? null : \var_export($this->config['include_granted_scopes'], \true);
        $params = \array_filter(['access_type' => $this->config['access_type'], 'approval_prompt' => $approvalPrompt, 'hd' => $this->config['hd'], 'include_granted_scopes' => $includeGrantedScopes, 'login_hint' => $this->config['login_hint'], 'openid.realm' => $this->config['openid.realm'], 'prompt' => $this->config['prompt'], 'redirect_uri' => $this->config['redirect_uri'], 'response_type' => 'code', 'scope' => $scope, 'state' => $this->config['state']]) + $queryParams;
        // If the list of scopes contains plus.login, add request_visible_actions
        // to auth URL.
        $rva = $this->config['request_visible_actions'];
        if (\strlen($rva) > 0 && \false !== \strpos($scope, 'plus.login')) {
            $params['request_visible_actions'] = $rva;
        }
        $auth = $this->getOAuth2Service();
        return (string) $auth->buildFullAuthorizationUri($params);
    }
    /**
     * Adds auth listeners to the HTTP client based on the credentials
     * set in the Google API Client object
     *
     * @param ClientInterface $http the http client object.
     * @return ClientInterface the http client object
     */
    public function authorize(ClientInterface $http = null)
    {
        $http = $http ?: $this->getHttpClient();
        $authHandler = $this->getAuthHandler();
        // These conditionals represent the decision tree for authentication
        //   1.  Check if a Google\Auth\CredentialsLoader instance has been supplied via the "credentials" option
        //   2.  Check for Application Default Credentials
        //   3a. Check for an Access Token
        //   3b. If access token exists but is expired, try to refresh it
        //   4.  Check for API Key
        if ($this->credentials) {
            $this->checkUniverseDomain($this->credentials);
            return $authHandler->attachCredentials($http, $this->credentials, $this->config['token_callback']);
        }
        if ($this->isUsingApplicationDefaultCredentials()) {
            $credentials = $this->createApplicationDefaultCredentials();
            $this->checkUniverseDomain($credentials);
            return $authHandler->attachCredentialsCache($http, $credentials, $this->config['token_callback']);
        }
        if ($token = $this->getAccessToken()) {
            $scopes = $this->prepareScopes();
            // add refresh subscriber to request a new token
            if (isset($token['refresh_token']) && $this->isAccessTokenExpired()) {
                $credentials = $this->createUserRefreshCredentials($scopes, $token['refresh_token']);
                $this->checkUniverseDomain($credentials);
                return $authHandler->attachCredentials($http, $credentials, $this->config['token_callback']);
            }
            return $authHandler->attachToken($http, $token, (array) $scopes);
        }
        if ($key = $this->config['developer_key']) {
            return $authHandler->attachKey($http, $key);
        }
        return $http;
    }
    /**
     * Set the configuration to use application default credentials for
     * authentication
     *
     * @see https://developers.google.com/identity/protocols/application-default-credentials
     * @param boolean $useAppCreds
     */
    public function useApplicationDefaultCredentials($useAppCreds = \true)
    {
        $this->config['use_application_default_credentials'] = $useAppCreds;
    }
    /**
     * To prevent useApplicationDefaultCredentials from inappropriately being
     * called in a conditional
     *
     * @see https://developers.google.com/identity/protocols/application-default-credentials
     */
    public function isUsingApplicationDefaultCredentials()
    {
        return $this->config['use_application_default_credentials'];
    }
    /**
     * Set the access token used for requests.
     *
     * Note that at the time requests are sent, tokens are cached. A token will be
     * cached for each combination of service and authentication scopes. If a
     * cache pool is not provided, creating a new instance of the client will
     * allow modification of access tokens. If a persistent cache pool is
     * provided, in order to change the access token, you must clear the cached
     * token by calling `$client->getCache()->clear()`. (Use caution in this case,
     * as calling `clear()` will remove all cache items, including any items not
     * related to Google API PHP Client.)
     *
     * **NOTE:** The universe domain is assumed to be "googleapis.com" unless
     * explicitly set. When setting an access token directly via this method, there
     * is no way to verify the universe domain. Be sure to set the "universe_domain"
     * option if "googleapis.com" is not intended.
     *
     * @param string|array $token
     * @throws InvalidArgumentException
     */
    public function setAccessToken($token)
    {
        if (\is_string($token)) {
            if ($json = \json_decode($token, \true)) {
                $token = $json;
            } else {
                // assume $token is just the token string
                $token = ['access_token' => $token];
            }
        }
        if ($token == null) {
            throw new InvalidArgumentException('invalid json token');
        }
        if (!isset($token['access_token'])) {
            throw new InvalidArgumentException("Invalid token format");
        }
        $this->token = $token;
    }
    public function getAccessToken()
    {
        return $this->token;
    }
    /**
     * @return string|null
     */
    public function getRefreshToken()
    {
        if (isset($this->token['refresh_token'])) {
            return $this->token['refresh_token'];
        }
        return null;
    }
    /**
     * Returns if the access_token is expired.
     * @return bool Returns True if the access_token is expired.
     */
    public function isAccessTokenExpired()
    {
        if (!$this->token) {
            return \true;
        }
        $created = 0;
        if (isset($this->token['created'])) {
            $created = $this->token['created'];
        } elseif (isset($this->token['id_token'])) {
            // check the ID token for "iat"
            // signature verification is not required here, as we are just
            // using this for convenience to save a round trip request
            // to the Google API server
            $idToken = $this->token['id_token'];
            if (\substr_count($idToken, '.') == 2) {
                $parts = \explode('.', $idToken);
                $payload = \json_decode(\base64_decode($parts[1]), \true);
                if ($payload && isset($payload['iat'])) {
                    $created = $payload['iat'];
                }
            }
        }
        if (!isset($this->token['expires_in'])) {
            // if the token does not have an "expires_in", then it's considered expired
            return \true;
        }
        // If the token is set to expire in the next 30 seconds.
        return $created + ($this->token['expires_in'] - 30) < \time();
    }
    /**
     * @deprecated See UPGRADING.md for more information
     */
    public function getAuth()
    {
        throw new BadMethodCallException('This function no longer exists. See UPGRADING.md for more information');
    }
    /**
     * @deprecated See UPGRADING.md for more information
     */
    public function setAuth($auth)
    {
        throw new BadMethodCallException('This function no longer exists. See UPGRADING.md for more information');
    }
    /**
     * Set the OAuth 2.0 Client ID.
     * @param string $clientId
     */
    public function setClientId($clientId)
    {
        $this->config['client_id'] = $clientId;
    }
    public function getClientId()
    {
        return $this->config['client_id'];
    }
    /**
     * Set the OAuth 2.0 Client Secret.
     * @param string $clientSecret
     */
    public function setClientSecret($clientSecret)
    {
        $this->config['client_secret'] = $clientSecret;
    }
    public function getClientSecret()
    {
        return $this->config['client_secret'];
    }
    /**
     * Set the OAuth 2.0 Redirect URI.
     * @param string $redirectUri
     */
    public function setRedirectUri($redirectUri)
    {
        $this->config['redirect_uri'] = $redirectUri;
    }
    public function getRedirectUri()
    {
        return $this->config['redirect_uri'];
    }
    /**
     * Set OAuth 2.0 "state" parameter to achieve per-request customization.
     * @see http://tools.ietf.org/html/draft-ietf-oauth-v2-22#section-3.1.2.2
     * @param string $state
     */
    public function setState($state)
    {
        $this->config['state'] = $state;
    }
    /**
     * @param string $accessType Possible values for access_type include:
     *  {@code "offline"} to request offline access from the user.
     *  {@code "online"} to request online access from the user.
     */
    public function setAccessType($accessType)
    {
        $this->config['access_type'] = $accessType;
    }
    /**
     * @param string $approvalPrompt Possible values for approval_prompt include:
     *  {@code "force"} to force the approval UI to appear.
     *  {@code "auto"} to request auto-approval when possible. (This is the default value)
     */
    public function setApprovalPrompt($approvalPrompt)
    {
        $this->config['approval_prompt'] = $approvalPrompt;
    }
    /**
     * Set the login hint, email address or sub id.
     * @param string $loginHint
     */
    public function setLoginHint($loginHint)
    {
        $this->config['login_hint'] = $loginHint;
    }
    /**
     * Set the application name, this is included in the User-Agent HTTP header.
     * @param string $applicationName
     */
    public function setApplicationName($applicationName)
    {
        $this->config['application_name'] = $applicationName;
    }
    /**
     * If 'plus.login' is included in the list of requested scopes, you can use
     * this method to define types of app activities that your app will write.
     * You can find a list of available types here:
     * @link https://developers.google.com/+/api/moment-types
     *
     * @param array $requestVisibleActions Array of app activity types
     */
    public function setRequestVisibleActions($requestVisibleActions)
    {
        if (\is_array($requestVisibleActions)) {
            $requestVisibleActions = \implode(" ", $requestVisibleActions);
        }
        $this->config['request_visible_actions'] = $requestVisibleActions;
    }
    /**
     * Set the developer key to use, these are obtained through the API Console.
     * @see http://code.google.com/apis/console-help/#generatingdevkeys
     * @param string $developerKey
     */
    public function setDeveloperKey($developerKey)
    {
        $this->config['developer_key'] = $developerKey;
    }
    /**
     * Set the hd (hosted domain) parameter streamlines the login process for
     * Google Apps hosted accounts. By including the domain of the user, you
     * restrict sign-in to accounts at that domain.
     * @param string $hd the domain to use.
     */
    public function setHostedDomain($hd)
    {
        $this->config['hd'] = $hd;
    }
    /**
     * Set the prompt hint. Valid values are none, consent and select_account.
     * If no value is specified and the user has not previously authorized
     * access, then the user is shown a consent screen.
     * @param string $prompt
     *  {@code "none"} Do not display any authentication or consent screens. Must not be specified with other values.
     *  {@code "consent"} Prompt the user for consent.
     *  {@code "select_account"} Prompt the user to select an account.
     */
    public function setPrompt($prompt)
    {
        $this->config['prompt'] = $prompt;
    }
    /**
     * openid.realm is a parameter from the OpenID 2.0 protocol, not from OAuth
     * 2.0. It is used in OpenID 2.0 requests to signify the URL-space for which
     * an authentication request is valid.
     * @param string $realm the URL-space to use.
     */
    public function setOpenidRealm($realm)
    {
        $this->config['openid.realm'] = $realm;
    }
    /**
     * If this is provided with the value true, and the authorization request is
     * granted, the authorization will include any previous authorizations
     * granted to this user/application combination for other scopes.
     * @param bool $include the URL-space to use.
     */
    public function setIncludeGrantedScopes($include)
    {
        $this->config['include_granted_scopes'] = $include;
    }
    /**
     * sets function to be called when an access token is fetched
     * @param callable $tokenCallback - function ($cacheKey, $accessToken)
     */
    public function setTokenCallback(callable $tokenCallback)
    {
        $this->config['token_callback'] = $tokenCallback;
    }
    /**
     * Revoke an OAuth2 access token or refresh token. This method will revoke the current access
     * token, if a token isn't provided.
     *
     * @param string|array|null $token The token (access token or a refresh token) that should be revoked.
     * @return boolean Returns True if the revocation was successful, otherwise False.
     */
    public function revokeToken($token = null)
    {
        $tokenRevoker = new Revoke($this->getHttpClient());
        return $tokenRevoker->revokeToken($token ?: $this->getAccessToken());
    }
    /**
     * Verify an id_token. This method will verify the current id_token, if one
     * isn't provided.
     *
     * @throws LogicException If no token was provided and no token was set using `setAccessToken`.
     * @throws UnexpectedValueException If the token is not a valid JWT.
     * @param string|null $idToken The token (id_token) that should be verified.
     * @return array|false Returns the token payload as an array if the verification was
     * successful, false otherwise.
     */
    public function verifyIdToken($idToken = null)
    {
        $tokenVerifier = new Verify($this->getHttpClient(), $this->getCache(), $this->config['jwt']);
        if (null === $idToken) {
            $token = $this->getAccessToken();
            if (!isset($token['id_token'])) {
                throw new LogicException('id_token must be passed in or set as part of setAccessToken');
            }
            $idToken = $token['id_token'];
        }
        return $tokenVerifier->verifyIdToken($idToken, $this->getClientId());
    }
    /**
     * Set the scopes to be requested. Must be called before createAuthUrl().
     * Will remove any previously configured scopes.
     * @param string|array $scope_or_scopes, ie:
     *    array(
     *        'https://www.googleapis.com/auth/plus.login',
     *        'https://www.googleapis.com/auth/moderator'
     *    );
     */
    public function setScopes($scope_or_scopes)
    {
        $this->requestedScopes = [];
        $this->addScope($scope_or_scopes);
    }
    /**
     * This functions adds a scope to be requested as part of the OAuth2.0 flow.
     * Will append any scopes not previously requested to the scope parameter.
     * A single string will be treated as a scope to request. An array of strings
     * will each be appended.
     * @param string|string[] $scope_or_scopes e.g. "profile"
     */
    public function addScope($scope_or_scopes)
    {
        if (\is_string($scope_or_scopes) && !\in_array($scope_or_scopes, $this->requestedScopes)) {
            $this->requestedScopes[] = $scope_or_scopes;
        } elseif (\is_array($scope_or_scopes)) {
            foreach ($scope_or_scopes as $scope) {
                $this->addScope($scope);
            }
        }
    }
    /**
     * Returns the list of scopes requested by the client
     * @return array the list of scopes
     *
     */
    public function getScopes()
    {
        return $this->requestedScopes;
    }
    /**
     * @return string|null
     * @visible For Testing
     */
    public function prepareScopes()
    {
        if (empty($this->requestedScopes)) {
            return null;
        }
        return \implode(' ', $this->requestedScopes);
    }
    /**
     * Helper method to execute deferred HTTP requests.
     *
     * @template T
     * @param RequestInterface $request
     * @param class-string<T>|false|null $expectedClass
     * @throws \Google\Exception
     * @return mixed|T|ResponseInterface
     */
    public function execute(RequestInterface $request, $expectedClass = null)
    {
        $request = $request->withHeader('User-Agent', \sprintf('%s %s%s', $this->config['application_name'], self::USER_AGENT_SUFFIX, $this->getLibraryVersion()))->withHeader('x-goog-api-client', \sprintf('gl-php/%s gdcl/%s', \phpversion(), $this->getLibraryVersion()));
        if ($this->config['api_format_v2']) {
            $request = $request->withHeader('X-GOOG-API-FORMAT-VERSION', '2');
        }
        // call the authorize method
        // this is where most of the grunt work is done
        $http = $this->authorize();
        return REST::execute($http, $request, $expectedClass, $this->config['retry'], $this->config['retry_map']);
    }
    /**
     * Declare whether batch calls should be used. This may increase throughput
     * by making multiple requests in one connection.
     *
     * @param boolean $useBatch True if the batch support should
     * be enabled. Defaults to False.
     */
    public function setUseBatch($useBatch)
    {
        // This is actually an alias for setDefer.
        $this->setDefer($useBatch);
    }
    /**
     * Are we running in Google AppEngine?
     * return bool
     */
    public function isAppEngine()
    {
        return isset($_SERVER['SERVER_SOFTWARE']) && \strpos($_SERVER['SERVER_SOFTWARE'], 'Google App Engine') !== \false;
    }
    public function setConfig($name, $value)
    {
        $this->config[$name] = $value;
    }
    public function getConfig($name, $default = null)
    {
        return isset($this->config[$name]) ? $this->config[$name] : $default;
    }
    /**
     * For backwards compatibility
     * alias for setAuthConfig
     *
     * @param string $file the configuration file
     * @throws \Google\Exception
     * @deprecated
     */
    public function setAuthConfigFile($file)
    {
        $this->setAuthConfig($file);
    }
    /**
     * Set the auth config from new or deprecated JSON config.
     * This structure should match the file downloaded from
     * the "Download JSON" button on in the Google Developer
     * Console.
     * @param string|array $config the configuration json
     * @throws \Google\Exception
     */
    public function setAuthConfig($config)
    {
        if (\is_string($config)) {
            if (!\file_exists($config)) {
                throw new InvalidArgumentException(\sprintf('file "%s" does not exist', $config));
            }
            $json = \file_get_contents($config);
            if (!($config = \json_decode($json, \true))) {
                throw new LogicException('invalid json for auth config');
            }
        }
        $key = isset($config['installed']) ? 'installed' : 'web';
        if (isset($config['type']) && $config['type'] == 'service_account') {
            // application default credentials
            $this->useApplicationDefaultCredentials();
            // set the information from the config
            $this->setClientId($config['client_id']);
            $this->config['client_email'] = $config['client_email'];
            $this->config['signing_key'] = $config['private_key'];
            $this->config['signing_algorithm'] = 'HS256';
        } elseif (isset($config[$key])) {
            // old-style
            $this->setClientId($config[$key]['client_id']);
            $this->setClientSecret($config[$key]['client_secret']);
            if (isset($config[$key]['redirect_uris'])) {
                $this->setRedirectUri($config[$key]['redirect_uris'][0]);
            }
        } else {
            // new-style
            $this->setClientId($config['client_id']);
            $this->setClientSecret($config['client_secret']);
            if (isset($config['redirect_uris'])) {
                $this->setRedirectUri($config['redirect_uris'][0]);
            }
        }
    }
    /**
     * Use when the service account has been delegated domain wide access.
     *
     * @param string $subject an email address account to impersonate
     */
    public function setSubject($subject)
    {
        $this->config['subject'] = $subject;
    }
    /**
     * Declare whether making API calls should make the call immediately, or
     * return a request which can be called with ->execute();
     *
     * @param boolean $defer True if calls should not be executed right away.
     */
    public function setDefer($defer)
    {
        $this->deferExecution = $defer;
    }
    /**
     * Whether or not to return raw requests
     * @return boolean
     */
    public function shouldDefer()
    {
        return $this->deferExecution;
    }
    /**
     * @return OAuth2 implementation
     */
    public function getOAuth2Service()
    {
        if (!isset($this->auth)) {
            $this->auth = $this->createOAuth2Service();
        }
        return $this->auth;
    }
    /**
     * create a default google auth object
     */
    protected function createOAuth2Service()
    {
        $auth = new OAuth2(['clientId' => $this->getClientId(), 'clientSecret' => $this->getClientSecret(), 'authorizationUri' => self::OAUTH2_AUTH_URL, 'tokenCredentialUri' => self::OAUTH2_TOKEN_URI, 'redirectUri' => $this->getRedirectUri(), 'issuer' => $this->config['client_id'], 'signingKey' => $this->config['signing_key'], 'signingAlgorithm' => $this->config['signing_algorithm']]);
        return $auth;
    }
    /**
     * Set the Cache object
     * @param CacheItemPoolInterface $cache
     */
    public function setCache(CacheItemPoolInterface $cache)
    {
        $this->cache = $cache;
    }
    /**
     * @return CacheItemPoolInterface
     */
    public function getCache()
    {
        if (!$this->cache) {
            $this->cache = $this->createDefaultCache();
        }
        return $this->cache;
    }
    /**
     * @param array $cacheConfig
     */
    public function setCacheConfig(array $cacheConfig)
    {
        $this->config['cache_config'] = $cacheConfig;
    }
    /**
     * Set the Logger object
     * @param LoggerInterface $logger
     */
    public function setLogger(LoggerInterface $logger)
    {
        $this->logger = $logger;
    }
    /**
     * @return LoggerInterface
     */
    public function getLogger()
    {
        if (!isset($this->logger)) {
            $this->logger = $this->createDefaultLogger();
        }
        return $this->logger;
    }
    protected function createDefaultLogger()
    {
        $logger = new Logger('google-api-php-client');
        if ($this->isAppEngine()) {
            $handler = new MonologSyslogHandler('app', \LOG_USER, Logger::NOTICE);
        } else {
            $handler = new MonologStreamHandler('php://stderr', Logger::NOTICE);
        }
        $logger->pushHandler($handler);
        return $logger;
    }
    protected function createDefaultCache()
    {
        return new MemoryCacheItemPool();
    }
    /**
     * Set the Http Client object
     * @param ClientInterface $http
     */
    public function setHttpClient(ClientInterface $http)
    {
        $this->http = $http;
    }
    /**
     * @return ClientInterface
     */
    public function getHttpClient()
    {
        if (null === $this->http) {
            $this->http = $this->createDefaultHttpClient();
        }
        return $this->http;
    }
    /**
     * Set the API format version.
     *
     * `true` will use V2, which may return more useful error messages.
     *
     * @param bool $value
     */
    public function setApiFormatV2($value)
    {
        $this->config['api_format_v2'] = (bool) $value;
    }
    protected function createDefaultHttpClient()
    {
        $guzzleVersion = null;
        if (\defined('\\ForminatorGoogleAddon\\GuzzleHttp\\ClientInterface::MAJOR_VERSION')) {
            $guzzleVersion = ClientInterface::MAJOR_VERSION;
        } elseif (\defined('\\ForminatorGoogleAddon\\GuzzleHttp\\ClientInterface::VERSION')) {
            $guzzleVersion = (int) \substr(ClientInterface::VERSION, 0, 1);
        }
        if (5 === $guzzleVersion) {
            $options = ['base_url' => $this->config['base_path'], 'defaults' => ['exceptions' => \false]];
            if ($this->isAppEngine()) {
                if (\class_exists(StreamHandler::class)) {
                    // set StreamHandler on AppEngine by default
                    $options['handler'] = new StreamHandler();
                    $options['defaults']['verify'] = '/etc/ca-certificates.crt';
                }
            }
        } elseif (6 === $guzzleVersion || 7 === $guzzleVersion) {
            // guzzle 6 or 7
            $options = ['base_uri' => $this->config['base_path'], 'http_errors' => \false];
        } else {
            throw new LogicException('Could not find supported version of Guzzle.');
        }
        return new GuzzleClient($options);
    }
    /**
     * @return FetchAuthTokenCache
     */
    private function createApplicationDefaultCredentials()
    {
        $scopes = $this->prepareScopes();
        $sub = $this->config['subject'];
        $signingKey = $this->config['signing_key'];
        // create credentials using values supplied in setAuthConfig
        if ($signingKey) {
            $serviceAccountCredentials = ['client_id' => $this->config['client_id'], 'client_email' => $this->config['client_email'], 'private_key' => $signingKey, 'type' => 'service_account', 'quota_project_id' => $this->config['quota_project']];
            $credentials = CredentialsLoader::makeCredentials($scopes, $serviceAccountCredentials);
        } else {
            // When $sub is provided, we cannot pass cache classes to ::getCredentials
            // because FetchAuthTokenCache::setSub does not exist.
            // The result is when $sub is provided, calls to ::onGce are not cached.
            $credentials = ApplicationDefaultCredentials::getCredentials($scopes, null, $sub ? null : $this->config['cache_config'], $sub ? null : $this->getCache(), $this->config['quota_project']);
        }
        // for service account domain-wide authority (impersonating a user)
        // @see https://developers.google.com/identity/protocols/OAuth2ServiceAccount
        if ($sub) {
            if (!$credentials instanceof ServiceAccountCredentials) {
                throw new DomainException('domain-wide authority requires service account credentials');
            }
            $credentials->setSub($sub);
        }
        // If we are not using FetchAuthTokenCache yet, create it now
        if (!$credentials instanceof FetchAuthTokenCache) {
            $credentials = new FetchAuthTokenCache($credentials, $this->config['cache_config'], $this->getCache());
        }
        return $credentials;
    }
    protected function getAuthHandler()
    {
        // Be very careful using the cache, as the underlying auth library's cache
        // implementation is naive, and the cache keys do not account for user
        // sessions.
        //
        // @see https://github.com/google/google-api-php-client/issues/821
        return AuthHandlerFactory::build($this->getCache(), $this->config['cache_config']);
    }
    private function createUserRefreshCredentials($scope, $refreshToken)
    {
        $creds = \array_filter(['client_id' => $this->getClientId(), 'client_secret' => $this->getClientSecret(), 'refresh_token' => $refreshToken]);
        return new UserRefreshCredentials($scope, $creds);
    }
    private function checkUniverseDomain($credentials)
    {
        $credentialsUniverse = $credentials instanceof GetUniverseDomainInterface ? $credentials->getUniverseDomain() : GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
        if ($credentialsUniverse !== $this->getUniverseDomain()) {
            throw new DomainException(\sprintf('The configured universe domain (%s) does not match the credential universe domain (%s)', $this->getUniverseDomain(), $credentialsUniverse));
        }
    }
    public function getUniverseDomain()
    {
        return $this->config['universe_domain'];
    }
}
