Index: vendor/google/apiclient/src/AccessToken/Revoke.php
===================================================================
--- vendor/google/apiclient/src/AccessToken/Revoke.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/AccessToken/Revoke.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,81 @@
+<?php
+
+/*
+ * Copyright 2008 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 Google\AccessToken;
+
+use Google\Auth\HttpHandler\HttpHandlerFactory;
+use Google\Client;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Psr7;
+use GuzzleHttp\Psr7\Request;
+
+/**
+ * Wrapper around Google Access Tokens which provides convenience functions
+ *
+ */
+class Revoke
+{
+    /**
+     * @var ClientInterface The http client
+     */
+    private $http;
+
+    /**
+     * Instantiates the class, but does not initiate the login flow, leaving it
+     * to the discretion of the caller.
+     */
+    public function __construct(?ClientInterface $http = null)
+    {
+        $this->http = $http;
+    }
+
+    /**
+     * 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 $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)
+    {
+        if (is_array($token)) {
+            if (isset($token['refresh_token'])) {
+                $token = $token['refresh_token'];
+            } else {
+                $token = $token['access_token'];
+            }
+        }
+
+        $body = Psr7\Utils::streamFor(http_build_query(['token' => $token]));
+        $request = new Request(
+            'POST',
+            Client::OAUTH2_REVOKE_URI,
+            [
+                'Cache-Control' => 'no-store',
+                'Content-Type'  => 'application/x-www-form-urlencoded',
+            ],
+            $body
+        );
+
+        $httpHandler = HttpHandlerFactory::build($this->http);
+
+        $response = $httpHandler($request);
+
+        return $response->getStatusCode() == 200;
+    }
+}
Index: vendor/google/apiclient/src/AccessToken/Verify.php
===================================================================
--- vendor/google/apiclient/src/AccessToken/Verify.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/AccessToken/Verify.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,264 @@
+<?php
+
+/*
+ * Copyright 2008 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 Google\AccessToken;
+
+use DateTime;
+use DomainException;
+use Exception;
+use ExpiredException;
+use Firebase\JWT\ExpiredException as ExpiredExceptionV3;
+use Firebase\JWT\JWT;
+use Firebase\JWT\Key;
+use Firebase\JWT\SignatureInvalidException;
+use Google\Auth\Cache\MemoryCacheItemPool;
+use Google\Exception as GoogleException;
+use GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+use InvalidArgumentException;
+use LogicException;
+use phpseclib3\Crypt\AES;
+use phpseclib3\Crypt\PublicKeyLoader;
+use phpseclib3\Math\BigInteger;
+use Psr\Cache\CacheItemPoolInterface;
+
+/**
+ * Wrapper around Google Access Tokens which provides convenience functions
+ *
+ */
+class Verify
+{
+    const FEDERATED_SIGNON_CERT_URL = 'https://www.googleapis.com/oauth2/v3/certs';
+    const OAUTH2_ISSUER = 'accounts.google.com';
+    const OAUTH2_ISSUER_HTTPS = 'https://accounts.google.com';
+
+    /**
+     * @var ClientInterface The http client
+     */
+    private $http;
+
+    /**
+     * @var CacheItemPoolInterface cache class
+     */
+    private $cache;
+
+    /**
+     * @var \Firebase\JWT\JWT
+     */
+    public $jwt;
+
+    /**
+     * Instantiates the class, but does not initiate the login flow, leaving it
+     * to the discretion of the caller.
+     */
+    public function __construct(
+        ?ClientInterface $http = null,
+        ?CacheItemPoolInterface $cache = null,
+        ?string $jwt = null
+    ) {
+        if (null === $http) {
+            $http = new Client();
+        }
+
+        if (null === $cache) {
+            $cache = new MemoryCacheItemPool();
+        }
+
+        $this->http = $http;
+        $this->cache = $cache;
+        $this->jwt = $jwt ?: $this->getJwtService();
+    }
+
+    /**
+     * Verifies an id token and returns the authenticated apiLoginTicket.
+     * Throws an exception if the id token is not valid.
+     * The audience parameter can be used to control which id tokens are
+     * accepted.  By default, the id token must have been issued to this OAuth2 client.
+     *
+     * @param string $idToken the ID token in JWT format
+     * @param string $audience Optional. The audience to verify against JWt "aud"
+     * @return array|false the token payload, if successful
+     */
+    public function verifyIdToken($idToken, $audience = null)
+    {
+        if (empty($idToken)) {
+            throw new LogicException('id_token cannot be null');
+        }
+
+        // set phpseclib constants if applicable
+        $this->setPhpsecConstants();
+
+        // Check signature
+        $certs = $this->getFederatedSignOnCerts();
+        foreach ($certs as $cert) {
+            try {
+                $args = [$idToken];
+                $publicKey = $this->getPublicKey($cert);
+                if (class_exists(Key::class)) {
+                    $args[] = new Key($publicKey, 'RS256');
+                } else {
+                    $args[] = $publicKey;
+                    $args[] = ['RS256'];
+                }
+                $payload = \call_user_func_array([$this->jwt, 'decode'], $args);
+
+                if (property_exists($payload, 'aud')) {
+                    if ($audience && $payload->aud != $audience) {
+                        return false;
+                    }
+                }
+
+                // support HTTP and HTTPS issuers
+                // @see https://developers.google.com/identity/sign-in/web/backend-auth
+                $issuers = [self::OAUTH2_ISSUER, self::OAUTH2_ISSUER_HTTPS];
+                if (!isset($payload->iss) || !in_array($payload->iss, $issuers)) {
+                    return false;
+                }
+
+                return (array)$payload;
+            } catch (ExpiredException $e) { // @phpstan-ignore-line
+                return false;
+            } catch (ExpiredExceptionV3 $e) {
+                return false;
+            } catch (SignatureInvalidException $e) {
+                // continue
+            } catch (DomainException $e) {
+                // continue
+            }
+        }
+
+        return false;
+    }
+
+    private function getCache()
+    {
+        return $this->cache;
+    }
+
+    /**
+     * Retrieve and cache a certificates file.
+     *
+     * @param string $url location
+     * @return array certificates
+     * @throws \Google\Exception
+     */
+    private function retrieveCertsFromLocation($url)
+    {
+        // If we're retrieving a local file, just grab it.
+        if (0 !== strpos($url, 'http')) {
+            if (!$file = file_get_contents($url)) {
+                throw new GoogleException(
+                    "Failed to retrieve verification certificates: '".
+                    $url."'."
+                );
+            }
+
+            return json_decode($file, true);
+        }
+
+        // @phpstan-ignore-next-line
+        $response = $this->http->get($url);
+
+        if ($response->getStatusCode() == 200) {
+            return json_decode((string)$response->getBody(), true);
+        }
+        throw new GoogleException(
+            sprintf(
+                'Failed to retrieve verification certificates: "%s".',
+                $response->getBody()->getContents()
+            ),
+            $response->getStatusCode()
+        );
+    }
+
+    // Gets federated sign-on certificates to use for verifying identity tokens.
+    // Returns certs as array structure, where keys are key ids, and values
+    // are PEM encoded certificates.
+    private function getFederatedSignOnCerts()
+    {
+        $certs = null;
+        if ($cache = $this->getCache()) {
+            $cacheItem = $cache->getItem('federated_signon_certs_v3');
+            $certs = $cacheItem->get();
+        }
+
+
+        if (!$certs) {
+            $certs = $this->retrieveCertsFromLocation(
+                self::FEDERATED_SIGNON_CERT_URL
+            );
+
+            if ($cache) {
+                $cacheItem->expiresAt(new DateTime('+1 hour'));
+                $cacheItem->set($certs);
+                $cache->save($cacheItem);
+            }
+        }
+
+        if (!isset($certs['keys'])) {
+            throw new InvalidArgumentException(
+                'federated sign-on certs expects "keys" to be set'
+            );
+        }
+
+        return $certs['keys'];
+    }
+
+    private function getJwtService()
+    {
+        $jwt = new JWT();
+        if ($jwt::$leeway < 1) {
+            // Ensures JWT leeway is at least 1
+            // @see https://github.com/google/google-api-php-client/issues/827
+            $jwt::$leeway = 1;
+        }
+
+        return $jwt;
+    }
+
+    private function getPublicKey($cert)
+    {
+        $modulus = new BigInteger($this->jwt->urlsafeB64Decode($cert['n']), 256);
+        $exponent = new BigInteger($this->jwt->urlsafeB64Decode($cert['e']), 256);
+        $component = ['n' => $modulus, 'e' => $exponent];
+
+        $loader = PublicKeyLoader::load($component);
+
+        return $loader->toString('PKCS8');
+    }
+
+    /**
+     * phpseclib calls "phpinfo" by default, which requires special
+     * whitelisting in the AppEngine VM environment. This function
+     * sets constants to bypass the need for phpseclib to check phpinfo
+     *
+     * @see phpseclib/Math/BigInteger
+     * @see https://github.com/GoogleCloudPlatform/getting-started-php/issues/85
+     */
+    private function setPhpsecConstants()
+    {
+        if (filter_var(getenv('GAE_VM'), FILTER_VALIDATE_BOOLEAN)) {
+            if (!defined('MATH_BIGINTEGER_OPENSSL_ENABLED')) {
+                define('MATH_BIGINTEGER_OPENSSL_ENABLED', true);
+            }
+            if (!defined('CRYPT_RSA_MODE')) {
+                define('CRYPT_RSA_MODE', AES::ENGINE_OPENSSL);
+            }
+        }
+    }
+}
Index: vendor/google/apiclient/src/AuthHandler/AuthHandlerFactory.php
===================================================================
--- vendor/google/apiclient/src/AuthHandler/AuthHandlerFactory.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/AuthHandler/AuthHandlerFactory.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright 2015 Google Inc. All Rights Reserved.
+ *
+ * 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 Google\AuthHandler;
+
+use Exception;
+use GuzzleHttp\ClientInterface;
+
+class AuthHandlerFactory
+{
+    /**
+     * Builds out a default http handler for the installed version of guzzle.
+     *
+     * @return Guzzle6AuthHandler|Guzzle7AuthHandler
+     * @throws Exception
+     */
+    public static function build($cache = null, array $cacheConfig = [])
+    {
+        $guzzleVersion = null;
+        if (defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) {
+            $guzzleVersion = ClientInterface::MAJOR_VERSION;
+        } elseif (defined('\GuzzleHttp\ClientInterface::VERSION')) {
+            $guzzleVersion = (int) substr(ClientInterface::VERSION, 0, 1);
+        }
+
+        switch ($guzzleVersion) {
+            case 6:
+                return new Guzzle6AuthHandler($cache, $cacheConfig);
+            case 7:
+                return new Guzzle7AuthHandler($cache, $cacheConfig);
+            default:
+                throw new Exception('Version not supported');
+        }
+    }
+}
Index: vendor/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php
===================================================================
--- vendor/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/AuthHandler/Guzzle6AuthHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,118 @@
+<?php
+
+namespace Google\AuthHandler;
+
+use Google\Auth\CredentialsLoader;
+use Google\Auth\FetchAuthTokenCache;
+use Google\Auth\HttpHandler\HttpHandlerFactory;
+use Google\Auth\Middleware\AuthTokenMiddleware;
+use Google\Auth\Middleware\ScopedAccessTokenMiddleware;
+use Google\Auth\Middleware\SimpleMiddleware;
+use GuzzleHttp\Client;
+use GuzzleHttp\ClientInterface;
+use Psr\Cache\CacheItemPoolInterface;
+
+/**
+ * This supports Guzzle 6
+ */
+class Guzzle6AuthHandler
+{
+    protected $cache;
+    protected $cacheConfig;
+
+    public function __construct(?CacheItemPoolInterface $cache = null, array $cacheConfig = [])
+    {
+        $this->cache = $cache;
+        $this->cacheConfig = $cacheConfig;
+    }
+
+    public function attachCredentials(
+        ClientInterface $http,
+        CredentialsLoader $credentials,
+        ?callable $tokenCallback = null
+    ) {
+        // use the provided cache
+        if ($this->cache) {
+            $credentials = new FetchAuthTokenCache(
+                $credentials,
+                $this->cacheConfig,
+                $this->cache
+            );
+        }
+
+        return $this->attachCredentialsCache($http, $credentials, $tokenCallback);
+    }
+
+    public function attachCredentialsCache(
+        ClientInterface $http,
+        FetchAuthTokenCache $credentials,
+        ?callable $tokenCallback = null
+    ) {
+        // if we end up needing to make an HTTP request to retrieve credentials, we
+        // can use our existing one, but we need to throw exceptions so the error
+        // bubbles up.
+        $authHttp = $this->createAuthHttp($http);
+        $authHttpHandler = HttpHandlerFactory::build($authHttp);
+        $middleware = new AuthTokenMiddleware(
+            $credentials,
+            $authHttpHandler,
+            $tokenCallback
+        );
+
+        $config = $http->getConfig();
+        $config['handler']->remove('google_auth');
+        $config['handler']->push($middleware, 'google_auth');
+        $config['auth'] = 'google_auth';
+        $http = new Client($config);
+
+        return $http;
+    }
+
+    public function attachToken(ClientInterface $http, array $token, array $scopes)
+    {
+        $tokenFunc = function ($scopes) use ($token) {
+            return $token['access_token'];
+        };
+
+        // Derive a cache prefix from the token, to ensure setting a new token
+        // results in a cache-miss.
+        // Note: Supplying a custom "prefix" will bust this behavior.
+        $cacheConfig = $this->cacheConfig;
+        if (!isset($cacheConfig['prefix']) && isset($token['access_token'])) {
+            $cacheConfig['prefix'] = substr(sha1($token['access_token']), -10);
+        }
+
+        $middleware = new ScopedAccessTokenMiddleware(
+            $tokenFunc,
+            $scopes,
+            $cacheConfig,
+            $this->cache
+        );
+
+        $config = $http->getConfig();
+        $config['handler']->remove('google_auth');
+        $config['handler']->push($middleware, 'google_auth');
+        $config['auth'] = 'scoped';
+        $http = new Client($config);
+
+        return $http;
+    }
+
+    public function attachKey(ClientInterface $http, $key)
+    {
+        $middleware = new SimpleMiddleware(['key' => $key]);
+
+        $config = $http->getConfig();
+        $config['handler']->remove('google_auth');
+        $config['handler']->push($middleware, 'google_auth');
+        $config['auth'] = 'simple';
+        $http = new Client($config);
+
+        return $http;
+    }
+
+    private function createAuthHttp(ClientInterface $http)
+    {
+        return new Client(['http_errors' => true] + $http->getConfig());
+    }
+}
Index: vendor/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php
===================================================================
--- vendor/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/AuthHandler/Guzzle7AuthHandler.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,25 @@
+<?php
+/**
+ * Copyright 2020 Google LLC
+ *
+ * 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 Google\AuthHandler;
+
+/**
+ * This supports Guzzle 7
+ */
+class Guzzle7AuthHandler extends Guzzle6AuthHandler
+{
+}
Index: vendor/google/apiclient/src/Client.php
===================================================================
--- vendor/google/apiclient/src/Client.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Client.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,1366 @@
+<?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 Google;
+
+use BadMethodCallException;
+use DomainException;
+use Google\AccessToken\Revoke;
+use Google\AccessToken\Verify;
+use Google\Auth\ApplicationDefaultCredentials;
+use Google\Auth\Cache\MemoryCacheItemPool;
+use Google\Auth\Credentials\ServiceAccountCredentials;
+use Google\Auth\Credentials\UserRefreshCredentials;
+use Google\Auth\CredentialsLoader;
+use Google\Auth\FetchAuthTokenCache;
+use Google\Auth\GetUniverseDomainInterface;
+use Google\Auth\HttpHandler\HttpHandlerFactory;
+use Google\Auth\OAuth2;
+use Google\AuthHandler\AuthHandlerFactory;
+use Google\Http\REST;
+use GuzzleHttp\Client as GuzzleClient;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Ring\Client\StreamHandler;
+use InvalidArgumentException;
+use LogicException;
+use Monolog\Handler\StreamHandler as MonologStreamHandler;
+use Monolog\Handler\SyslogHandler as MonologSyslogHandler;
+use Monolog\Logger;
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+use 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,
+            'logger' => 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']);
+        }
+
+        if (!is_null($this->config['logger'])) {
+            $this->setLogger($this->config['logger']);
+            unset($this->config['logger']);
+        }
+    }
+
+    /**
+     * 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') {
+            // @TODO(v3): Remove this, as it isn't accurate. ADC applies only to determining
+            // credentials based on the user's environment.
+            $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('\GuzzleHttp\ClientInterface::MAJOR_VERSION')) {
+            $guzzleVersion = ClientInterface::MAJOR_VERSION;
+        } elseif (defined('\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'];
+    }
+}
Index: vendor/google/apiclient/src/Collection.php
===================================================================
--- vendor/google/apiclient/src/Collection.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Collection.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,122 @@
+<?php
+
+namespace Google;
+
+/**
+ * Extension to the regular Google\Model that automatically
+ * exposes the items array for iteration, so you can just
+ * iterate over the object rather than a reference inside.
+ */
+class Collection extends Model implements \Iterator, \Countable
+{
+    protected $collection_key = 'items';
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function rewind()
+    {
+        if (
+            isset($this->{$this->collection_key})
+            && is_array($this->{$this->collection_key})
+        ) {
+            reset($this->{$this->collection_key});
+        }
+    }
+
+    /** @return mixed */
+    #[\ReturnTypeWillChange]
+    public function current()
+    {
+        $this->coerceType($this->key());
+        if (is_array($this->{$this->collection_key})) {
+            return current($this->{$this->collection_key});
+        }
+    }
+
+    /** @return mixed */
+    #[\ReturnTypeWillChange]
+    public function key()
+    {
+        if (
+            isset($this->{$this->collection_key})
+            && is_array($this->{$this->collection_key})
+        ) {
+            return key($this->{$this->collection_key});
+        }
+    }
+
+    /** @return mixed */
+    #[\ReturnTypeWillChange]
+    public function next()
+    {
+        return next($this->{$this->collection_key});
+    }
+
+    /** @return bool */
+    #[\ReturnTypeWillChange]
+    public function valid()
+    {
+        $key = $this->key();
+        return $key !== null && $key !== false;
+    }
+
+    /** @return int */
+    #[\ReturnTypeWillChange]
+    public function count()
+    {
+        if (!isset($this->{$this->collection_key})) {
+            return 0;
+        }
+        return count($this->{$this->collection_key});
+    }
+
+    /** @return bool */
+    #[\ReturnTypeWillChange]
+    public function offsetExists($offset)
+    {
+        if (!is_numeric($offset)) {
+            return parent::offsetExists($offset);
+        }
+        return isset($this->{$this->collection_key}[$offset]);
+    }
+
+    /** @return mixed */
+    #[\ReturnTypeWillChange]
+    public function offsetGet($offset)
+    {
+        if (!is_numeric($offset)) {
+            return parent::offsetGet($offset);
+        }
+        $this->coerceType($offset);
+        return $this->{$this->collection_key}[$offset];
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function offsetSet($offset, $value)
+    {
+        if (!is_numeric($offset)) {
+            parent::offsetSet($offset, $value);
+        }
+        $this->{$this->collection_key}[$offset] = $value;
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function offsetUnset($offset)
+    {
+        if (!is_numeric($offset)) {
+            parent::offsetUnset($offset);
+        }
+        unset($this->{$this->collection_key}[$offset]);
+    }
+
+    private function coerceType($offset)
+    {
+        $keyType = $this->keyType($this->collection_key);
+        if ($keyType && !is_object($this->{$this->collection_key}[$offset])) {
+            $this->{$this->collection_key}[$offset] =
+                new $keyType($this->{$this->collection_key}[$offset]);
+        }
+    }
+}
Index: vendor/google/apiclient/src/Exception.php
===================================================================
--- vendor/google/apiclient/src/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,24 @@
+<?php
+/*
+ * Copyright 2013 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 Google;
+
+use Exception as BaseException;
+
+class Exception extends BaseException
+{
+}
Index: vendor/google/apiclient/src/Http/Batch.php
===================================================================
--- vendor/google/apiclient/src/Http/Batch.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Http/Batch.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,261 @@
+<?php
+/*
+ * Copyright 2012 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 Google\Http;
+
+use Google\Client;
+use Google\Service\Exception as GoogleServiceException;
+use GuzzleHttp\Psr7;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * Class to handle batched requests to the Google API service.
+ *
+ * Note that calls to `Google\Http\Batch::execute()` do not clear the queued
+ * requests. To start a new batch, be sure to create a new instance of this
+ * class.
+ */
+class Batch
+{
+    const BATCH_PATH = 'batch';
+
+    private static $CONNECTION_ESTABLISHED_HEADERS = [
+        "HTTP/1.0 200 Connection established\r\n\r\n",
+        "HTTP/1.1 200 Connection established\r\n\r\n",
+    ];
+
+    /** @var string Multipart Boundary. */
+    private $boundary;
+
+    /** @var array service requests to be executed. */
+    private $requests = [];
+
+    /** @var Client */
+    private $client;
+
+    private $rootUrl;
+
+    private $batchPath;
+
+    public function __construct(
+        Client $client,
+        $boundary = false,
+        $rootUrl = null,
+        $batchPath = null
+    ) {
+        $this->client = $client;
+        $this->boundary = $boundary ?: mt_rand();
+        $rootUrl = rtrim($rootUrl ?: $this->client->getConfig('base_path'), '/');
+        $this->rootUrl = str_replace(
+            'UNIVERSE_DOMAIN',
+            $this->client->getUniverseDomain(),
+            $rootUrl
+        );
+        $this->batchPath = $batchPath ?: self::BATCH_PATH;
+    }
+
+    public function add(RequestInterface $request, $key = false)
+    {
+        if (false == $key) {
+            $key = mt_rand();
+        }
+
+        $this->requests[$key] = $request;
+    }
+
+    public function execute()
+    {
+        $body = '';
+        $classes = [];
+        $batchHttpTemplate = <<<EOF
+--%s
+Content-Type: application/http
+Content-Transfer-Encoding: binary
+MIME-Version: 1.0
+Content-ID: %s
+
+%s
+%s%s
+
+
+EOF;
+
+        /** @var RequestInterface $request */
+        foreach ($this->requests as $key => $request) {
+            $firstLine = sprintf(
+                '%s %s HTTP/%s',
+                $request->getMethod(),
+                $request->getRequestTarget(),
+                $request->getProtocolVersion()
+            );
+
+            $content = (string) $request->getBody();
+
+            $headers = '';
+            foreach ($request->getHeaders() as $name => $values) {
+                $headers .= sprintf("%s:%s\r\n", $name, implode(', ', $values));
+            }
+
+            $body .= sprintf(
+                $batchHttpTemplate,
+                $this->boundary,
+                $key,
+                $firstLine,
+                $headers,
+                $content ? "\n" . $content : ''
+            );
+
+            $classes['response-' . $key] = $request->getHeaderLine('X-Php-Expected-Class');
+        }
+
+        $body .= "--{$this->boundary}--";
+        $body = trim($body);
+        $url = $this->rootUrl . '/' . $this->batchPath;
+        $headers = [
+            'Content-Type' => sprintf('multipart/mixed; boundary=%s', $this->boundary),
+            'Content-Length' => (string) strlen($body),
+        ];
+
+        $request = new Request(
+            'POST',
+            $url,
+            $headers,
+            $body
+        );
+
+        $response = $this->client->execute($request);
+
+        return $this->parseResponse($response, $classes);
+    }
+
+    public function parseResponse(ResponseInterface $response, $classes = [])
+    {
+        $contentType = $response->getHeaderLine('content-type');
+        $contentType = explode(';', $contentType);
+        $boundary = false;
+        foreach ($contentType as $part) {
+            $part = explode('=', $part, 2);
+            if (isset($part[0]) && 'boundary' == trim($part[0])) {
+                $boundary = $part[1];
+            }
+        }
+
+        $body = (string) $response->getBody();
+        if (!empty($body)) {
+            $body = str_replace("--$boundary--", "--$boundary", $body);
+            $parts = explode("--$boundary", $body);
+            $responses = [];
+            $requests = array_values($this->requests);
+
+            foreach ($parts as $i => $part) {
+                $part = trim($part);
+                if (!empty($part)) {
+                    list($rawHeaders, $part) = explode("\r\n\r\n", $part, 2);
+                    $headers = $this->parseRawHeaders($rawHeaders);
+
+                    $status = substr($part, 0, strpos($part, "\n"));
+                    $status = explode(" ", $status);
+                    $status = $status[1];
+
+                    list($partHeaders, $partBody) = $this->parseHttpResponse($part, 0);
+                    $response = new Response(
+                        (int) $status,
+                        $partHeaders,
+                        Psr7\Utils::streamFor($partBody)
+                    );
+
+                    // Need content id.
+                    $key = $headers['content-id'];
+
+                    try {
+                        $response = REST::decodeHttpResponse($response, $requests[$i-1]);
+                    } catch (GoogleServiceException $e) {
+                        // Store the exception as the response, so successful responses
+                        // can be processed.
+                        $response = $e;
+                    }
+
+                    $responses[$key] = $response;
+                }
+            }
+
+            return $responses;
+        }
+
+        return null;
+    }
+
+    private function parseRawHeaders($rawHeaders)
+    {
+        $headers = [];
+        $responseHeaderLines = explode("\r\n", $rawHeaders);
+        foreach ($responseHeaderLines as $headerLine) {
+            if ($headerLine && strpos($headerLine, ':') !== false) {
+                list($header, $value) = explode(': ', $headerLine, 2);
+                $header = strtolower($header);
+                if (isset($headers[$header])) {
+                    $headers[$header] = array_merge((array)$headers[$header], (array)$value);
+                } else {
+                    $headers[$header] = $value;
+                }
+            }
+        }
+        return $headers;
+    }
+
+    /**
+     * Used by the IO lib and also the batch processing.
+     *
+     * @param string $respData
+     * @param int $headerSize
+     * @return array
+     */
+    private function parseHttpResponse($respData, $headerSize)
+    {
+        // check proxy header
+        foreach (self::$CONNECTION_ESTABLISHED_HEADERS as $established_header) {
+            if (stripos($respData, $established_header) !== false) {
+                // existed, remove it
+                $respData = str_ireplace($established_header, '', $respData);
+                // Subtract the proxy header size unless the cURL bug prior to 7.30.0
+                // is present which prevented the proxy header size from being taken into
+                // account.
+                // @TODO look into this
+                // if (!$this->needsQuirk()) {
+                //   $headerSize -= strlen($established_header);
+                // }
+                break;
+            }
+        }
+
+        if ($headerSize) {
+            $responseBody = substr($respData, $headerSize);
+            $responseHeaders = substr($respData, 0, $headerSize);
+        } else {
+            $responseSegments = explode("\r\n\r\n", $respData, 2);
+            $responseHeaders = $responseSegments[0];
+            $responseBody = isset($responseSegments[1]) ? $responseSegments[1] : null;
+        }
+
+        $responseHeaders = $this->parseRawHeaders($responseHeaders);
+
+        return [$responseHeaders, $responseBody];
+    }
+}
Index: vendor/google/apiclient/src/Http/MediaFileUpload.php
===================================================================
--- vendor/google/apiclient/src/Http/MediaFileUpload.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Http/MediaFileUpload.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,353 @@
+<?php
+/**
+ * Copyright 2012 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 Google\Http;
+
+use Google\Client;
+use Google\Exception as GoogleException;
+use GuzzleHttp\Psr7;
+use GuzzleHttp\Psr7\Request;
+use GuzzleHttp\Psr7\Uri;
+use Psr\Http\Message\RequestInterface;
+
+/**
+ * Manage large file uploads, which may be media but can be any type
+ * of sizable data.
+ */
+class MediaFileUpload
+{
+    const UPLOAD_MEDIA_TYPE = 'media';
+    const UPLOAD_MULTIPART_TYPE = 'multipart';
+    const UPLOAD_RESUMABLE_TYPE = 'resumable';
+
+    /** @var string $mimeType */
+    private $mimeType;
+
+    /** @var string $data */
+    private $data;
+
+    /** @var bool $resumable */
+    private $resumable;
+
+    /** @var int $chunkSize */
+    private $chunkSize;
+
+    /** @var int $size */
+    private $size;
+
+    /** @var string $resumeUri */
+    private $resumeUri;
+
+    /** @var int $progress */
+    private $progress;
+
+    /** @var Client */
+    private $client;
+
+    /** @var RequestInterface */
+    private $request;
+
+    /** @var string */
+    private $boundary; // @phpstan-ignore-line
+
+    /**
+   * Result code from last HTTP call
+   * @var int
+   */
+    private $httpResultCode;
+
+    /**
+     * @param Client $client
+     * @param RequestInterface $request
+     * @param string $mimeType
+     * @param string $data The bytes you want to upload.
+     * @param bool $resumable
+     * @param int $chunkSize File will be uploaded in chunks of this many bytes.
+     * only used if resumable=True
+     */
+    public function __construct(
+        Client $client,
+        RequestInterface $request,
+        $mimeType,
+        $data,
+        $resumable = false,
+        $chunkSize = 0
+    ) {
+        $this->client = $client;
+        $this->request = $request;
+        $this->mimeType = $mimeType;
+        $this->data = $data;
+        $this->resumable = $resumable;
+        $this->chunkSize = $chunkSize;
+        $this->progress = 0;
+
+        $this->process();
+    }
+
+    /**
+     * Set the size of the file that is being uploaded.
+     * @param int $size - int file size in bytes
+     */
+    public function setFileSize($size)
+    {
+        $this->size = $size;
+    }
+
+    /**
+     * Return the progress on the upload
+     * @return int progress in bytes uploaded.
+     */
+    public function getProgress()
+    {
+        return $this->progress;
+    }
+
+    /**
+     * Send the next part of the file to upload.
+     * @param string|bool $chunk Optional. The next set of bytes to send. If false will
+     * use $data passed at construct time.
+     */
+    public function nextChunk($chunk = false)
+    {
+        $resumeUri = $this->getResumeUri();
+
+        if (false == $chunk) {
+            $chunk = substr($this->data, $this->progress, $this->chunkSize);
+        }
+
+        $lastBytePos = $this->progress + strlen($chunk) - 1;
+        $headers = [
+            'content-range' => "bytes $this->progress-$lastBytePos/$this->size",
+            'content-length' => (string) strlen($chunk),
+            'expect' => '',
+        ];
+
+        $request = new Request(
+            'PUT',
+            $resumeUri,
+            $headers,
+            Psr7\Utils::streamFor($chunk)
+        );
+
+        return $this->makePutRequest($request);
+    }
+
+    /**
+     * Return the HTTP result code from the last call made.
+     * @return int code
+     */
+    public function getHttpResultCode()
+    {
+        return $this->httpResultCode;
+    }
+
+    /**
+     * Sends a PUT-Request to google drive and parses the response,
+     * setting the appropiate variables from the response()
+     *
+     * @param RequestInterface $request the Request which will be send
+     *
+     * @return false|mixed false when the upload is unfinished or the decoded http response
+     *
+     */
+    private function makePutRequest(RequestInterface $request)
+    {
+        $response = $this->client->execute($request);
+        $this->httpResultCode = $response->getStatusCode();
+
+        if (308 == $this->httpResultCode) {
+            // Track the amount uploaded.
+            $range = $response->getHeaderLine('range');
+            if ($range) {
+                $range_array = explode('-', $range);
+                $this->progress = ((int) $range_array[1]) + 1;
+            }
+
+            // Allow for changing upload URLs.
+            $location = $response->getHeaderLine('location');
+            if ($location) {
+                $this->resumeUri = $location;
+            }
+
+            // No problems, but upload not complete.
+            return false;
+        }
+
+        return REST::decodeHttpResponse($response, $this->request);
+    }
+
+    /**
+     * Resume a previously unfinished upload
+     * @param string $resumeUri the resume-URI of the unfinished, resumable upload.
+     */
+    public function resume($resumeUri)
+    {
+        $this->resumeUri = $resumeUri;
+        $headers = [
+            'content-range' => "bytes */$this->size",
+            'content-length' => '0',
+        ];
+        $httpRequest = new Request(
+            'PUT',
+            $this->resumeUri,
+            $headers
+        );
+        return $this->makePutRequest($httpRequest);
+    }
+
+    /**
+     * @return RequestInterface
+     * @visible for testing
+     */
+    private function process()
+    {
+        $this->transformToUploadUrl();
+        $request = $this->request;
+
+        $postBody = '';
+        $contentType = false;
+
+        $meta = json_decode((string) $request->getBody(), true);
+
+        $uploadType = $this->getUploadType($meta);
+        $request = $request->withUri(
+            Uri::withQueryValue($request->getUri(), 'uploadType', $uploadType)
+        );
+
+        $mimeType = $this->mimeType ?: $request->getHeaderLine('content-type');
+
+        if (self::UPLOAD_RESUMABLE_TYPE == $uploadType) {
+            $contentType = $mimeType;
+            $postBody = is_string($meta) ? $meta : json_encode($meta);
+        } elseif (self::UPLOAD_MEDIA_TYPE == $uploadType) {
+            $contentType = $mimeType;
+            $postBody = $this->data;
+        } elseif (self::UPLOAD_MULTIPART_TYPE == $uploadType) {
+            // This is a multipart/related upload.
+            $boundary = $this->boundary ?: mt_rand();
+            $boundary = str_replace('"', '', $boundary);
+            $contentType = 'multipart/related; boundary=' . $boundary;
+            $related = "--$boundary\r\n";
+            $related .= "Content-Type: application/json; charset=UTF-8\r\n";
+            $related .= "\r\n" . json_encode($meta) . "\r\n";
+            $related .= "--$boundary\r\n";
+            $related .= "Content-Type: $mimeType\r\n";
+            $related .= "Content-Transfer-Encoding: base64\r\n";
+            $related .= "\r\n" . base64_encode($this->data) . "\r\n";
+            $related .= "--$boundary--";
+            $postBody = $related;
+        }
+
+        $request = $request->withBody(Psr7\Utils::streamFor($postBody));
+
+        if ($contentType) {
+            $request = $request->withHeader('content-type', $contentType);
+        }
+
+        return $this->request = $request;
+    }
+
+    /**
+     * Valid upload types:
+     * - resumable (UPLOAD_RESUMABLE_TYPE)
+     * - media (UPLOAD_MEDIA_TYPE)
+     * - multipart (UPLOAD_MULTIPART_TYPE)
+     * @param string|false $meta
+     * @return string
+     * @visible for testing
+     */
+    public function getUploadType($meta)
+    {
+        if ($this->resumable) {
+            return self::UPLOAD_RESUMABLE_TYPE;
+        }
+
+        if (false == $meta && $this->data) {
+            return self::UPLOAD_MEDIA_TYPE;
+        }
+
+        return self::UPLOAD_MULTIPART_TYPE;
+    }
+
+    public function getResumeUri()
+    {
+        if (null === $this->resumeUri) {
+            $this->resumeUri = $this->fetchResumeUri();
+        }
+
+        return $this->resumeUri;
+    }
+
+    private function fetchResumeUri()
+    {
+        $body = $this->request->getBody();
+        $headers = [
+            'content-type' => 'application/json; charset=UTF-8',
+            'content-length' => $body->getSize(),
+            'x-upload-content-type' => $this->mimeType,
+            'x-upload-content-length' => $this->size,
+            'expect' => '',
+        ];
+        foreach ($headers as $key => $value) {
+            $this->request = $this->request->withHeader($key, $value);
+        }
+
+        $response = $this->client->execute($this->request, false);
+        $location = $response->getHeaderLine('location');
+        $code = $response->getStatusCode();
+
+        if (200 == $code && true == $location) {
+            return $location;
+        }
+
+        $message = $code;
+        $body = json_decode((string) $this->request->getBody(), true);
+        if (isset($body['error']['errors'])) {
+            $message .= ': ';
+            foreach ($body['error']['errors'] as $error) {
+                $message .= "{$error['domain']}, {$error['message']};";
+            }
+            $message = rtrim($message, ';');
+        }
+
+        $error = "Failed to start the resumable upload (HTTP {$message})";
+        $this->client->getLogger()->error($error);
+
+        throw new GoogleException($error);
+    }
+
+    private function transformToUploadUrl()
+    {
+        $parts = parse_url((string) $this->request->getUri());
+        if (!isset($parts['path'])) {
+            $parts['path'] = '';
+        }
+        $parts['path'] = '/upload' . $parts['path'];
+        $uri = Uri::fromParts($parts);
+        $this->request = $this->request->withUri($uri);
+    }
+
+    public function setChunkSize($chunkSize)
+    {
+        $this->chunkSize = $chunkSize;
+    }
+
+    public function getRequest()
+    {
+        return $this->request;
+    }
+}
Index: vendor/google/apiclient/src/Http/REST.php
===================================================================
--- vendor/google/apiclient/src/Http/REST.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Http/REST.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,198 @@
+<?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 Google\Http;
+
+use Google\Auth\HttpHandler\HttpHandlerFactory;
+use Google\Service\Exception as GoogleServiceException;
+use Google\Task\Runner;
+use GuzzleHttp\ClientInterface;
+use GuzzleHttp\Exception\RequestException;
+use GuzzleHttp\Psr7\Response;
+use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
+
+/**
+ * This class implements the RESTful transport of apiServiceRequest()'s
+ */
+class REST
+{
+    /**
+     * Executes a Psr\Http\Message\RequestInterface and (if applicable) automatically retries
+     * when errors occur.
+     *
+     * @template T
+     * @param ClientInterface $client
+     * @param RequestInterface $request
+     * @param class-string<T>|false|null $expectedClass
+     * @param array $config
+     * @param array $retryMap
+     * @return mixed|T|null
+     * @throws \Google\Service\Exception on server side error (ie: not authenticated,
+     *  invalid or malformed post body, invalid url)
+     */
+    public static function execute(
+        ClientInterface $client,
+        RequestInterface $request,
+        $expectedClass = null,
+        $config = [],
+        $retryMap = null
+    ) {
+        $runner = new Runner(
+            $config,
+            sprintf('%s %s', $request->getMethod(), (string)$request->getUri()),
+            [self::class, 'doExecute'],
+            [$client, $request, $expectedClass]
+        );
+
+        if (null !== $retryMap) {
+            $runner->setRetryMap($retryMap);
+        }
+
+        return $runner->run();
+    }
+
+    /**
+     * Executes a Psr\Http\Message\RequestInterface
+     *
+     * @template T
+     * @param ClientInterface $client
+     * @param RequestInterface $request
+     * @param class-string<T>|false|null $expectedClass
+     * @return mixed|T|null
+     * @throws \Google\Service\Exception on server side error (ie: not authenticated,
+     *  invalid or malformed post body, invalid url)
+     */
+    public static function doExecute(ClientInterface $client, RequestInterface $request, $expectedClass = null)
+    {
+        try {
+            $httpHandler = HttpHandlerFactory::build($client);
+            $response = $httpHandler($request);
+        } catch (RequestException $e) {
+            // if Guzzle throws an exception, catch it and handle the response
+            if (!$e->hasResponse()) {
+                throw $e;
+            }
+
+            $response = $e->getResponse();
+            // specific checking for Guzzle 5: convert to PSR7 response
+            if (
+                interface_exists('\GuzzleHttp\Message\ResponseInterface')
+                && $response instanceof \GuzzleHttp\Message\ResponseInterface
+            ) {
+                $response = new Response(
+                    $response->getStatusCode(),
+                    $response->getHeaders() ?: [],
+                    $response->getBody(),
+                    $response->getProtocolVersion(),
+                    $response->getReasonPhrase()
+                );
+            }
+        }
+
+        return self::decodeHttpResponse($response, $request, $expectedClass);
+    }
+
+    /**
+     * Decode an HTTP Response.
+     * @static
+     *
+     * @template T
+     * @param RequestInterface $response The http response to be decoded.
+     * @param ResponseInterface $response
+     * @param class-string<T>|false|null $expectedClass
+     * @return mixed|T|null
+     * @throws \Google\Service\Exception
+     */
+    public static function decodeHttpResponse(
+        ResponseInterface $response,
+        ?RequestInterface $request = null,
+        $expectedClass = null
+    ) {
+        $code = $response->getStatusCode();
+
+        // retry strategy
+        if (intVal($code) >= 400) {
+            // if we errored out, it should be safe to grab the response body
+            $body = (string)$response->getBody();
+
+            // Check if we received errors, and add those to the Exception for convenience
+            throw new GoogleServiceException($body, $code, null, self::getResponseErrors($body));
+        }
+
+        // Ensure we only pull the entire body into memory if the request is not
+        // of media type
+        $body = self::decodeBody($response, $request);
+
+        if ($expectedClass = self::determineExpectedClass($expectedClass, $request)) {
+            $json = json_decode($body, true);
+
+            return new $expectedClass($json);
+        }
+
+        return $response;
+    }
+
+    private static function decodeBody(ResponseInterface $response, ?RequestInterface $request = null)
+    {
+        if (self::isAltMedia($request)) {
+            // don't decode the body, it's probably a really long string
+            return '';
+        }
+
+        return (string)$response->getBody();
+    }
+
+    private static function determineExpectedClass($expectedClass, ?RequestInterface $request = null)
+    {
+        // "false" is used to explicitly prevent an expected class from being returned
+        if (false === $expectedClass) {
+            return null;
+        }
+
+        // if we don't have a request, we just use what's passed in
+        if (null === $request) {
+            return $expectedClass;
+        }
+
+        // return what we have in the request header if one was not supplied
+        return $expectedClass ?: $request->getHeaderLine('X-Php-Expected-Class');
+    }
+
+    private static function getResponseErrors($body)
+    {
+        $json = json_decode($body, true);
+
+        if (isset($json['error']['errors'])) {
+            return $json['error']['errors'];
+        }
+
+        return null;
+    }
+
+    private static function isAltMedia(?RequestInterface $request = null)
+    {
+        if ($request && $qs = $request->getUri()->getQuery()) {
+            parse_str($qs, $query);
+            if (isset($query['alt']) && $query['alt'] == 'media') {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}
Index: vendor/google/apiclient/src/Model.php
===================================================================
--- vendor/google/apiclient/src/Model.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Model.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,333 @@
+<?php
+/*
+ * Copyright 2011 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 Google;
+
+use Google\Exception as GoogleException;
+use ReflectionObject;
+use ReflectionProperty;
+use stdClass;
+
+/**
+ * This class defines attributes, valid values, and usage which is generated
+ * from a given json schema.
+ * http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5
+ *
+ */
+#[\AllowDynamicProperties]
+class Model implements \ArrayAccess
+{
+    /**
+     * If you need to specify a NULL JSON value, use Google\Model::NULL_VALUE
+     * instead - it will be replaced when converting to JSON with a real null.
+     */
+    const NULL_VALUE = "{}gapi-php-null";
+    protected $internal_gapi_mappings = [];
+    protected $modelData = [];
+    protected $processed = [];
+
+    /**
+     * Polymorphic - accepts a variable number of arguments dependent
+     * on the type of the model subclass.
+     */
+    final public function __construct()
+    {
+        if (func_num_args() == 1 && is_array(func_get_arg(0))) {
+            // Initialize the model with the array's contents.
+            $array = func_get_arg(0);
+            $this->mapTypes($array);
+        }
+        $this->gapiInit();
+    }
+
+    /**
+     * Getter that handles passthrough access to the data array, and lazy object creation.
+     * @param string $key Property name.
+     * @return mixed The value if any, or null.
+     */
+    public function __get($key)
+    {
+        $keyType = $this->keyType($key);
+        $keyDataType = $this->dataType($key);
+        if ($keyType && !isset($this->processed[$key])) {
+            if (isset($this->modelData[$key])) {
+                $val = $this->modelData[$key];
+            } elseif ($keyDataType == 'array' || $keyDataType == 'map') {
+                $val = [];
+            } else {
+                $val = null;
+            }
+
+            if ($this->isAssociativeArray($val)) {
+                if ($keyDataType && 'map' == $keyDataType) {
+                    foreach ($val as $arrayKey => $arrayItem) {
+                        $this->modelData[$key][$arrayKey] =
+                            new $keyType($arrayItem);
+                    }
+                } else {
+                    $this->modelData[$key] = new $keyType($val);
+                }
+            } elseif (is_array($val)) {
+                $arrayObject = [];
+                foreach ($val as $arrayIndex => $arrayItem) {
+                    $arrayObject[$arrayIndex] = new $keyType($arrayItem);
+                }
+                $this->modelData[$key] = $arrayObject;
+            }
+            $this->processed[$key] = true;
+        }
+
+        return isset($this->modelData[$key]) ? $this->modelData[$key] : null;
+    }
+
+    /**
+     * Initialize this object's properties from an array.
+     *
+     * @param array $array Used to seed this object's properties.
+     * @return void
+     */
+    protected function mapTypes($array)
+    {
+        // Hard initialise simple types, lazy load more complex ones.
+        foreach ($array as $key => $val) {
+            if ($keyType = $this->keyType($key)) {
+                $dataType = $this->dataType($key);
+                if ($dataType == 'array' || $dataType == 'map') {
+                    $this->$key = [];
+                    foreach ($val as $itemKey => $itemVal) {
+                        if ($itemVal instanceof $keyType) {
+                            $this->{$key}[$itemKey] = $itemVal;
+                        } else {
+                            $this->{$key}[$itemKey] = new $keyType($itemVal);
+                        }
+                    }
+                } elseif ($val instanceof $keyType) {
+                    $this->$key = $val;
+                } else {
+                    $this->$key = new $keyType($val);
+                }
+                unset($array[$key]);
+            } elseif (property_exists($this, $key)) {
+                $this->$key = $val;
+                unset($array[$key]);
+            } elseif (property_exists($this, $camelKey = $this->camelCase($key))) {
+                // This checks if property exists as camelCase, leaving it in array as snake_case
+                // in case of backwards compatibility issues.
+                $this->$camelKey = $val;
+            }
+        }
+        $this->modelData = $array;
+    }
+
+    /**
+     * Blank initialiser to be used in subclasses to do  post-construction initialisation - this
+     * avoids the need for subclasses to have to implement the variadics handling in their
+     * constructors.
+     */
+    protected function gapiInit()
+    {
+        return;
+    }
+
+    /**
+     * Create a simplified object suitable for straightforward
+     * conversion to JSON. This is relatively expensive
+     * due to the usage of reflection, but shouldn't be called
+     * a whole lot, and is the most straightforward way to filter.
+     */
+    public function toSimpleObject()
+    {
+        $object = new stdClass();
+
+        // Process all other data.
+        foreach ($this->modelData as $key => $val) {
+            $result = $this->getSimpleValue($val);
+            if ($result !== null) {
+                $object->$key = $this->nullPlaceholderCheck($result);
+            }
+        }
+
+        // Process all public properties.
+        $reflect = new ReflectionObject($this);
+        $props = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
+        foreach ($props as $member) {
+            $name = $member->getName();
+            $result = $this->getSimpleValue($this->$name);
+            if ($result !== null) {
+                $name = $this->getMappedName($name);
+                $object->$name = $this->nullPlaceholderCheck($result);
+            }
+        }
+
+        return $object;
+    }
+
+    /**
+     * Handle different types of values, primarily
+     * other objects and map and array data types.
+     */
+    private function getSimpleValue($value)
+    {
+        if ($value instanceof Model) {
+            return $value->toSimpleObject();
+        } elseif (is_array($value)) {
+            $return = [];
+            foreach ($value as $key => $a_value) {
+                $a_value = $this->getSimpleValue($a_value);
+                if ($a_value !== null) {
+                    $key = $this->getMappedName($key);
+                    $return[$key] = $this->nullPlaceholderCheck($a_value);
+                }
+            }
+            return $return;
+        }
+        return $value;
+    }
+
+    /**
+     * Check whether the value is the null placeholder and return true null.
+     */
+    private function nullPlaceholderCheck($value)
+    {
+        if ($value === self::NULL_VALUE) {
+            return null;
+        }
+        return $value;
+    }
+
+    /**
+     * If there is an internal name mapping, use that.
+     */
+    private function getMappedName($key)
+    {
+        if (isset($this->internal_gapi_mappings, $this->internal_gapi_mappings[$key])) {
+            $key = $this->internal_gapi_mappings[$key];
+        }
+        return $key;
+    }
+
+    /**
+     * Returns true only if the array is associative.
+     * @param array $array
+     * @return bool True if the array is associative.
+     */
+    protected function isAssociativeArray($array)
+    {
+        if (!is_array($array)) {
+            return false;
+        }
+        $keys = array_keys($array);
+        foreach ($keys as $key) {
+            if (is_string($key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Verify if $obj is an array.
+     * @throws \Google\Exception Thrown if $obj isn't an array.
+     * @param array $obj Items that should be validated.
+     * @param string $method Method expecting an array as an argument.
+     */
+    public function assertIsArray($obj, $method)
+    {
+        if ($obj && !is_array($obj)) {
+            throw new GoogleException(
+                "Incorrect parameter type passed to $method(). Expected an array."
+            );
+        }
+    }
+
+    /** @return bool */
+    #[\ReturnTypeWillChange]
+    public function offsetExists($offset)
+    {
+        return isset($this->$offset) || isset($this->modelData[$offset]);
+    }
+
+    /** @return mixed */
+    #[\ReturnTypeWillChange]
+    public function offsetGet($offset)
+    {
+        return isset($this->$offset) ?
+        $this->$offset :
+        $this->__get($offset);
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function offsetSet($offset, $value)
+    {
+        if (property_exists($this, $offset)) {
+            $this->$offset = $value;
+        } else {
+            $this->modelData[$offset] = $value;
+            $this->processed[$offset] = true;
+        }
+    }
+
+    /** @return void */
+    #[\ReturnTypeWillChange]
+    public function offsetUnset($offset)
+    {
+        unset($this->modelData[$offset]);
+    }
+
+    protected function keyType($key)
+    {
+        $keyType = $key . "Type";
+
+        // ensure keyType is a valid class
+        if (property_exists($this, $keyType) && $this->$keyType !== null && class_exists($this->$keyType)) {
+            return $this->$keyType;
+        }
+    }
+
+    protected function dataType($key)
+    {
+        $dataType = $key . "DataType";
+
+        if (property_exists($this, $dataType)) {
+            return $this->$dataType;
+        }
+    }
+
+    public function __isset($key)
+    {
+        return isset($this->modelData[$key]);
+    }
+
+    public function __unset($key)
+    {
+        unset($this->modelData[$key]);
+    }
+
+    /**
+   * Convert a string to camelCase
+   * @param  string $value
+   * @return string
+   */
+    private function camelCase($value)
+    {
+        $value = ucwords(str_replace(['-', '_'], ' ', $value));
+        $value = str_replace(' ', '', $value);
+        $value[0] = strtolower($value[0]);
+        return $value;
+    }
+}
Index: vendor/google/apiclient/src/Service.php
===================================================================
--- vendor/google/apiclient/src/Service.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Service.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,76 @@
+<?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 Google;
+
+use Google\Http\Batch;
+use TypeError;
+
+class Service
+{
+    public $batchPath;
+    /**
+     * Only used in getBatch
+     */
+    public $rootUrl;
+    public $rootUrlTemplate;
+    public $version;
+    public $servicePath;
+    public $serviceName;
+    public $availableScopes;
+    public $resource;
+    private $client;
+
+    public function __construct($clientOrConfig = [])
+    {
+        if ($clientOrConfig instanceof Client) {
+            $this->client = $clientOrConfig;
+        } elseif (is_array($clientOrConfig)) {
+            $this->client = new Client($clientOrConfig ?: []);
+        } else {
+            $errorMessage = 'constructor must be array or instance of Google\Client';
+            if (class_exists('TypeError')) {
+                throw new TypeError($errorMessage);
+            }
+            trigger_error($errorMessage, E_USER_ERROR);
+        }
+    }
+
+    /**
+   * Return the associated Google\Client class.
+   * @return \Google\Client
+   */
+    public function getClient()
+    {
+        return $this->client;
+    }
+
+    /**
+   * Create a new HTTP Batch handler for this service
+   *
+   * @return Batch
+   */
+    public function createBatch()
+    {
+        return new Batch(
+            $this->client,
+            false,
+            $this->rootUrlTemplate ?? $this->rootUrl,
+            $this->batchPath
+        );
+    }
+}
Index: vendor/google/apiclient/src/Service/Exception.php
===================================================================
--- vendor/google/apiclient/src/Service/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Service/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,73 @@
+<?php
+/*
+ * Copyright 2014 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 Google\Service;
+
+use Google\Exception as GoogleException;
+
+class Exception extends GoogleException
+{
+    /**
+     * Optional list of errors returned in a JSON body of an HTTP error response.
+     */
+    protected $errors = [];
+
+    /**
+     * Override default constructor to add the ability to set $errors and a retry
+     * map.
+     *
+     * @param string $message
+     * @param int $code
+     * @param Exception|null $previous
+     * @param array<array<string,string>>|null $errors List of errors returned in an HTTP
+     * response or null.  Defaults to [].
+     */
+    public function __construct(
+        $message,
+        $code = 0,
+        ?Exception $previous = null,
+        $errors = []
+    ) {
+        if (version_compare(PHP_VERSION, '5.3.0') >= 0) {
+            parent::__construct($message, $code, $previous);
+        } else {
+            parent::__construct($message, $code);
+        }
+
+        $this->errors = $errors;
+    }
+
+    /**
+     * An example of the possible errors returned.
+     *
+     * [
+     *   {
+     *     "domain": "global",
+     *     "reason": "authError",
+     *     "message": "Invalid Credentials",
+     *     "locationType": "header",
+     *     "location": "Authorization",
+     *   }
+     * ]
+     *
+     * @return array<array<string,string>>|null List of errors returned in an HTTP response or null.
+     */
+    public function getErrors()
+    {
+        return $this->errors;
+    }
+}
Index: vendor/google/apiclient/src/Service/README.md
===================================================================
--- vendor/google/apiclient/src/Service/README.md	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Service/README.md	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,5 @@
+# Google API Client Services
+
+Google API Client Service classes have been moved to the 
+[google-api-php-client-services](https://github.com/google/google-api-php-client-services) 
+repository. 
Index: vendor/google/apiclient/src/Service/Resource.php
===================================================================
--- vendor/google/apiclient/src/Service/Resource.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Service/Resource.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,320 @@
+<?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 Google\Service;
+
+use Google\Exception as GoogleException;
+use Google\Http\MediaFileUpload;
+use Google\Model;
+use Google\Utils\UriTemplate;
+use GuzzleHttp\Psr7\Request;
+
+/**
+ * Implements the actual methods/resources of the discovered Google API using magic function
+ * calling overloading (__call()), which on call will see if the method name (plus.activities.list)
+ * is available in this service, and if so construct an apiHttpRequest representing it.
+ *
+ */
+class Resource
+{
+    // Valid query parameters that work, but don't appear in discovery.
+    private $stackParameters = [
+        'alt' => ['type' => 'string', 'location' => 'query'],
+        'fields' => ['type' => 'string', 'location' => 'query'],
+        'trace' => ['type' => 'string', 'location' => 'query'],
+        'userIp' => ['type' => 'string', 'location' => 'query'],
+        'quotaUser' => ['type' => 'string', 'location' => 'query'],
+        'data' => ['type' => 'string', 'location' => 'body'],
+        'mimeType' => ['type' => 'string', 'location' => 'header'],
+        'uploadType' => ['type' => 'string', 'location' => 'query'],
+        'mediaUpload' => ['type' => 'complex', 'location' => 'query'],
+        'prettyPrint' => ['type' => 'string', 'location' => 'query'],
+    ];
+
+    /** @var string $rootUrlTemplate */
+    private $rootUrlTemplate;
+
+    /** @var string $apiVersion */
+    protected $apiVersion;
+
+    /** @var \Google\Client $client */
+    private $client;
+
+    /** @var string $serviceName */
+    private $serviceName;
+
+    /** @var string $servicePath */
+    private $servicePath;
+
+    /** @var string $resourceName */
+    private $resourceName;
+
+    /** @var array $methods */
+    private $methods;
+
+    public function __construct($service, $serviceName, $resourceName, $resource)
+    {
+        $this->rootUrlTemplate = $service->rootUrlTemplate ?? $service->rootUrl;
+        $this->client = $service->getClient();
+        $this->servicePath = $service->servicePath;
+        $this->serviceName = $serviceName;
+        $this->resourceName = $resourceName;
+        $this->methods = is_array($resource) && isset($resource['methods']) ?
+        $resource['methods'] :
+        [$resourceName => $resource];
+    }
+
+    /**
+     * TODO: This function needs simplifying.
+     *
+     * @template T
+     * @param string $name
+     * @param array $arguments
+     * @param class-string<T> $expectedClass - optional, the expected class name
+     * @return mixed|T|ResponseInterface|RequestInterface
+     * @throws \Google\Exception
+     */
+    public function call($name, $arguments, $expectedClass = null)
+    {
+        if (! isset($this->methods[$name])) {
+            $this->client->getLogger()->error(
+                'Service method unknown',
+                [
+                    'service' => $this->serviceName,
+                    'resource' => $this->resourceName,
+                    'method' => $name
+                ]
+            );
+
+            throw new GoogleException(
+                "Unknown function: " .
+                "{$this->serviceName}->{$this->resourceName}->{$name}()"
+            );
+        }
+        $method = $this->methods[$name];
+        $parameters = $arguments[0];
+
+        // postBody is a special case since it's not defined in the discovery
+        // document as parameter, but we abuse the param entry for storing it.
+        $postBody = null;
+        if (isset($parameters['postBody'])) {
+            if ($parameters['postBody'] instanceof Model) {
+                // In the cases the post body is an existing object, we want
+                // to use the smart method to create a simple object for
+                // for JSONification.
+                $parameters['postBody'] = $parameters['postBody']->toSimpleObject();
+            } elseif (is_object($parameters['postBody'])) {
+                // If the post body is another kind of object, we will try and
+                // wrangle it into a sensible format.
+                $parameters['postBody'] =
+                    $this->convertToArrayAndStripNulls($parameters['postBody']);
+            }
+            $postBody = (array) $parameters['postBody'];
+            unset($parameters['postBody']);
+        }
+
+        // TODO: optParams here probably should have been
+        // handled already - this may well be redundant code.
+        if (isset($parameters['optParams'])) {
+            $optParams = $parameters['optParams'];
+            unset($parameters['optParams']);
+            $parameters = array_merge($parameters, $optParams);
+        }
+
+        if (!isset($method['parameters'])) {
+            $method['parameters'] = [];
+        }
+
+        $method['parameters'] = array_merge(
+            $this->stackParameters,
+            $method['parameters']
+        );
+
+        foreach ($parameters as $key => $val) {
+            if ($key != 'postBody' && !isset($method['parameters'][$key])) {
+                $this->client->getLogger()->error(
+                    'Service parameter unknown',
+                    [
+                        'service' => $this->serviceName,
+                        'resource' => $this->resourceName,
+                        'method' => $name,
+                        'parameter' => $key
+                    ]
+                );
+                throw new GoogleException("($name) unknown parameter: '$key'");
+            }
+        }
+
+        foreach ($method['parameters'] as $paramName => $paramSpec) {
+            if (
+                isset($paramSpec['required']) &&
+                $paramSpec['required'] &&
+                ! isset($parameters[$paramName])
+            ) {
+                $this->client->getLogger()->error(
+                    'Service parameter missing',
+                    [
+                        'service' => $this->serviceName,
+                        'resource' => $this->resourceName,
+                        'method' => $name,
+                        'parameter' => $paramName
+                    ]
+                );
+                throw new GoogleException("($name) missing required param: '$paramName'");
+            }
+            if (isset($parameters[$paramName])) {
+                $value = $parameters[$paramName];
+                $parameters[$paramName] = $paramSpec;
+                $parameters[$paramName]['value'] = $value;
+                unset($parameters[$paramName]['required']);
+            } else {
+                // Ensure we don't pass nulls.
+                unset($parameters[$paramName]);
+            }
+        }
+
+        $this->client->getLogger()->info(
+            'Service Call',
+            [
+                'service' => $this->serviceName,
+                'resource' => $this->resourceName,
+                'method' => $name,
+                'arguments' => $parameters,
+            ]
+        );
+
+        // build the service uri
+        $url = $this->createRequestUri($method['path'], $parameters);
+
+        // NOTE: because we're creating the request by hand,
+        // and because the service has a rootUrl property
+        // the "base_uri" of the Http Client is not accounted for
+        $request = new Request(
+            $method['httpMethod'],
+            $url,
+            $postBody ? ['content-type' => 'application/json'] : [],
+            $postBody ? json_encode($postBody) : ''
+        );
+
+        // support uploads
+        if (isset($parameters['data'])) {
+            $mimeType = isset($parameters['mimeType'])
+                ? $parameters['mimeType']['value']
+                : 'application/octet-stream';
+            $data = $parameters['data']['value'];
+            $upload = new MediaFileUpload($this->client, $request, $mimeType, $data);
+
+            // pull down the modified request
+            $request = $upload->getRequest();
+        }
+
+        // if this is a media type, we will return the raw response
+        // rather than using an expected class
+        if (isset($parameters['alt']) && $parameters['alt']['value'] == 'media') {
+            $expectedClass = null;
+        }
+
+        // If the class which is extending from this one contains
+        // an Api Version, add it to the header
+        if ($this->apiVersion) {
+            $request = $request
+                ->withHeader('X-Goog-Api-Version', $this->apiVersion);
+        }
+
+        // if the client is marked for deferring, rather than
+        // execute the request, return the response
+        if ($this->client->shouldDefer()) {
+            // @TODO find a better way to do this
+            $request = $request
+                ->withHeader('X-Php-Expected-Class', $expectedClass);
+
+            return $request;
+        }
+
+        return $this->client->execute($request, $expectedClass);
+    }
+
+    protected function convertToArrayAndStripNulls($o)
+    {
+        $o = (array) $o;
+        foreach ($o as $k => $v) {
+            if ($v === null) {
+                unset($o[$k]);
+            } elseif (is_object($v) || is_array($v)) {
+                $o[$k] = $this->convertToArrayAndStripNulls($o[$k]);
+            }
+        }
+        return $o;
+    }
+
+    /**
+     * Parse/expand request parameters and create a fully qualified
+     * request uri.
+     * @static
+     * @param string $restPath
+     * @param array $params
+     * @return string $requestUrl
+     */
+    public function createRequestUri($restPath, $params)
+    {
+        // Override the default servicePath address if the $restPath use a /
+        if ('/' == substr($restPath, 0, 1)) {
+            $requestUrl = substr($restPath, 1);
+        } else {
+            $requestUrl = $this->servicePath . $restPath;
+        }
+
+        if ($this->rootUrlTemplate) {
+            // code for universe domain
+            $rootUrl = str_replace('UNIVERSE_DOMAIN', $this->client->getUniverseDomain(), $this->rootUrlTemplate);
+            // code for leading slash
+            if ('/' !== substr($rootUrl, -1) && '/' !== substr($requestUrl, 0, 1)) {
+                $requestUrl = '/' . $requestUrl;
+            }
+            $requestUrl = $rootUrl . $requestUrl;
+        }
+        $uriTemplateVars = [];
+        $queryVars = [];
+        foreach ($params as $paramName => $paramSpec) {
+            if ($paramSpec['type'] == 'boolean') {
+                $paramSpec['value'] = $paramSpec['value'] ? 'true' : 'false';
+            }
+            if ($paramSpec['location'] == 'path') {
+                $uriTemplateVars[$paramName] = $paramSpec['value'];
+            } elseif ($paramSpec['location'] == 'query') {
+                if (is_array($paramSpec['value'])) {
+                    foreach ($paramSpec['value'] as $value) {
+                        $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($value));
+                    }
+                } else {
+                    $queryVars[] = $paramName . '=' . rawurlencode(rawurldecode($paramSpec['value']));
+                }
+            }
+        }
+
+        if (count($uriTemplateVars)) {
+            $uriTemplateParser = new UriTemplate();
+            $requestUrl = $uriTemplateParser->parse($requestUrl, $uriTemplateVars);
+        }
+
+        if (count($queryVars)) {
+            $requestUrl .= '?' . implode('&', $queryVars);
+        }
+
+        return $requestUrl;
+    }
+}
Index: vendor/google/apiclient/src/Task/Composer.php
===================================================================
--- vendor/google/apiclient/src/Task/Composer.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Task/Composer.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,113 @@
+<?php
+/*
+ * Copyright 2020 Google LLC
+ *
+ * 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 Google\Task;
+
+use Composer\Script\Event;
+use InvalidArgumentException;
+use Symfony\Component\Filesystem\Filesystem;
+use Symfony\Component\Finder\Finder;
+
+class Composer
+{
+    /**
+     * @param Event $event Composer event passed in for any script method
+     * @param Filesystem $filesystem Optional. Used for testing.
+     */
+    public static function cleanup(
+        Event $event,
+        ?Filesystem $filesystem = null
+    ) {
+        $composer = $event->getComposer();
+        $extra = $composer->getPackage()->getExtra();
+        $servicesToKeep = isset($extra['google/apiclient-services'])
+            ? $extra['google/apiclient-services']
+            : [];
+        if ($servicesToKeep) {
+            $vendorDir = $composer->getConfig()->get('vendor-dir');
+            $serviceDir = sprintf(
+                '%s/google/apiclient-services/src/Google/Service',
+                $vendorDir
+            );
+            if (!is_dir($serviceDir)) {
+                // path for google/apiclient-services >= 0.200.0
+                $serviceDir = sprintf(
+                    '%s/google/apiclient-services/src',
+                    $vendorDir
+                );
+            }
+            self::verifyServicesToKeep($serviceDir, $servicesToKeep);
+            $finder = self::getServicesToRemove($serviceDir, $servicesToKeep);
+            $filesystem = $filesystem ?: new Filesystem();
+            if (0 !== $count = count($finder)) {
+                $event->getIO()->write(
+                    sprintf('Removing %s google services', $count)
+                );
+                foreach ($finder as $file) {
+                    $realpath = $file->getRealPath();
+                    $filesystem->remove($realpath);
+                    $filesystem->remove($realpath . '.php');
+                }
+            }
+        }
+    }
+
+    /**
+     * @throws InvalidArgumentException when the service doesn't exist
+     */
+    private static function verifyServicesToKeep(
+        $serviceDir,
+        array $servicesToKeep
+    ) {
+        $finder = (new Finder())
+            ->directories()
+            ->depth('== 0');
+
+        foreach ($servicesToKeep as $service) {
+            if (!preg_match('/^[a-zA-Z0-9]*$/', $service)) {
+                throw new InvalidArgumentException(
+                    sprintf(
+                        'Invalid Google service name "%s"',
+                        $service
+                    )
+                );
+            }
+            try {
+                $finder->in($serviceDir . '/' . $service);
+            } catch (InvalidArgumentException $e) {
+                throw new InvalidArgumentException(
+                    sprintf(
+                        'Google service "%s" does not exist or was removed previously',
+                        $service
+                    )
+                );
+            }
+        }
+    }
+
+    private static function getServicesToRemove(
+        $serviceDir,
+        array $servicesToKeep
+    ) {
+        // find all files in the current directory
+        return (new Finder())
+            ->directories()
+            ->depth('== 0')
+            ->in($serviceDir)
+            ->exclude($servicesToKeep);
+    }
+}
Index: vendor/google/apiclient/src/Task/Exception.php
===================================================================
--- vendor/google/apiclient/src/Task/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Task/Exception.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,24 @@
+<?php
+/*
+ * Copyright 2014 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 Google\Task;
+
+use Google\Exception as GoogleException;
+
+class Exception extends GoogleException
+{
+}
Index: vendor/google/apiclient/src/Task/Retryable.php
===================================================================
--- vendor/google/apiclient/src/Task/Retryable.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Task/Retryable.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,26 @@
+<?php
+/*
+ * Copyright 2014 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 Google\Task;
+
+/**
+ * Interface for checking how many times a given task can be retried following
+ * a failure.
+ */
+interface Retryable
+{
+}
Index: vendor/google/apiclient/src/Task/Runner.php
===================================================================
--- vendor/google/apiclient/src/Task/Runner.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Task/Runner.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,293 @@
+<?php
+/*
+ * Copyright 2014 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 Google\Task;
+
+use Google\Service\Exception as GoogleServiceException;
+use Google\Task\Exception as GoogleTaskException;
+
+/**
+ * A task runner with exponential backoff support.
+ *
+ * @see https://developers.google.com/drive/web/handle-errors#implementing_exponential_backoff
+ */
+class Runner
+{
+    const TASK_RETRY_NEVER = 0;
+    const TASK_RETRY_ONCE = 1;
+    const TASK_RETRY_ALWAYS = -1;
+
+    /**
+     * @var integer $maxDelay The max time (in seconds) to wait before a retry.
+     */
+    private $maxDelay = 60;
+
+    /**
+     * @var integer $delay The previous delay from which the next is calculated.
+     */
+    private $delay = 1;
+
+    /**
+     * @var integer $factor The base number for the exponential back off.
+     */
+    private $factor = 2;
+
+    /**
+     * @var float $jitter A random number between -$jitter and $jitter will be
+     * added to $factor on each iteration to allow for a better distribution of
+     * retries.
+     */
+    private $jitter = 0.5;
+
+    /**
+     * @var integer $attempts The number of attempts that have been tried so far.
+     */
+    private $attempts = 0;
+
+    /**
+     * @var integer $maxAttempts The max number of attempts allowed.
+     */
+    private $maxAttempts = 1;
+
+    /**
+     * @var callable $action The task to run and possibly retry.
+     */
+    private $action;
+
+    /**
+     * @var array $arguments The task arguments.
+     */
+    private $arguments;
+
+    /**
+     * @var array $retryMap Map of errors with retry counts.
+     */
+    protected $retryMap = [
+        '500' => self::TASK_RETRY_ALWAYS,
+        '503' => self::TASK_RETRY_ALWAYS,
+        'rateLimitExceeded' => self::TASK_RETRY_ALWAYS,
+        'userRateLimitExceeded' => self::TASK_RETRY_ALWAYS,
+        6  => self::TASK_RETRY_ALWAYS,  // CURLE_COULDNT_RESOLVE_HOST
+        7  => self::TASK_RETRY_ALWAYS,  // CURLE_COULDNT_CONNECT
+        28 => self::TASK_RETRY_ALWAYS,  // CURLE_OPERATION_TIMEOUTED
+        35 => self::TASK_RETRY_ALWAYS,  // CURLE_SSL_CONNECT_ERROR
+        52 => self::TASK_RETRY_ALWAYS,  // CURLE_GOT_NOTHING
+        'lighthouseError' => self::TASK_RETRY_NEVER
+    ];
+
+    /**
+     * Creates a new task runner with exponential backoff support.
+     *
+     * @param array $config The task runner config
+     * @param string $name The name of the current task (used for logging)
+     * @param callable $action The task to run and possibly retry
+     * @param array $arguments The task arguments
+     * @throws \Google\Task\Exception when misconfigured
+     */
+    // @phpstan-ignore-next-line
+    public function __construct(
+        $config,
+        $name,
+        $action,
+        array $arguments = []
+    ) {
+        if (isset($config['initial_delay'])) {
+            if ($config['initial_delay'] < 0) {
+                throw new GoogleTaskException(
+                    'Task configuration `initial_delay` must not be negative.'
+                );
+            }
+
+            $this->delay = $config['initial_delay'];
+        }
+
+        if (isset($config['max_delay'])) {
+            if ($config['max_delay'] <= 0) {
+                throw new GoogleTaskException(
+                    'Task configuration `max_delay` must be greater than 0.'
+                );
+            }
+
+            $this->maxDelay = $config['max_delay'];
+        }
+
+        if (isset($config['factor'])) {
+            if ($config['factor'] <= 0) {
+                throw new GoogleTaskException(
+                    'Task configuration `factor` must be greater than 0.'
+                );
+            }
+
+            $this->factor = $config['factor'];
+        }
+
+        if (isset($config['jitter'])) {
+            if ($config['jitter'] <= 0) {
+                throw new GoogleTaskException(
+                    'Task configuration `jitter` must be greater than 0.'
+                );
+            }
+
+            $this->jitter = $config['jitter'];
+        }
+
+        if (isset($config['retries'])) {
+            if ($config['retries'] < 0) {
+                throw new GoogleTaskException(
+                    'Task configuration `retries` must not be negative.'
+                );
+            }
+            $this->maxAttempts += $config['retries'];
+        }
+
+        if (!is_callable($action)) {
+            throw new GoogleTaskException(
+                'Task argument `$action` must be a valid callable.'
+            );
+        }
+
+        $this->action = $action;
+        $this->arguments = $arguments;
+    }
+
+    /**
+     * Checks if a retry can be attempted.
+     *
+     * @return boolean
+     */
+    public function canAttempt()
+    {
+        return $this->attempts < $this->maxAttempts;
+    }
+
+    /**
+     * Runs the task and (if applicable) automatically retries when errors occur.
+     *
+     * @return mixed
+     * @throws \Google\Service\Exception on failure when no retries are available.
+     */
+    public function run()
+    {
+        while ($this->attempt()) {
+            try {
+                return call_user_func_array($this->action, $this->arguments);
+            } catch (GoogleServiceException $exception) {
+                $allowedRetries = $this->allowedRetries(
+                    $exception->getCode(),
+                    $exception->getErrors()
+                );
+
+                if (!$this->canAttempt() || !$allowedRetries) {
+                    throw $exception;
+                }
+
+                if ($allowedRetries > 0) {
+                    $this->maxAttempts = min(
+                        $this->maxAttempts,
+                        $this->attempts + $allowedRetries
+                    );
+                }
+            }
+        }
+    }
+
+    /**
+     * Runs a task once, if possible. This is useful for bypassing the `run()`
+     * loop.
+     *
+     * NOTE: If this is not the first attempt, this function will sleep in
+     * accordance to the backoff configurations before running the task.
+     *
+     * @return boolean
+     */
+    public function attempt()
+    {
+        if (!$this->canAttempt()) {
+            return false;
+        }
+
+        if ($this->attempts > 0) {
+            $this->backOff();
+        }
+
+        $this->attempts++;
+
+        return true;
+    }
+
+    /**
+     * Sleeps in accordance to the backoff configurations.
+     */
+    private function backOff()
+    {
+        $delay = $this->getDelay();
+
+        usleep((int) ($delay * 1000000));
+    }
+
+    /**
+     * Gets the delay (in seconds) for the current backoff period.
+     *
+     * @return int
+     */
+    private function getDelay()
+    {
+        $jitter = $this->getJitter();
+        $factor = $this->attempts > 1 ? $this->factor + $jitter : 1 + abs($jitter);
+
+        return $this->delay = min($this->maxDelay, $this->delay * $factor);
+    }
+
+    /**
+     * Gets the current jitter (random number between -$this->jitter and
+     * $this->jitter).
+     *
+     * @return float
+     */
+    private function getJitter()
+    {
+        return $this->jitter * 2 * mt_rand() / mt_getrandmax() - $this->jitter;
+    }
+
+    /**
+     * Gets the number of times the associated task can be retried.
+     *
+     * NOTE: -1 is returned if the task can be retried indefinitely
+     *
+     * @return integer
+     */
+    public function allowedRetries($code, $errors = [])
+    {
+        if (isset($this->retryMap[$code])) {
+            return $this->retryMap[$code];
+        }
+
+        if (
+            !empty($errors) &&
+            isset($errors[0]['reason'], $this->retryMap[$errors[0]['reason']])
+        ) {
+            return $this->retryMap[$errors[0]['reason']];
+        }
+
+        return 0;
+    }
+
+    public function setRetryMap($retryMap)
+    {
+        $this->retryMap = $retryMap;
+    }
+}
Index: vendor/google/apiclient/src/Utils/UriTemplate.php
===================================================================
--- vendor/google/apiclient/src/Utils/UriTemplate.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/Utils/UriTemplate.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,334 @@
+<?php
+/*
+ * Copyright 2013 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 Google\Utils;
+
+/**
+ * Implementation of levels 1-3 of the URI Template spec.
+ * @see http://tools.ietf.org/html/rfc6570
+ */
+class UriTemplate
+{
+    const TYPE_MAP = "1";
+    const TYPE_LIST = "2";
+    const TYPE_SCALAR = "4";
+
+    /**
+     * @var array $operators
+     * These are valid at the start of a template block to
+     * modify the way in which the variables inside are
+     * processed.
+     */
+    private $operators = [
+        "+" => "reserved",
+        "/" => "segments",
+        "." => "dotprefix",
+        "#" => "fragment",
+        ";" => "semicolon",
+        "?" => "form",
+        "&" => "continuation"
+    ];
+
+    /**
+     * @var array<string>
+     * These are the characters which should not be URL encoded in reserved
+     * strings.
+     */
+    private $reserved = [
+        "=", ",", "!", "@", "|", ":", "/", "?", "#",
+        "[", "]", '$', "&", "'", "(", ")", "*", "+", ";"
+    ];
+    private $reservedEncoded = [
+        "%3D", "%2C", "%21", "%40", "%7C", "%3A", "%2F", "%3F",
+        "%23", "%5B", "%5D", "%24", "%26", "%27", "%28", "%29",
+        "%2A", "%2B", "%3B"
+    ];
+
+    public function parse($string, array $parameters)
+    {
+        return $this->resolveNextSection($string, $parameters);
+    }
+
+    /**
+     * This function finds the first matching {...} block and
+     * executes the replacement. It then calls itself to find
+     * subsequent blocks, if any.
+     */
+    private function resolveNextSection($string, $parameters)
+    {
+        $start = strpos($string, "{");
+        if ($start === false) {
+            return $string;
+        }
+        $end = strpos($string, "}");
+        if ($end === false) {
+            return $string;
+        }
+        $string = $this->replace($string, $start, $end, $parameters);
+        return $this->resolveNextSection($string, $parameters);
+    }
+
+    private function replace($string, $start, $end, $parameters)
+    {
+        // We know a data block will have {} round it, so we can strip that.
+        $data = substr($string, $start + 1, $end - $start - 1);
+
+        // If the first character is one of the reserved operators, it effects
+        // the processing of the stream.
+        if (isset($this->operators[$data[0]])) {
+            $op = $this->operators[$data[0]];
+            $data = substr($data, 1);
+            $prefix = "";
+            $prefix_on_missing = false;
+
+            switch ($op) {
+                case "reserved":
+                    // Reserved means certain characters should not be URL encoded
+                    $data = $this->replaceVars($data, $parameters, ",", null, true);
+                    break;
+                case "fragment":
+                    // Comma separated with fragment prefix. Bare values only.
+                    $prefix = "#";
+                    $prefix_on_missing = true;
+                    $data = $this->replaceVars($data, $parameters, ",", null, true);
+                    break;
+                case "segments":
+                    // Slash separated data. Bare values only.
+                    $prefix = "/";
+                    $data =$this->replaceVars($data, $parameters, "/");
+                    break;
+                case "dotprefix":
+                    // Dot separated data. Bare values only.
+                    $prefix = ".";
+                    $prefix_on_missing = true;
+                    $data = $this->replaceVars($data, $parameters, ".");
+                    break;
+                case "semicolon":
+                    // Semicolon prefixed and separated. Uses the key name
+                    $prefix = ";";
+                    $data = $this->replaceVars($data, $parameters, ";", "=", false, true, false);
+                    break;
+                case "form":
+                    // Standard URL format. Uses the key name
+                    $prefix = "?";
+                    $data = $this->replaceVars($data, $parameters, "&", "=");
+                    break;
+                case "continuation":
+                    // Standard URL, but with leading ampersand. Uses key name.
+                    $prefix = "&";
+                    $data = $this->replaceVars($data, $parameters, "&", "=");
+                    break;
+            }
+
+            // Add the initial prefix character if data is valid.
+            if ($data || ($data !== false && $prefix_on_missing)) {
+                $data = $prefix . $data;
+            }
+        } else {
+            // If no operator we replace with the defaults.
+            $data = $this->replaceVars($data, $parameters);
+        }
+        // This is chops out the {...} and replaces with the new section.
+        return substr($string, 0, $start) . $data . substr($string, $end + 1);
+    }
+
+    private function replaceVars(
+        $section,
+        $parameters,
+        $sep = ",",
+        $combine = null,
+        $reserved = false,
+        $tag_empty = false,
+        $combine_on_empty = true
+    ) {
+        if (strpos($section, ",") === false) {
+            // If we only have a single value, we can immediately process.
+            return $this->combine(
+                $section,
+                $parameters,
+                $sep,
+                $combine,
+                $reserved,
+                $tag_empty,
+                $combine_on_empty
+            );
+        } else {
+            // If we have multiple values, we need to split and loop over them.
+            // Each is treated individually, then glued together with the
+            // separator character.
+            $vars = explode(",", $section);
+            return $this->combineList(
+                $vars,
+                $sep,
+                $parameters,
+                $combine,
+                $reserved,
+                false, // Never emit empty strings in multi-param replacements
+                $combine_on_empty
+            );
+        }
+    }
+
+    public function combine(
+        $key,
+        $parameters,
+        $sep,
+        $combine,
+        $reserved,
+        $tag_empty,
+        $combine_on_empty
+    ) {
+        $length = false;
+        $explode = false;
+        $skip_final_combine = false;
+        $value = false;
+
+        // Check for length restriction.
+        if (strpos($key, ":") !== false) {
+            list($key, $length) = explode(":", $key);
+        }
+
+        // Check for explode parameter.
+        if ($key[strlen($key) - 1] == "*") {
+            $explode = true;
+            $key = substr($key, 0, -1);
+            $skip_final_combine = true;
+        }
+
+        // Define the list separator.
+        $list_sep = $explode ? $sep : ",";
+
+        if (isset($parameters[$key])) {
+            $data_type = $this->getDataType($parameters[$key]);
+            switch ($data_type) {
+                case self::TYPE_SCALAR:
+                    $value = $this->getValue($parameters[$key], $length);
+                    break;
+                case self::TYPE_LIST:
+                    $values = [];
+                    foreach ($parameters[$key] as $pkey => $pvalue) {
+                        $pvalue = $this->getValue($pvalue, $length);
+                        if ($combine && $explode) {
+                            $values[$pkey] = $key . $combine . $pvalue;
+                        } else {
+                            $values[$pkey] = $pvalue;
+                        }
+                    }
+                    $value = implode($list_sep, $values);
+                    if ($value == '') {
+                        return '';
+                    }
+                    break;
+                case self::TYPE_MAP:
+                    $values = [];
+                    foreach ($parameters[$key] as $pkey => $pvalue) {
+                        $pvalue = $this->getValue($pvalue, $length);
+                        if ($explode) {
+                            $pkey = $this->getValue($pkey, $length);
+                            $values[] = $pkey . "=" . $pvalue; // Explode triggers = combine.
+                        } else {
+                            $values[] = $pkey;
+                            $values[] = $pvalue;
+                        }
+                    }
+                    $value = implode($list_sep, $values);
+                    if ($value == '') {
+                        return false;
+                    }
+                    break;
+            }
+        } elseif ($tag_empty) {
+            // If we are just indicating empty values with their key name, return that.
+            return $key;
+        } else {
+            // Otherwise we can skip this variable due to not being defined.
+            return false;
+        }
+
+        if ($reserved) {
+            $value = str_replace($this->reservedEncoded, $this->reserved, $value);
+        }
+
+        // If we do not need to include the key name, we just return the raw
+        // value.
+        if (!$combine || $skip_final_combine) {
+            return $value;
+        }
+
+        // Else we combine the key name: foo=bar, if value is not the empty string.
+        return $key . ($value != '' || $combine_on_empty ? $combine . $value : '');
+    }
+
+    /**
+     * Return the type of a passed in value
+     */
+    private function getDataType($data)
+    {
+        if (is_array($data)) {
+            reset($data);
+            if (key($data) !== 0) {
+                return self::TYPE_MAP;
+            }
+            return self::TYPE_LIST;
+        }
+        return self::TYPE_SCALAR;
+    }
+
+    /**
+     * Utility function that merges multiple combine calls
+     * for multi-key templates.
+     */
+    private function combineList(
+        $vars,
+        $sep,
+        $parameters,
+        $combine,
+        $reserved,
+        $tag_empty,
+        $combine_on_empty
+    ) {
+        $ret = [];
+        foreach ($vars as $var) {
+            $response = $this->combine(
+                $var,
+                $parameters,
+                $sep,
+                $combine,
+                $reserved,
+                $tag_empty,
+                $combine_on_empty
+            );
+            if ($response === false) {
+                continue;
+            }
+            $ret[] = $response;
+        }
+        return implode($sep, $ret);
+    }
+
+    /**
+     * Utility function to encode and trim values
+     */
+    private function getValue($value, $length)
+    {
+        if ($length) {
+            $value = substr($value, 0, $length);
+        }
+        $value = rawurlencode($value);
+        return $value;
+    }
+}
Index: vendor/google/apiclient/src/aliases.php
===================================================================
--- vendor/google/apiclient/src/aliases.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
+++ vendor/google/apiclient/src/aliases.php	(revision e3d4e0a8bc0f13135f27c0b055b5c00f64a28f01)
@@ -0,0 +1,102 @@
+<?php
+
+if (class_exists('Google_Client', false)) {
+    // Prevent error with preloading in PHP 7.4
+    // @see https://github.com/googleapis/google-api-php-client/issues/1976
+    return;
+}
+
+$classMap = [
+    'Google\\Client' => 'Google_Client',
+    'Google\\Service' => 'Google_Service',
+    'Google\\AccessToken\\Revoke' => 'Google_AccessToken_Revoke',
+    'Google\\AccessToken\\Verify' => 'Google_AccessToken_Verify',
+    'Google\\Model' => 'Google_Model',
+    'Google\\Utils\\UriTemplate' => 'Google_Utils_UriTemplate',
+    'Google\\AuthHandler\\Guzzle6AuthHandler' => 'Google_AuthHandler_Guzzle6AuthHandler',
+    'Google\\AuthHandler\\Guzzle7AuthHandler' => 'Google_AuthHandler_Guzzle7AuthHandler',
+    'Google\\AuthHandler\\AuthHandlerFactory' => 'Google_AuthHandler_AuthHandlerFactory',
+    'Google\\Http\\Batch' => 'Google_Http_Batch',
+    'Google\\Http\\MediaFileUpload' => 'Google_Http_MediaFileUpload',
+    'Google\\Http\\REST' => 'Google_Http_REST',
+    'Google\\Task\\Retryable' => 'Google_Task_Retryable',
+    'Google\\Task\\Exception' => 'Google_Task_Exception',
+    'Google\\Task\\Runner' => 'Google_Task_Runner',
+    'Google\\Collection' => 'Google_Collection',
+    'Google\\Service\\Exception' => 'Google_Service_Exception',
+    'Google\\Service\\Resource' => 'Google_Service_Resource',
+    'Google\\Exception' => 'Google_Exception',
+];
+
+foreach ($classMap as $class => $alias) {
+    class_alias($class, $alias);
+}
+
+/**
+ * This class needs to be defined explicitly as scripts must be recognized by
+ * the autoloader.
+ */
+class Google_Task_Composer extends \Google\Task\Composer
+{
+}
+
+/** @phpstan-ignore-next-line */
+if (\false) {
+    class Google_AccessToken_Revoke extends \Google\AccessToken\Revoke
+    {
+    }
+    class Google_AccessToken_Verify extends \Google\AccessToken\Verify
+    {
+    }
+    class Google_AuthHandler_AuthHandlerFactory extends \Google\AuthHandler\AuthHandlerFactory
+    {
+    }
+    class Google_AuthHandler_Guzzle6AuthHandler extends \Google\AuthHandler\Guzzle6AuthHandler
+    {
+    }
+    class Google_AuthHandler_Guzzle7AuthHandler extends \Google\AuthHandler\Guzzle7AuthHandler
+    {
+    }
+    class Google_Client extends \Google\Client
+    {
+    }
+    class Google_Collection extends \Google\Collection
+    {
+    }
+    class Google_Exception extends \Google\Exception
+    {
+    }
+    class Google_Http_Batch extends \Google\Http\Batch
+    {
+    }
+    class Google_Http_MediaFileUpload extends \Google\Http\MediaFileUpload
+    {
+    }
+    class Google_Http_REST extends \Google\Http\REST
+    {
+    }
+    class Google_Model extends \Google\Model
+    {
+    }
+    class Google_Service extends \Google\Service
+    {
+    }
+    class Google_Service_Exception extends \Google\Service\Exception
+    {
+    }
+    class Google_Service_Resource extends \Google\Service\Resource
+    {
+    }
+    class Google_Task_Exception extends \Google\Task\Exception
+    {
+    }
+    interface Google_Task_Retryable extends \Google\Task\Retryable
+    {
+    }
+    class Google_Task_Runner extends \Google\Task\Runner
+    {
+    }
+    class Google_Utils_UriTemplate extends \Google\Utils\UriTemplate
+    {
+    }
+}
