feat: add S3-compatible storage provider (MinIO, Ceph, R2, etc.)

Adds a new 'S3-Compatible Storage' provider that works with any
S3-API-compatible object storage service, including MinIO, Ceph,
Cloudflare R2, Backblaze B2, and others.

Changes:
- New provider class: classes/providers/storage/s3-compatible-provider.php
  - Provider key: s3compatible
  - Reads user-configured endpoint URL from settings
  - Uses path-style URL access (required by most S3-compatible services)
  - Supports credentials via AS3CF_S3COMPAT_ACCESS_KEY_ID /
    AS3CF_S3COMPAT_SECRET_ACCESS_KEY wp-config.php constants
  - Disables AWS-specific features (Block Public Access, Object Ownership)
- New provider SVG icons (s3compatible.svg, -link.svg, -round.svg)
- Registered provider in main plugin class with endpoint setting support
- Updated StorageProviderSubPage to show endpoint URL input for S3-compatible
- Built pro settings bundle with rollup (Svelte 4.2.19)
- Added package.json and updated rollup.config.mjs for pro-only builds
This commit is contained in:
2026-03-03 12:30:18 +01:00
commit 3248cbb029
2086 changed files with 359427 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
# Contributor Code of Conduct
As contributors and maintainers of this project,
and in the interest of fostering an open and welcoming community,
we pledge to respect all people who contribute through reporting issues,
posting feature requests, updating documentation,
submitting pull requests or patches, and other activities.
We are committed to making participation in this project
a harassment-free experience for everyone,
regardless of level of experience, gender, gender identity and expression,
sexual orientation, disability, personal appearance,
body size, race, ethnicity, age, religion, or nationality.
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery
* Personal attacks
* Trolling or insulting/derogatory comments
* Public or private harassment
* Publishing other's private information,
such as physical or electronic
addresses, without explicit permission
* Other unethical or unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct.
By adopting this Code of Conduct,
project maintainers commit themselves to fairly and consistently
applying these principles to every aspect of managing this project.
Project maintainers who do not follow or enforce the Code of Conduct
may be permanently removed from the project team.
This code of conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community.
Instances of abusive, harassing, or otherwise unacceptable behavior
may be reported by opening an issue
or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)

202
vendor/Gcp/google/cloud-core/LICENSE vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

View File

@@ -0,0 +1,7 @@
# Security Policy
To report a security issue, please use [g.co/vulnz](https://g.co/vulnz).
The Google Security Team will respond within 5 working days of your report on g.co/vulnz.
We use g.co/vulnz for our intake, and do coordination and disclosure here using GitHub Security Advisory to privately discuss and fix the issue.

1
vendor/Gcp/google/cloud-core/VERSION vendored Normal file
View File

@@ -0,0 +1 @@
1.56.0

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env php
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
$foundAutoloader = \false;
$autoloaderCandidates = [
'/../vendor/autoload.php',
// Git clone of google-cloud-php-core
'/../../vendor/autoload.php',
// Git clone of google-cloud-php
'/../../../autoload.php',
// google/cloud-core installation
'/../../../../autoload.php',
];
foreach ($autoloaderCandidates as $candidate) {
if (\file_exists(__DIR__ . $candidate)) {
require_once __DIR__ . $candidate;
$foundAutoloader = \true;
break;
}
}
if (!$foundAutoloader) {
die('No autoloader found');
}
function showUsageAndDie()
{
die("usage: " . __FILE__ . " [daemon|retry] {id}");
}
if (\count($argv) < 2) {
showUsageAndDie();
}
if ($argv[1] === 'daemon') {
$daemon = new BatchDaemon(__FILE__);
if (\count($argv) == 2) {
$daemon->run();
} else {
$idNum = (int) $argv[2];
$job = $daemon->job($idNum);
$job->run();
}
} elseif ($argv[1] === 'retry') {
$retry = new Retry();
$retry->retryAll();
} else {
showUsageAndDie();
}

View File

@@ -0,0 +1,6 @@
<?php
namespace DeliciousBrains\WP_Offload_Media\Gcp;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Testing\TestHelpers;
TestHelpers::perfBootstrap();

View File

@@ -0,0 +1,7 @@
<?php
namespace DeliciousBrains\WP_Offload_Media\Gcp;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Testing\TestHelpers;
TestHelpers::snippetBootstrap();
\date_default_timezone_set('UTC');

View File

@@ -0,0 +1,84 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\UpdateMetadataInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\GetQuotaProjectInterface;
/**
* Provides an anonymous set of credentials, which is useful for APIs which do
* not require authentication.
*/
class AnonymousCredentials implements FetchAuthTokenInterface, UpdateMetadataInterface, GetQuotaProjectInterface
{
/**
* @var array
*/
private $token = ['access_token' => null];
/**
* Fetches the auth token. In this case it returns a null value.
*
* @param callable $httpHandler
* @return array
*/
public function fetchAuthToken(callable $httpHandler = null)
{
return $this->token;
}
/**
* Returns the cache key. In this case it returns a null value, disabling
* caching.
*
* @return string|null
*/
public function getCacheKey()
{
return null;
}
/**
* Fetches the last received token. In this case, it returns the same null
* auth token.
*
* @return array
*/
public function getLastReceivedToken()
{
return $this->token;
}
/**
* This method has no effect for AnonymousCredentials.
*
* @param array $metadata metadata hashmap
* @param string $authUri optional auth uri
* @param callable $httpHandler callback which delivers psr7 request
* @return array updated metadata hashmap
*/
public function updateMetadata($metadata, $authUri = null, callable $httpHandler = null)
{
return $metadata;
}
/**
* This method always returns null for AnonymousCredentials.
*
* @return string|null
*/
public function getQuotaProject()
{
return null;
}
}

View File

@@ -0,0 +1,227 @@
<?php
/**
* Copyright 2023 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\ArrayTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\Options\CallOptions;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Protobuf\NullValue;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Duration;
/**
* @internal
* Supplies helper methods to interact with the APIs.
*/
trait ApiHelperTrait
{
use ArrayTrait;
use TimeTrait;
/**
* Format a struct for the API.
*
* @param array $fields
* @return array
*/
private function formatStructForApi(array $fields)
{
$fFields = [];
foreach ($fields as $key => $value) {
$fFields[$key] = $this->formatValueForApi($value);
}
return ['fields' => $fFields];
}
private function unpackStructFromApi(array $struct)
{
$vals = [];
foreach ($struct['fields'] as $key => $val) {
$vals[$key] = $this->unpackValue($val);
}
return $vals;
}
private function unpackValue($value)
{
if (\count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: {$value}");
}
foreach ($value as $setField => $setValue) {
switch ($setField) {
case 'listValue':
$valueList = [];
foreach ($setValue['values'] as $innerValue) {
$valueList[] = $this->unpackValue($innerValue);
}
return $valueList;
case 'structValue':
return $this->unpackStructFromApi($value['structValue']);
default:
return $setValue;
}
}
}
private function flattenStruct(array $struct)
{
return $struct['fields'];
}
private function flattenValue(array $value)
{
if (\count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: {$value}");
}
if (isset($value['nullValue'])) {
return null;
}
return \array_pop($value);
}
private function flattenListValue(array $value)
{
return $value['values'];
}
/**
* Format a list for the API.
*
* @param array $list
* @return array
*/
private function formatListForApi(array $list)
{
$values = [];
foreach ($list as $value) {
$values[] = $this->formatValueForApi($value);
}
return ['values' => $values];
}
/**
* Format a value for the API.
*
* @param mixed $value
* @return array
*/
private function formatValueForApi($value)
{
$type = \gettype($value);
switch ($type) {
case 'string':
return ['string_value' => $value];
case 'double':
case 'integer':
return ['number_value' => $value];
case 'boolean':
return ['bool_value' => $value];
case 'NULL':
return ['null_value' => NullValue::NULL_VALUE];
case 'array':
if (!empty($value) && $this->isAssoc($value)) {
return ['struct_value' => $this->formatStructForApi($value)];
}
return ['list_value' => $this->formatListForApi($value)];
}
return [];
}
/**
* Format a gRPC timestamp to match the format returned by the REST API.
*
* @param array $timestamp
* @return string
*/
private function formatTimestampFromApi(array $timestamp)
{
$timestamp += ['seconds' => 0, 'nanos' => 0];
$dt = $this->createDateTimeFromSeconds($timestamp['seconds']);
return $this->formatTimeAsString($dt, $timestamp['nanos']);
}
/**
* Format a timestamp for the API with nanosecond precision.
*
* @param string $value
* @return array
*/
private function formatTimestampForApi($value)
{
list($dt, $nanos) = $this->parseTimeString($value);
return ['seconds' => (int) $dt->format('U'), 'nanos' => (int) $nanos];
}
/**
* Format a duration for the API.
*
* @param string|mixed $value
* @return array
*/
private function formatDurationForApi($value)
{
if (\is_string($value)) {
$d = \explode('.', \trim($value, 's'));
if (\count($d) < 2) {
$seconds = $d[0];
$nanos = 0;
} else {
$seconds = (int) $d[0];
$nanos = $this->convertFractionToNanoSeconds($d[1]);
}
} elseif ($value instanceof Duration) {
$d = $value->get();
$seconds = $d['seconds'];
$nanos = $d['nanos'];
}
return ['seconds' => $seconds, 'nanos' => $nanos];
}
/**
* Construct a gapic client. Allows for tests to intercept.
*
* @param string $gapicName
* @param array $config
* @return mixed
*/
protected function constructGapic($gapicName, array $config)
{
return new $gapicName($config);
}
/**
* Helper function to convert selective elements into protos out of a given input array.
*
* Example:
* ```
* $output = $topic->convertDataToProtos(['schema' =>[], 'other vals'], ['schema' => Schema::class]);
* $output['schema']; // This will be of the Schema type.
* ```
*
* @param array $input The input array.
* @param array $map The key,value pairs specifying the elements and the proto classes.
*
* @return array The modified array
*/
private function convertDataToProtos(array $input, array $map) : array
{
foreach ($map as $key => $className) {
if (isset($input[$key])) {
$input[$key] = $this->serializer->decodeMessage(new $className(), $input[$key]);
}
}
return $input;
}
/**
* Helper method used to split a supplied set of options into parameters that are passed into
* a proto message and optional args.
* We strictly treat the parameters allowed by `CallOptions` in GAX as the optional params
* and everything else that is passed is passed to the Proto message constructor.
*/
private function splitOptionalArgs(array $input, array $extraAllowedKeys = []) : array
{
$callOptionFields = \array_keys((new CallOptions([]))->toArray());
$keys = \array_merge($callOptionFields, $extraAllowedKeys);
$optionalArgs = $this->pluckArray($keys, $input);
return [$input, $optionalArgs];
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Provides basic array helper methods.
*/
trait ArrayTrait
{
/**
* Pluck a value out of an array.
*
* @param string $key
* @param array $arr
* @param bool $isRequired
* @return mixed
* @throws \InvalidArgumentException
*/
private function pluck($key, array &$arr, $isRequired = \true)
{
if (!\array_key_exists($key, $arr)) {
if ($isRequired) {
throw new \InvalidArgumentException("Key {$key} does not exist in the provided array.");
}
return null;
}
$value = $arr[$key];
unset($arr[$key]);
return $value;
}
/**
* Pluck a subset of an array.
*
* @param string[] $keys
* @param array $arr
* @return array
*/
private function pluckArray(array $keys, &$arr)
{
$values = [];
foreach ($keys as $key) {
if (\array_key_exists($key, $arr)) {
$values[$key] = $this->pluck($key, $arr, \false);
}
}
return $values;
}
/**
* Determine whether given array is associative.
* If $arr is empty, then $onEmpty will be returned
* $onEmpty defaults to true to maintain compatibility
* with the current usage.
*
* @param array $arr
* @param bool $onEmpty
* @return bool
*/
private function isAssoc(array $arr, $onEmpty = \true)
{
if (empty($arr)) {
return $onEmpty;
}
return \array_keys($arr) !== \range(0, \count($arr) - 1);
}
/**
* Just like array_filter(), but preserves falsey values except null.
*
* @param array $arr
* @return array
*/
private function arrayFilterRemoveNull(array $arr)
{
return \array_filter($arr, function ($element) {
return !\is_null($element);
});
}
/**
* A method, similar to PHP's `array_merge_recursive`, with two differences.
*
* 1. Keys in $array2 take precedence over keys in $array1.
* 2. Non-array keys found in both inputs are not transformed into an array
* and appended. Rather, the value in $array2 is used.
*
* @param array $array1
* @param array $array2
* @return array
*/
private function arrayMergeRecursive(array $array1, array $array2)
{
foreach ($array2 as $key => $value) {
if (\array_key_exists($key, $array1) && \is_array($array1[$key]) && \is_array($value)) {
$array1[$key] = $this->isAssoc($array1[$key]) && $this->isAssoc($value) ? $this->arrayMergeRecursive($array1[$key], $value) : \array_merge($array1[$key], $value);
} else {
$array1[$key] = $value;
}
}
return $array1;
}
}

View File

@@ -0,0 +1,143 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* An external daemon script for executing the batch jobs.
*
* @codeCoverageIgnore
*
* The system test is responsible for testing this class.
* {@see \Google\Cloud\Tests\System\Core\Batch}
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class BatchDaemon
{
use BatchDaemonTrait;
use HandleFailureTrait;
use SysvTrait;
use InterruptTrait;
/* @var BatchRunner */
private $runner;
/* @var array */
private $descriptorSpec;
/* @var string */
private $command;
/**
* Prepare the descriptor spec and install signal handlers.
*
* @param string $entrypoint Daemon's entrypoint script.
* @throws \RuntimeException
*/
public function __construct($entrypoint)
{
if (!$this->isSysvIPCLoaded()) {
throw new \RuntimeException('SystemV IPC extensions are missing.');
}
$this->runner = new BatchRunner(new SysvConfigStorage(), new SysvProcessor());
$this->shutdown = \false;
// Just share the usual descriptors.
$this->descriptorSpec = [0 => ['file', 'php://stdin', 'r'], 1 => ['file', 'php://stdout', 'w'], 2 => ['file', 'php://stderr', 'w']];
$this->command = \sprintf('exec php -d auto_prepend_file="" %s daemon', $entrypoint);
$this->initFailureFile();
}
/**
* A loop for the parent.
*
* @return void
*/
public function run()
{
$this->setupSignalHandlers();
$procs = [];
while (\true) {
$jobs = $this->runner->getJobs();
foreach ($jobs as $job) {
if (!\array_key_exists($job->identifier(), $procs)) {
$procs[$job->identifier()] = [];
}
while (\count($procs[$job->identifier()]) > $job->numWorkers()) {
// Stopping an excessive child.
echo 'Stopping an excessive child.' . \PHP_EOL;
$proc = \array_pop($procs[$job->identifier()]);
$status = \proc_get_status($proc);
// Keep sending SIGTERM until the child exits.
while ($status['running'] === \true) {
@\proc_terminate($proc);
\usleep(50000);
$status = \proc_get_status($proc);
}
@\proc_close($proc);
}
for ($i = 0; $i < $job->numWorkers(); $i++) {
$needStart = \false;
if (\array_key_exists($i, $procs[$job->identifier()])) {
$status = \proc_get_status($procs[$job->identifier()][$i]);
if ($status['running'] !== \true) {
$needStart = \true;
}
} else {
$needStart = \true;
}
if ($needStart) {
echo 'Starting a child.' . \PHP_EOL;
$procs[$job->identifier()][$i] = \proc_open(\sprintf('%s %d', $this->command, $job->id()), $this->descriptorSpec, $pipes);
}
}
}
\usleep(1000000);
// Reload the config after 1 second
\pcntl_signal_dispatch();
if ($this->shutdown) {
echo 'Shutting down, waiting for the children' . \PHP_EOL;
foreach ($procs as $k => $v) {
foreach ($v as $proc) {
$status = \proc_get_status($proc);
// Keep sending SIGTERM until the child exits.
while ($status['running'] === \true) {
@\proc_terminate($proc);
\usleep(50000);
$status = \proc_get_status($proc);
}
@\proc_close($proc);
}
}
echo 'BatchDaemon exiting' . \PHP_EOL;
exit;
}
// Reload the config
$this->runner->loadConfig();
\gc_collect_cycles();
}
}
/**
* Fetch the child job by id.
*
* @param int $idNum The id of the job to find
* @return JobInterface
*/
public function job($idNum)
{
return $this->runner->getJobFromIdNum($idNum);
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* A utility trait related to BatchDaemon functionality.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait BatchDaemonTrait
{
/**
* Returns whether or not the BatchDaemon is running.
*
* @return bool
*/
private function isDaemonRunning()
{
$isDaemonRunning = \filter_var(\getenv('IS_BATCH_DAEMON_RUNNING'), \FILTER_VALIDATE_BOOLEAN);
return $isDaemonRunning !== \false;
}
}

View File

@@ -0,0 +1,179 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* Represent batch jobs.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class BatchJob implements JobInterface
{
const DEFAULT_BATCH_SIZE = 100;
const DEFAULT_CALL_PERIOD = 2.0;
const DEFAULT_WORKERS = 1;
use JobTrait;
use SysvTrait;
use InterruptTrait;
use HandleFailureTrait;
/**
* @var callable The batch job handler. This callable accepts an array
* of items and should return a boolean.
*/
private $func;
/**
* @var int The size of the batch.
*/
private $batchSize;
/**
* @var float The period in seconds from the last execution to force
* executing the job.
*/
private $callPeriod;
/**
* @param string $identifier Unique identifier of the job.
* @param callable $func Any Callable except for Closure. The callable
* should accept an array of items as the first argument.
* @param int $idNum A numeric id for the job.
* @param array $options [optional] {
* Configuration options.
*
* @type int $batchSize The size of the batch. **Defaults to** `100`.
* @type float $callPeriod The period in seconds from the last execution
* to force executing the job. **Defaults to** `2.0`.
* @type int $numWorkers The number of child processes. It only takes
* effect with the {@see \Google\Cloud\Core\Batch\BatchDaemon}.
* **Defaults to** `1`.
* @type string $bootstrapFile A file to load before executing the
* job. It's needed for registering global functions.
* }
*/
public function __construct($identifier, $func, $idNum, array $options = [])
{
$options += ['batchSize' => self::DEFAULT_BATCH_SIZE, 'callPeriod' => self::DEFAULT_CALL_PERIOD, 'bootstrapFile' => null, 'numWorkers' => self::DEFAULT_WORKERS];
$this->identifier = $identifier;
$this->func = $func;
$this->id = $idNum;
$this->batchSize = $options['batchSize'];
$this->callPeriod = $options['callPeriod'];
$this->bootstrapFile = $options['bootstrapFile'];
$this->numWorkers = $options['numWorkers'];
$this->initFailureFile();
}
/**
* Run the job.
*/
public function run()
{
$this->setupSignalHandlers();
$sysvKey = $this->getSysvKey($this->id);
$q = \msg_get_queue($sysvKey);
$items = [];
$lastInvoked = \microtime(\true);
if (!\is_null($this->bootstrapFile)) {
require_once $this->bootstrapFile;
}
while (\true) {
// Fire SIGALRM after 1 second to unblock the blocking call.
\pcntl_alarm(1);
if (\msg_receive(
$q,
0,
$type,
8192,
$message,
\true,
0,
// blocking mode
$errorcode
)) {
if ($type === self::$typeDirect) {
$items[] = $message;
} elseif ($type === self::$typeFile) {
$items[] = \unserialize(\file_get_contents($message));
@\unlink($message);
}
}
\pcntl_signal_dispatch();
// It runs the job when
// 1. Number of items reaches the batchSize.
// 2-a. Count is >0 and the current time is larger than lastInvoked + period.
// 2-b. Count is >0 and the shutdown flag is true.
if (\count($items) >= $this->batchSize || \count($items) > 0 && (\microtime(\true) > $lastInvoked + $this->callPeriod || $this->shutdown)) {
\printf('Running the job with %d items' . \PHP_EOL, \count($items));
$this->flush($items);
$items = [];
$lastInvoked = \microtime(\true);
}
\gc_collect_cycles();
if ($this->shutdown) {
return;
}
}
}
/**
* Finish any pending activity for this job.
*
* @param array $items
* @return bool
*/
public function flush(array $items = [])
{
if (!$this->callFunc($items)) {
$this->handleFailure($this->id, $items);
return \false;
}
return \true;
}
/**
* Finish any pending activity for this job.
*
* @access private
* @internal
*
* @param array $items
* @return bool
*/
public function callFunc(array $items = [])
{
return \call_user_func_array($this->func, [$items]);
}
/**
* Returns the period in seconds from the last execution to force
* executing the job.
*
* @return float
*/
public function getCallPeriod()
{
return $this->callPeriod;
}
/**
* Returns the batch size.
*
* @return int
*/
public function getBatchSize()
{
return $this->batchSize;
}
}

View File

@@ -0,0 +1,191 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* A class for executing jobs in batch.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class BatchRunner
{
use BatchDaemonTrait;
use SysvTrait;
/**
* @var JobConfig
*/
private $config;
/**
* @var ConfigStorageInterface
*/
private $configStorage;
/**
* @var ProcessItemInterface
*/
private $processor;
/**
* Determine internal implementation and loads the configuration.
*
* @param ConfigStorageInterface $configStorage [optional] The
* ConfigStorage object to use. **Defaults to** null. This is only
* for testing purpose.
* @param ProcessItemInterface $processor [optional] The processor object
* to use. **Defaults to** null. This is only for testing purpose.
*/
public function __construct(ConfigStorageInterface $configStorage = null, ProcessItemInterface $processor = null)
{
if ($configStorage === null || $processor === null) {
if ($this->isSysvIPCLoaded() && $this->isDaemonRunning()) {
$configStorage = new SysvConfigStorage();
$processor = new SysvProcessor();
} else {
$configStorage = InMemoryConfigStorage::getInstance();
$processor = $configStorage;
}
}
$this->configStorage = $configStorage;
$this->processor = $processor;
$this->loadConfig();
}
/**
* Register a job for batch execution.
*
* @param string $identifier Unique identifier of the job.
* @param callable $func Any Callable except for Closure. The callable
* should accept an array of items as the first argument.
* @param array $options [optional] {
* Configuration options.
*
* @type int $batchSize The size of the batch.
* @type float $callPeriod The period in seconds from the last execution
* to force executing the job.
* @type int $numWorkers The number of child processes. It only takes
* effect with the {@see \Google\Cloud\Core\Batch\BatchDaemon}.
* @type string $bootstrapFile A file to load before executing the
* job. It's needed for registering global functions.
* }
* @return bool true on success, false on failure
* @throws \InvalidArgumentException When receiving a Closure.
*/
public function registerJob($identifier, $func, array $options = [])
{
if ($func instanceof \Closure) {
throw new \InvalidArgumentException('Closure is not allowed');
}
// Always work on the latest data
$result = $this->configStorage->lock();
if ($result === \false) {
return \false;
}
$this->config = $this->configStorage->load();
$this->config->registerJob($identifier, function ($id) use($identifier, $func, $options) {
return new BatchJob($identifier, $func, $id, $options);
});
try {
$result = $this->configStorage->save($this->config);
} finally {
$this->configStorage->unlock();
}
return $result;
}
/**
* Submit an item.
*
* @param string $identifier Unique identifier of the job.
* @param mixed $item It needs to be serializable.
*
* @return void
* @throws \RuntimeException
*/
public function submitItem($identifier, $item)
{
$job = $this->getJobFromId($identifier);
if ($job === null) {
throw new \RuntimeException("The identifier does not exist: {$identifier}");
}
$idNum = $job->id();
$this->processor->submit($item, $idNum);
}
/**
* Get the job with the given identifier.
*
* @param string $identifier Unique identifier of the job.
*
* @return BatchJob|null
*/
public function getJobFromId($identifier)
{
return $this->config->getJobFromId($identifier);
}
/**
* Get the job with the given numeric id.
*
* @param int $idNum A numeric id of the job.
*
* @return BatchJob|null
*/
public function getJobFromIdNum($idNum)
{
return $this->config->getJobFromIdNum($idNum);
}
/**
* Get all the jobs.
*
* @return BatchJob[]
*/
public function getJobs()
{
return $this->config->getJobs();
}
/**
* Load the config from the storage.
*
* @return bool true on success
* @throws \RuntimeException when it fails to load the config.
*/
public function loadConfig()
{
$result = $this->configStorage->lock();
if ($result === \false) {
throw new \RuntimeException('Failed to lock the configStorage');
}
try {
$result = $this->configStorage->load();
} catch (\RuntimeException $e) {
$this->configStorage->clear();
throw $e;
} finally {
$this->configStorage->unlock();
}
$this->config = $result;
return \true;
}
/**
* Gets the item processor.
*
* @return ProcessItemInterface
*/
public function getProcessor()
{
return $this->processor;
}
}

View File

@@ -0,0 +1,151 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Opis\Closure\SerializableClosure;
/**
* A trait to assist in the registering and processing of batch jobs.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait BatchTrait
{
use SerializableClientTrait;
/**
* @var array
*/
private $batchOptions;
/**
* @var BatchRunner
*/
private $batchRunner;
/**
* @var string
*/
private $identifier;
/**
* @var string
*/
private $batchMethod;
/**
* @var bool
*/
private $debugOutput;
/**
* @var resource
*/
private $debugOutputResource;
/**
* Flushes items in the batch queue that have yet to be delivered. Please
* note this will have no effect when using the batch daemon.
*
* @return bool
*/
public function flush()
{
$id = $this->batchRunner->getJobFromId($this->identifier)->id();
return $this->batchRunner->getProcessor()->flush($id);
}
/**
* Deliver a list of items in a batch call.
*
* @param array $items
* @return bool
* @access private
*/
public function send(array $items)
{
$start = \microtime(\true);
try {
\call_user_func_array($this->getCallback(), [$items]);
} catch (\Exception $e) {
if ($this->debugOutput) {
\fwrite($this->debugOutputResource, $e->getMessage() . \PHP_EOL . \PHP_EOL . $e->getTraceAsString() . \PHP_EOL);
}
return \false;
}
$end = \microtime(\true);
if ($this->debugOutput) {
\fwrite($this->debugOutputResource, \sprintf('%f seconds for %s: %d items' . \PHP_EOL, $end - $start, $this->batchMethod, \count($items)));
\fwrite($this->debugOutputResource, \sprintf('memory used: %d' . \PHP_EOL, \memory_get_usage()));
}
return \true;
}
/**
* Returns an array representation of a callback which will be used to write
* batch items.
*
* @return array
*/
protected abstract function getCallback();
/**
* @param array $options [optional] {
* Configuration options.
*
* @type resource $debugOutputResource A resource to output debug output
* to. **Defaults to** `php://stderr`.
* @type bool $debugOutput Whether or not to output debug information.
* Please note that unless a debug output resource is configured
* this setting will only apply to CLI based applications.
* **Defaults to** `false`.
* @type array $batchOptions A set of options for a BatchJob.
* {@see \Google\Cloud\Core\Batch\BatchJob::__construct()} for
* more details.
* **Defaults to** ['batchSize' => 1000,
* 'callPeriod' => 2.0,
* 'numWorkers' => 2].
* @type array $clientConfig A config used to construct the client upon
* which requests will be made.
* @type BatchRunner $batchRunner A BatchRunner object. Mainly used for
* the tests to inject a mock. **Defaults to** a newly created
* BatchRunner.
* @type string $identifier An identifier for the batch job. This
* value must be unique across all job configs.
* @type string $batchMethod The name of the batch method used to
* deliver items.
* @type ClosureSerializerInterface $closureSerializer An implementation
* responsible for serializing closures used in the
* `$clientConfig`. This is especially important when using the
* batch daemon. **Defaults to**
* {@see \Google\Cloud\Core\Batch\OpisClosureSerializer} if the
* `opis/closure` library is installed.
* }
* @throws \InvalidArgumentException
*/
private function setCommonBatchProperties(array $options = [])
{
if (!isset($options['identifier'])) {
throw new \InvalidArgumentException('A valid identifier is required in order to register a job.');
}
if (!isset($options['batchMethod'])) {
throw new \InvalidArgumentException('A batchMethod is required.');
}
$this->setSerializableClientOptions($options);
$this->batchMethod = $options['batchMethod'];
$this->identifier = $options['identifier'];
$this->debugOutputResource = $options['debugOutputResource'] ?? \fopen('php://stderr', 'w');
$this->debugOutput = $options['debugOutput'] ?? \false;
$batchOptions = $options['batchOptions'] ?? [];
$this->batchOptions = $batchOptions + ['batchSize' => 1000, 'callPeriod' => 2.0, 'numWorkers' => 2];
$this->batchRunner = $options['batchRunner'] ?? new BatchRunner();
$this->batchRunner->registerJob($this->identifier, [$this, 'send'], $this->batchOptions);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* An interface for serializing closures.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
interface ClosureSerializerInterface
{
/**
* Recursively serializes closures.
*
* @param mixed $data
*/
public function wrapClosures(&$data);
/**
* Recursively unserializes closures.
*
* @param mixed $data
*/
public function unwrapClosures(&$data);
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* An interface for storing the configuration.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
interface ConfigStorageInterface
{
/**
* locks the storage
*
* @return bool
*/
public function lock();
/**
* releases the lock
*
* @return bool
*/
public function unlock();
/**
* saves the JobConfig to the storage
* @param JobConfig $config A JobConfig to save.
* @return bool true on success, false on failure
*/
public function save(JobConfig $config);
/**
* loads the JobConfig from the storage
*
* @return JobConfig
* @throws \RuntimeException when failed to load the JobConfig.
*/
public function load();
/**
* Clear the JobConfig from storage.
*/
public function clear();
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* A utility trait for handling failed items.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait HandleFailureTrait
{
/**
* @var string A filename to save the failed items.
*/
private $failureFile;
/**
* @var string Base directory for the failure files.
*/
private $baseDir;
/**
* Determine the failureFile.
*/
private function initFailureFile()
{
$this->baseDir = \getenv('GOOGLE_CLOUD_BATCH_DAEMON_FAILURE_DIR');
if ('false' === $this->baseDir) {
// setting the file to the string "false" will prevent logging of failed items
return;
}
if ($this->baseDir === \false) {
$this->baseDir = \sprintf('%s/batch-daemon-failure', \sys_get_temp_dir());
}
if (!\is_dir($this->baseDir) && !@\mkdir($this->baseDir, 0700, \true) && !\is_dir($this->baseDir)) {
throw new \RuntimeException(\sprintf('Could not create a directory: %s', $this->baseDir));
}
// Use getmypid for simplicity.
$this->failureFile = \sprintf('%s/failed-items-%d', $this->baseDir, \getmypid());
}
/**
* Save the items to the failureFile. We silently abandon the items upon
* failures in this method because there's nothing we can do.
*
* @param int $idNum A numeric id for the job.
* @param array $items Items to save.
*/
public function handleFailure($idNum, array $items)
{
if (!$this->failureFile) {
$this->initFailureFile();
}
if ($this->failureFile) {
$fp = @\fopen($this->failureFile, 'a');
@\fwrite($fp, \json_encode(\serialize([$idNum => $items])) . \PHP_EOL);
@\fclose($fp);
}
}
/**
* Get all the filenames for the failure files.
*
* @return array Filenames for all the failure files.
*/
private function getFailedFiles()
{
$pattern = \sprintf('%s/failed-items-*', $this->baseDir);
return \glob($pattern) ?: [];
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use BadMethodCallException;
/**
* In-memory ConfigStorageInterface implementation.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
final class InMemoryConfigStorage implements ConfigStorageInterface, ProcessItemInterface
{
use HandleFailureTrait;
/* @var JobConfig */
private $config;
/* @var array */
private $items = [];
/* @var array */
private $lastInvoked = [];
/* @var float */
private $created;
/* @var bool */
private $hasShutdownHookRegistered;
/**
* Singleton getInstance.
*
* @return InMemoryConfigStorage
*/
public static function getInstance()
{
static $instance = null;
if ($instance === null) {
$instance = new InMemoryConfigStorage();
}
return $instance;
}
/**
* To prevent serialize.
*/
public function __sleep()
{
throw new BadMethodCallException('Serialization not supported');
}
/**
* To prevent unserialize.
*/
public function __wakeup()
{
throw new BadMethodCallException('Serialization not supported');
}
/**
* To prevent cloning.
*/
private function __clone()
{
}
/**
* The constructor registers the shutdown function for running the job for
* remainder items when the script exits.
*/
private function __construct()
{
$this->config = new JobConfig();
$this->created = \microtime(\true);
$this->initFailureFile();
$this->hasShutdownHookRegistered = \false;
}
/**
* Just return true
*
* @return bool
*/
public function lock()
{
return \true;
}
/**
* Just return true
*
* @return bool
*/
public function unlock()
{
return \true;
}
/**
* Save the given JobConfig.
*
* @param JobConfig $config A JobConfig to save.
* @return bool
*/
public function save(JobConfig $config)
{
$this->config = $config;
return \true;
}
/**
* Load a JobConfig from the storage.
*
* @return JobConfig
* @throws \RuntimeException when failed to load the JobConfig.
*/
public function load()
{
return $this->config;
}
/**
* Clear the JobConfig from storage.
*/
public function clear()
{
$this->config = new JobConfig();
}
/**
* Hold the items in memory and run the job in the same process when it
* meets the condition.
*
* We want to delay registering the shutdown function. The error
* reporter also registers a shutdown function and the order matters.
*
* @see \Google\Cloud\ErrorReporting\Bootstrap::init()
* @see http://php.net/manual/en/function.register-shutdown-function.php
*
* @param mixed $item An item to submit.
* @param int $idNum A numeric id for the job.
* @return void
*/
public function submit($item, $idNum)
{
if (!$this->hasShutdownHookRegistered) {
\register_shutdown_function([$this, 'shutdown']);
$this->hasShutdownHookRegistered = \true;
}
if (!\array_key_exists($idNum, $this->items)) {
$this->items[$idNum] = [];
$this->lastInvoked[$idNum] = $this->created;
}
$this->items[$idNum][] = $item;
$job = $this->config->getJobFromIdNum($idNum);
$batchSize = $job->getBatchSize();
$period = $job->getCallPeriod();
if (\count($this->items[$idNum]) >= $batchSize || \count($this->items[$idNum]) !== 0 && \microtime(\true) > $this->lastInvoked[$idNum] + $period) {
$this->flush($idNum);
$this->items[$idNum] = [];
$this->lastInvoked[$idNum] = \microtime(\true);
}
}
/**
* Run the job with the given id.
*
* @param int $idNum A numeric id for the job.
* @return bool
*/
public function flush($idNum)
{
if (isset($this->items[$idNum])) {
$job = $this->config->getJobFromIdNum($idNum);
if (!$job->flush($this->items[$idNum])) {
$this->handleFailure($idNum, $this->items[$idNum]);
}
$this->items[$idNum] = [];
$this->lastInvoked[$idNum] = \microtime(\true);
}
return \true;
}
/**
* Run the job for remainder items.
*/
public function shutdown()
{
foreach ($this->items as $idNum => $items) {
if (\count($items) !== 0) {
$this->flush($idNum);
}
}
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* A trait to assist in handling interrupt signals and gracefully stopping work.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait InterruptTrait
{
private $shutdown = \false;
private function setupSignalHandlers()
{
// setup signal handlers
\pcntl_signal(\SIGTERM, [$this, "sigHandler"]);
\pcntl_signal(\SIGINT, [$this, "sigHandler"]);
\pcntl_signal(\SIGHUP, [$this, "sigHandler"]);
\pcntl_signal(\SIGALRM, [$this, "sigHandler"]);
}
/**
* A signal handler for setting the terminate switch.
*
* @see http://php.net/manual/en/function.pcntl-signal.php
*
* @param int $signo The received signal.
* @param mixed $siginfo [optional] An array representing the signal
* information. **Defaults to** null.
*
* @return void
*/
public function sigHandler($signo, $siginfo = null)
{
switch ($signo) {
case \SIGINT:
case \SIGTERM:
$this->shutdown = \true;
break;
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* Hold configurations for the {@see \Google\Cloud\Core\Batch\BatchRunner}.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class JobConfig
{
/**
* @var array Associative array of JobInterface instances keyed by
* identifier.
*/
private $jobs = [];
/**
* @var array[string]int Associative array of job identifier to job id.
*/
private $identifierToId = [];
/**
* @var array[int]string Associative array of job id to job identifier.
*/
private $idToIdentifier = [];
/**
* Get the job with the given identifier.
*
* @param string $identifier Unique identifier of the job.
*
* @return JobInterface|null
*/
public function getJobFromId($identifier)
{
return \array_key_exists($identifier, $this->identifierToId) ? $this->jobs[$identifier] : null;
}
/**
* Get the job with the given numeric id.
*
* @param int $idNum A numeric id of the job.
*
* @return JobInterface|null
*/
public function getJobFromIdNum($idNum)
{
return \array_key_exists($idNum, $this->idToIdentifier) ? $this->jobs[$this->idToIdentifier[$idNum]] : null;
}
/**
* Register a job for executing in batch.
*
* @param string $identifier Unique identifier of the job.
* @param callable $callback Callback that accepts the job $idNum
* and returns a JobInterface instance.
* @return void
*/
public function registerJob($identifier, $callback)
{
if (\array_key_exists($identifier, $this->identifierToId)) {
$idNum = $this->identifierToId[$identifier];
} else {
$idNum = \count($this->identifierToId) + 1;
$this->idToIdentifier[$idNum] = $identifier;
}
$this->jobs[$identifier] = \call_user_func($callback, $idNum);
$this->identifierToId[$identifier] = $idNum;
}
/**
* Get all the jobs indexed by the job's identifier.
*
* @return array Associative array of JobInterface instances keyed by a
* string identifier.
*/
public function getJobs()
{
return $this->jobs;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* The JobInterface represents any job that can be serialized and run in a
* separate process via the Batch daemon.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
interface JobInterface
{
/**
* Runs the job loop. This is expected to be a blocking call.
*/
public function run();
/**
* Return the job identifier
*
* @return string
*/
public function identifier();
/**
* Returns the number of workers for this job.
*
* @return int
*/
public function numWorkers();
/**
* Finish any pending activity for this job.
*
* @param array $items
* @return bool
*/
public function flush(array $items = []);
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* A trait to assist in implementing the JobInterface
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait JobTrait
{
/**
* @var string The job identifier
*/
private $identifier;
/**
* @var int The job id
*/
private $id;
/**
* @var int The number of workers for this job.
*/
private $numWorkers;
/**
* @var string|null An optional file that is required to run this job.
*/
private $bootstrapFile;
/**
* Return the job identifier
*
* @return string
*/
public function identifier()
{
return $this->identifier;
}
/**
* Return the job id
*
* @return int
*/
public function id()
{
return $this->id;
}
/**
* Returns the number of workers for this job. **Defaults to* 1.
*
* @return int
*/
public function numWorkers()
{
return $this->numWorkers;
}
/**
* Returns the optional file required to run this job.
*
* @return string|null
*/
public function bootstrapFile()
{
return $this->bootstrapFile;
}
/**
* Runs the job loop. This is expected to be a blocking call.
*/
public abstract function run();
/**
* Finish any pending activity for this job.
*
* @param array $items
* @return bool
*/
public function flush(array $items = [])
{
return \false;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Opis\Closure\SerializableClosure;
/**
* A closure serializer utilizing
* [Opis Closure Library](https://github.com/opis/closure).
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class OpisClosureSerializer implements ClosureSerializerInterface
{
/**
* Recursively serializes closures.
*
* @param mixed $data
*/
public function wrapClosures(&$data)
{
SerializableClosure::enterContext();
SerializableClosure::wrapClosures($data);
SerializableClosure::exitContext();
}
/**
* Recursively unserializes closures.
*
* @param mixed $data
*/
public function unwrapClosures(&$data)
{
SerializableClosure::enterContext();
SerializableClosure::unwrapClosures($data);
SerializableClosure::exitContext();
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* An interface for processing the items.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
interface ProcessItemInterface
{
/**
* Submit a job for async processing.
*
* @param mixed $item An item to submit.
* @param int $idNum A numeric id of the job.
* @return void
* @throws \RuntimeException when failed to store the item.
*/
public function submit($item, $idNum);
/**
* Run the job with the given id.
*
* @param int $idNum A numeric id of the job.
* @return bool
*/
public function flush($idNum);
}

View File

@@ -0,0 +1,35 @@
<?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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* Exception thrown in {@see \Google\Cloud\Core\Batch\SysvProcessor::submit()}
* method when it cannot add an item to the message queue.
* Possible causes include:
*
* - batch daemon is not running
* - no job registered for this queue
* - items are submitted faster than a job can handle them
*/
class QueueOverflowException extends \RuntimeException
{
public function __construct()
{
parent::__construct('Item queue overflow. Check that the batch daemon is running.');
}
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* A class for retrying the failed items.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class Retry
{
use HandleFailureTrait;
/* @var BatchRunner */
private $runner;
/**
* Initialize a BatchRunner and $failureFile.
*
* @param BatchRunner $runner [optional] **Defaults to** a new BatchRunner.
*/
public function __construct(BatchRunner $runner = null)
{
$this->runner = $runner ?: new BatchRunner();
$this->initFailureFile();
}
/**
* Retry all the failed items.
*/
public function retryAll()
{
foreach ($this->getFailedFiles() as $file) {
// Rename the file first
$tmpFile = \dirname($file) . '/retrying-' . \basename($file);
\rename($file, $tmpFile);
$fp = @\fopen($tmpFile, 'r');
if ($fp === \false) {
\fwrite(\STDERR, \sprintf('Could not open the file: %s' . \PHP_EOL, $tmpFile));
continue;
}
while ($line = \fgets($fp)) {
$jsonDecodedValue = \json_decode($line);
// Check if data json_encoded after serialization
if ($jsonDecodedValue !== null || $jsonDecodedValue !== \false) {
$line = $jsonDecodedValue;
}
$a = \unserialize($line);
$idNum = \key($a);
$job = $this->runner->getJobFromIdNum($idNum);
if (!$job->callFunc($a[$idNum])) {
$this->handleFailure($idNum, $a[$idNum]);
}
}
@\fclose($fp);
@\unlink($tmpFile);
}
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Opis\Closure\SerializableClosure;
/**
* A trait to assist in serializing/deserializing client configuration that may
* contain closures.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait SerializableClientTrait
{
/**
* @var array
*/
private $clientConfig;
/**
* @var ClosureSerializerInterface|null
*/
private $closureSerializer;
/**
* @param array $options {
* Configuration options.
*
* @type ClosureSerializerInterface $closureSerializer An implementation
* responsible for serializing closures used in the
* `$clientConfig`. This is especially important when using the
* batch daemon. **Defaults to**
* {@see \Google\Cloud\Core\Batch\OpisClosureSerializer} if the
* `opis/closure` library is installed.
* @type array $clientConfig A config used to construct the client upon
* which requests will be made.
* }
*/
private function setSerializableClientOptions(array $options)
{
$options += ['closureSerializer' => null, 'clientConfig' => []];
$this->closureSerializer = $options['closureSerializer'] ?? $this->getDefaultClosureSerializer();
$this->setWrappedClientConfig($options);
}
/**
* @param array $options
*/
private function setWrappedClientConfig(array $options)
{
$config = $options['clientConfig'] ?? [];
if ($config && $this->closureSerializer) {
$this->closureSerializer->wrapClosures($config);
}
$this->clientConfig = $config;
}
/**
* @return array
*/
private function getUnwrappedClientConfig()
{
if ($this->clientConfig && $this->closureSerializer) {
$this->closureSerializer->unwrapClosures($this->clientConfig);
}
return $this->clientConfig;
}
/**
* @return ClosureSerializerInterface|null
*/
private function getDefaultClosureSerializer()
{
if (\class_exists(SerializableClosure::class)) {
return new OpisClosureSerializer();
}
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* Represents a simple job that runs a single method that loops forever.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class SimpleJob implements JobInterface
{
use JobTrait;
/**
* @var callable
*/
private $func;
/**
* Creates a new Simple Job.
*
* @param string $identifier Unique identifier of the job.
* @param callable $func Any Callable except for Closure. The callable
* should not accept arguments and should loop forever.
* @param int $id The job id.
* @param array $options [optional] {
* Configuration options.
*
* @type string $bootstrapFile A file to load before executing the job.
* It's needed for registering global functions.
* @type int $numWorkers The number of workers for this job.
* }
*/
public function __construct($identifier, $func, $id, array $options = [])
{
$this->identifier = $identifier;
$this->func = $func;
$this->id = $id;
$options += ['bootstrapFile' => null, 'numWorkers' => 1];
$this->numWorkers = $options['numWorkers'];
$this->bootstrapFile = $options['bootstrapFile'];
}
/**
* Runs the job loop. This is expected to be a blocking call.
*/
public function run()
{
if ($this->bootstrapFile) {
require_once $this->bootstrapFile;
}
\call_user_func($this->func);
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* A trait to assist in the registering and processing of simple jobs.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
trait SimpleJobTrait
{
use BatchDaemonTrait;
use SysvTrait;
use SerializableClientTrait;
/**
* The simple loop function. This method is expected to be a blocking call.
*/
public abstract function run();
/**
* Registers this object as a SimpleJob.
*
* @param array $options [optional] {
* Configuration options.
*
* @type string $identifier An identifier for the simple job. This
* value must be unique across all job configs.
* @type ConfigStorageInterface $configStorage The configuration storage
* used to save configuration.
* @type int $numWorkers The number of workers for this job.
* @type array $clientConfig A config used to construct the client upon
* which requests will be made.
* @type ClosureSerializerInterface $closureSerializer An implementation
* responsible for serializing closures used in the
* `$clientConfig`. This is especially important when using the
* batch daemon. **Defaults to**
* {@see \Google\Cloud\Core\Batch\OpisClosureSerializer} if the
* `opis/closure` library is installed.
* }
*/
private function setSimpleJobProperties(array $options = [])
{
if (!isset($options['identifier'])) {
throw new \InvalidArgumentException('A valid identifier is required in order to register a job.');
}
$options += ['configStorage' => null];
$this->setSerializableClientOptions($options);
$identifier = $options['identifier'];
$configStorage = $options['configStorage'] ?: $this->defaultConfigStorage();
$result = $configStorage->lock();
if ($result === \false) {
return \false;
}
$config = $configStorage->load();
$config->registerJob($identifier, function ($id) use($identifier, $options) {
return new SimpleJob($identifier, [$this, 'run'], $id, $options);
});
try {
$result = $configStorage->save($config);
} finally {
$configStorage->unlock();
}
return $result;
}
private function defaultConfigStorage()
{
if ($this->isSysvIPCLoaded() && $this->isDaemonRunning()) {
return new SysvConfigStorage();
} else {
return InMemoryConfigStorage::getInstance();
}
}
}

View File

@@ -0,0 +1,148 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
/**
* ConfigStorageInterface implementation with SystemV IPC shared memory.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class SysvConfigStorage implements ConfigStorageInterface
{
const VAR_KEY = 1;
const DEFAULT_SHM_SIZE = 200000;
const DEFAULT_PERM = 0600;
const DEFAULT_PROJECT = 'A';
/* @var int */
private $sysvKey;
/* @var int */
private $semid;
/* @var int */
private $shmSize;
/* @var int */
private $perm;
/* @var string */
private $project;
/**
* Prepare the key for semaphore and shared memory.
*/
public function __construct()
{
$this->shmSize = \intval(\getenv('GOOGLE_CLOUD_BATCH_SHM_SIZE'));
if ($this->shmSize === 0) {
$this->shmSize = self::DEFAULT_SHM_SIZE;
}
$this->perm = \octdec(\getenv('GOOGLE_CLOUD_BATCH_PERM'));
if ($this->perm === 0) {
$this->perm = self::DEFAULT_PERM;
}
$this->project = \getenv('GOOGLE_CLOUD_BATCH_PROJECT');
if ($this->project === \false) {
$this->project = self::DEFAULT_PROJECT;
}
$this->sysvKey = \ftok(__FILE__, $this->project);
$this->semid = \sem_get($this->sysvKey, 1, $this->perm, 1);
}
/**
* Acquire a lock.
*
* @return bool
*/
public function lock()
{
return \sem_acquire($this->semid);
}
/**
* Release a lock.
*
* @return bool
*/
public function unlock()
{
return \sem_release($this->semid);
}
/**
* Save the given JobConfig.
*
* @param JobConfig $config A JobConfig to save.
* @return bool
* @throws \RuntimeException when failed to attach to the shared memory or serialization fails
*/
public function save(JobConfig $config)
{
$shmid = \shm_attach($this->sysvKey, $this->shmSize, $this->perm);
if ($shmid === \false) {
throw new \RuntimeException('Failed to attach to the shared memory');
}
// If the variable write fails, clear the memory and re-raise the exception
try {
$result = \shm_put_var($shmid, self::VAR_KEY, $config);
} catch (\Exception $e) {
$this->clear();
throw new \RuntimeException($e->getMessage());
} finally {
\shm_detach($shmid);
}
return $result;
}
/**
* Load a JobConfig from the storage.
*
* @return JobConfig
* @throws \RuntimeException when failed to attach to the shared memory or deserialization fails
*/
public function load()
{
$shmid = \shm_attach($this->sysvKey, $this->shmSize, $this->perm);
if ($shmid === \false) {
throw new \RuntimeException('Failed to attach to the shared memory');
}
if (!\shm_has_var($shmid, self::VAR_KEY)) {
$result = new JobConfig();
} else {
$result = \shm_get_var($shmid, self::VAR_KEY);
}
\shm_detach($shmid);
if ($result === \false) {
throw new \RuntimeException('Failed to deserialize data from shared memory');
}
return $result;
}
/**
* Clear the JobConfig from storage.
*/
public function clear()
{
$shmid = \shm_attach($this->sysvKey, $this->shmSize, $this->perm);
\shm_remove_var($shmid, self::VAR_KEY);
}
/**
* Serialize the object
*/
public function __serialize()
{
$vars = \get_object_vars($this);
// As of PHP 8.0, "semid" is the unserializable object "SysvSemaphore"
// @see https://github.com/googleapis/google-cloud-php/issues/3749
unset($vars['semid']);
return $vars;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Batch;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* ProcessItemInterface implementation with SysV IPC message queue.
*
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class SysvProcessor implements ProcessItemInterface
{
use BatchDaemonTrait;
use SysvTrait;
/* @var array */
private $sysvQs = [];
/**
* Submit an item for async processing.
*
* @param mixed $item An item to submit.
* @param int $idNum A numeric id of the job.
* @return void
*
* @throws \RuntimeException when failed to store the item.
*/
public function submit($item, $idNum)
{
if (!\array_key_exists($idNum, $this->sysvQs)) {
$this->sysvQs[$idNum] = \msg_get_queue($this->getSysvKey($idNum));
}
$result = @\msg_send($this->sysvQs[$idNum], self::$typeDirect, $item, \true, \false);
if ($result === \false) {
// Try to put the content in a temp file and send the filename.
$tempFile = \tempnam(\sys_get_temp_dir(), 'Item');
$result = \file_put_contents($tempFile, \serialize($item));
if ($result === \false) {
throw new \RuntimeException("Failed to write to {$tempFile} while submiting the item");
}
$result = @\msg_send($this->sysvQs[$idNum], self::$typeFile, $tempFile, \true, \false);
if ($result === \false) {
@\unlink($tempFile);
throw new QueueOverflowException();
}
}
}
/**
* Run the job with the given id. This has no effect and simply always
* returns false when using the batch daemon.
*
* @param int $idNum A numeric id of the job.
* @return bool
*/
public function flush($idNum)
{
return \false;
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\Utils;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\StreamInterface;
/**
* Represents a Blob value.
*
* Blobs can be used to store binary across data various Google Cloud services.
*
* Example:
* ```
* use Google\Cloud\Core\Blob;
*
* $blob = new Blob(file_get_contents(__DIR__ .'/family-photo.jpg'));
* ```
*
* ```
* // Get the value of a blob by casting to a string.
*
* echo (string) $blob;
* ```
*/
class Blob implements \JsonSerializable
{
/**
* @var mixed
*/
private $value;
/**
* Create a blob
*
* @param string|resource|StreamInterface $value The blob value
*/
public function __construct($value)
{
$this->value = Utils::streamFor($value);
}
/**
* Get the blob contents as a stream
*
* Example:
* ```
* $value = $blob->get();
* ```
*
* @return StreamInterface
*/
public function get()
{
return $this->value;
}
/**
* Cast the blob to a string
*
* @access private
* @return string
*/
public function __toString()
{
return (string) $this->value;
}
/**
* Implement JsonSerializable by returning a base64 encoded string of the blob
*
* @return string
* @access private
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return \base64_encode((string) $this->value);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Provide magic method support for fetching values from results
*/
trait CallTrait
{
/**
* @access private
*/
public function __call($name, array $args)
{
if (!isset($this->info()[$name])) {
\trigger_error(\sprintf('Call to undefined method %s::%s', __CLASS__, $name), \E_USER_ERROR);
}
return $this->info()[$name];
}
}

View File

@@ -0,0 +1,203 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\CredentialsLoader;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\Credentials\GCECredentials;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\GoogleException;
/**
* Provides functionality common to each service client.
*/
trait ClientTrait
{
use JsonTrait;
/**
* @var string|null The project ID created in the Google Developers Console.
*/
private $projectId;
/**
* Get either a gRPC or REST connection based on the provided config
* and the system dependencies available.
*
* @param array $config
* @return string
* @throws GoogleException
*/
private function getConnectionType(array $config)
{
$isGrpcExtensionLoaded = $this->isGrpcLoaded();
$defaultTransport = $isGrpcExtensionLoaded ? 'grpc' : 'rest';
$transport = \strtolower($config['transport'] ?? $defaultTransport);
if ($transport === 'grpc') {
if (!$isGrpcExtensionLoaded) {
throw new GoogleException('gRPC support has been requested but required dependencies ' . 'have not been found. ' . $this->getGrpcInstallationMessage());
}
}
return $transport;
}
/**
* Throw an exception if the gRPC extension is not loaded.
*
* @throws GoogleException
*/
private function requireGrpc()
{
if (!$this->isGrpcLoaded()) {
throw new GoogleException('The requested client requires the gRPC extension. ' . $this->getGrpcInstallationMessage());
}
}
/**
* @return string
*/
private function getGrpcInstallationMessage()
{
return 'Please see https://cloud.google.com/php/grpc for installation ' . 'instructions.';
}
/**
* Fetch and validate the keyfile and set the project ID.
*
* @param array $config
* @return array
* @throws GoogleException
*/
private function configureAuthentication(array $config)
{
$config['keyFile'] = $this->getKeyFile($config);
$this->projectId = $this->detectProjectId($config);
return $config;
}
/**
* Get a keyfile if it exists.
*
* Process:
* 1. If $config['keyFile'] is set, use that.
* 2. If $config['keyFilePath'] is set, load the file and use that.
* 3. If GOOGLE_APPLICATION_CREDENTIALS environment variable is set, load
* from that location and use that.
* 4. If OS-specific well-known-file is set, load from that location and use
* that.
*
* @param array $config
* @return array|null Key data
* @throws GoogleException
*/
private function getKeyFile(array $config = [])
{
$config += ['keyFile' => null, 'keyFilePath' => null];
if ($config['keyFile']) {
return $config['keyFile'];
}
if ($config['keyFilePath']) {
if (!\file_exists($config['keyFilePath'])) {
throw new GoogleException(\sprintf('Given keyfile path %s does not exist', $config['keyFilePath']));
}
try {
$keyFileData = $this->jsonDecode(\file_get_contents($config['keyFilePath']), \true);
} catch (\InvalidArgumentException $ex) {
throw new GoogleException(\sprintf('Given keyfile at path %s was invalid', $config['keyFilePath']));
}
return $keyFileData;
}
return CredentialsLoader::fromEnv() ?: CredentialsLoader::fromWellKnownFile();
}
/**
* Detect and return a project ID.
*
* Process:
* 1. If $config['projectId'] is set, use that.
* 2. If an emulator is enabled, return a dummy value.
* 3. If $config['keyFile'] is set, attempt to retrieve a project ID from
* that.
* 4. Check `GOOGLE_CLOUD_PROJECT` environment variable.
* 5. Check `GCLOUD_PROJECT` environment variable.
* 6. If code is running on compute engine, try to get the project ID from
* the metadata store.
* 7. Throw exception.
*
* @param array $config
* @return string
* @throws GoogleException
*/
private function detectProjectId(array $config)
{
$config += ['httpHandler' => null, 'projectId' => null, 'projectIdRequired' => \false, 'hasEmulator' => \false, 'preferNumericProjectId' => \false, 'suppressKeyFileNotice' => \false];
if ($config['projectId']) {
return $config['projectId'];
}
if ($config['hasEmulator']) {
return 'emulator-project';
}
if (isset($config['keyFile'])) {
if (isset($config['keyFile']['project_id'])) {
return $config['keyFile']['project_id'];
}
if ($config['suppressKeyFileNotice'] !== \true) {
$serviceAccountUri = 'https://cloud.google.com/iam/docs/' . 'creating-managing-service-account-keys#creating_service_account_keys';
\trigger_error(\sprintf('A keyfile was given, but it does not contain a project ' . 'ID. This can indicate an old and obsolete keyfile, ' . 'in which case you should create a new one. To suppress ' . 'this message, set `suppressKeyFileNotice` to `true` in your client configuration. ' . 'To learn more about generating new keys, see this URL: %s', $serviceAccountUri), \E_USER_NOTICE);
}
}
if (\getenv('GOOGLE_CLOUD_PROJECT')) {
return \getenv('GOOGLE_CLOUD_PROJECT');
}
if (\getenv('GCLOUD_PROJECT')) {
return \getenv('GCLOUD_PROJECT');
}
if ($this->onGce($config['httpHandler'])) {
$metadata = $this->getMetaData();
$projectId = $config['preferNumericProjectId'] ? $metadata->getNumericProjectId() : $metadata->getProjectId();
if ($projectId) {
return $projectId;
}
}
if ($config['projectIdRequired']) {
throw new GoogleException('No project ID was provided, ' . 'and we were unable to detect a default project ID.');
}
return '';
}
/**
* Abstract the GCECredentials call so we can mock it in the unit tests!
*
* @codeCoverageIgnore
* @return bool
*/
protected function onGce($httpHandler)
{
return GCECredentials::onGce($httpHandler);
}
/**
* Abstract the Metadata instantiation for unit testing
*
* @codeCoverageIgnore
* @return Metadata
*/
protected function getMetaData()
{
return new Metadata();
}
/**
* Abstract the checking of the grpc extension for unit testing.
*
* @codeCoverageIgnore
* @return bool
*/
protected function isGrpcLoaded()
{
return \extension_loaded('grpc');
}
}

View File

@@ -0,0 +1,153 @@
<?php
/**
* Copyright 2015 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata\Readers\HttpHandlerReader;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata\Readers\ReaderInterface;
/**
* A library for accessing the Google Compute Engine (GCE) metadata.
*
* The metadata is available from Google Compute Engine instances and
* App Engine Managed VMs instances.
*
* Example:
* ```
* use Google\Cloud\Core\Compute\Metadata;
*
* $metadata = new Metadata();
* $projectId = $metadata->getProjectId();
* ```
*
* ```
* // It is easy to get any metadata from a project.
* $val = $metadata->getProjectMetadata($key);
* ```
*/
class Metadata
{
/**
* @var ReaderInterface The metadata reader.
*/
private $reader;
/**
* @var string The project id.
*/
private $projectId;
/**
* @var int The numeric project id.
*/
private $numericProjectId;
/**
* @param ReaderInterface $reader [optional] A metadata reader implementation.
*/
public function __construct(ReaderInterface $reader = null)
{
$this->reader = $reader ?: new HttpHandlerReader();
}
/**
* Replace the default reader implementation
*
* @deprecated If a custom reader implementation is desired, provide it at
* construction.
* @param ReaderInterface $reader The reader implementation
*/
public function setReader(ReaderInterface $reader)
{
$this->reader = $reader;
}
/**
* Fetch a metadata item by its path
*
* Example:
* ```
* $projectId = $metadata->get('project/project-id');
* ```
*
* @param string $path The path of the item to retrieve.
*/
public function get($path)
{
return $this->reader->read($path);
}
/**
* Detect and return the project ID
*
* Example:
* ```
* $projectId = $metadata->getProjectId();
* ```
*
* @return string
*/
public function getProjectId()
{
if (!isset($this->projectId)) {
$this->projectId = $this->get('project/project-id');
}
return $this->projectId;
}
/**
* Detect and return the numeric project ID
*
* Example:
* ```
* $projectId = $metadata->getNumericProjectId();
* ```
*
* @return string
*/
public function getNumericProjectId()
{
if (!isset($this->numericProjectId)) {
$this->numericProjectId = $this->get('project/numeric-project-id');
}
return $this->numericProjectId;
}
/**
* Fetch an item from the project metadata
*
* Example:
* ```
* $foo = $metadata->getProjectMetadata('foo');
* ```
*
* @param string $key The metadata key
* @return string
*/
public function getProjectMetadata($key)
{
$path = 'project/attributes/' . $key;
return $this->get($path);
}
/**
* Fetch an item from the instance metadata
*
* Example:
* ```
* $foo = $metadata->getInstanceMetadata('foo');
* ```
*
* @param string $key The instance metadata key
* @return string
*/
public function getInstanceMetadata($key)
{
$path = 'instance/attributes/' . $key;
return $this->get($path);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/**
* Copyright 2019 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata\Readers;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\Credentials\GCECredentials;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpClientCache;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpHandlerFactory;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\Request;
/**
* Read Compute Metadata using the HTTP Handler utility.
*/
class HttpHandlerReader implements ReaderInterface
{
/**
* @var callable
*/
private $httpHandler;
/**
* @param callable $httpHandler [optional] An HTTP Handler capable of
* accepting PSR7 requests and returning PSR7 responses.
*/
public function __construct(callable $httpHandler = null)
{
$this->httpHandler = $httpHandler ?: HttpHandlerFactory::build(HttpClientCache::getHttpClient());
}
/**
* Read the metadata for a given path.
*
* @param string $path The metadata path, relative to `/computeMetadata/v1/`.
* @return string
*/
public function read($path)
{
$url = \sprintf('http://%s/computeMetadata/v1/%s', GCECredentials::METADATA_IP, $path);
$request = new Request('GET', $url, [GCECredentials::FLAVOR_HEADER => 'Google']);
$handler = $this->httpHandler;
$res = $handler($request);
return (string) $res->getBody();
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata\Readers;
/**
* Defines a metadata reader implementation.
*/
interface ReaderInterface
{
/**
* Read metadata from a given path.
*/
public function read($path);
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* Copyright 2015 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata\Readers;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\Credentials\GCECredentials;
/**
* A class only reading the metadata URL with an appropriate header.
*
* This class makes it easy to test the MetadataStream class.
*/
class StreamReader implements ReaderInterface
{
/**
* The base PATH for the metadata.
*
* @deprecated
*/
const BASE_URL = 'http://169.254.169.254/computeMetadata/v1/';
/**
* The header whose presence indicates GCE presence.
*
* @deprecated
*/
const FLAVOR_HEADER = 'Metadata-Flavor: Google';
/**
* A common context for this reader.
*/
private $context;
/**
* We create the common context in the constructor.
*/
public function __construct()
{
$options = ['http' => ['method' => 'GET', 'header' => GCECredentials::FLAVOR_HEADER . ': Google']];
$this->context = $this->createStreamContext($options);
}
/**
* Read the metadata for a given path.
*
* @param string $path The metadata path, relative to `/computeMetadata/v1/`.
* @return string
*/
public function read($path)
{
$url = \sprintf('http://%s/computeMetadata/v1/%s', GCECredentials::METADATA_IP, $path);
return $this->getMetadata($url);
}
/**
* Abstracted for testing.
*
* @param array $options
* @return resource
* @codeCoverageIgnore
*/
protected function createStreamContext(array $options)
{
return \stream_context_create($options);
}
/**
* Abstracted for testing.
*
* @param string $url
* @return string
* @codeCoverageIgnore
*/
protected function getMetadata($url)
{
return \file_get_contents($url, \false, $this->context);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Methods to control concurrent updates.
*/
trait ConcurrencyControlTrait
{
/**
* Apply the If-Match header to requests requiring concurrency control.
*
* @param array $options
* @param string $argName
* @return array
*/
private function applyEtagHeader(array $options, $argName = 'etag')
{
if (isset($options[$argName])) {
if (!isset($options['restOptions'])) {
$options['restOptions'] = [];
}
if (!isset($options['restOptions']['headers'])) {
$options['restOptions']['headers'] = [];
}
$options['restOptions']['headers']['If-Match'] = $options[$argName];
}
return $options;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Provides easier to read debug information when dumping a class to stdout.
*
* @codeCoverageIgnore
*/
trait DebugInfoTrait
{
/**
* @access private
* @return array
*/
public function __debugInfo()
{
$props = \get_object_vars($this);
if (isset($this->connection)) {
$props['connection'] = \get_class($this->connection);
}
if (isset($props['__excludeFromDebug'])) {
$exclude = $props['__excludeFromDebug'];
unset($props['__excludeFromDebug']);
foreach ($exclude as $e) {
unset($props[$e]);
}
}
return $props;
}
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* Copyright 2024 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\ProjectIdProviderInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\GoogleException;
/**
* @internal
* Provides functionality common to handwritten clients.
*/
trait DetectProjectIdTrait
{
/**
* @var string|null The project ID created in the Google Developers Console.
*/
private $projectId;
/**
* Detect and return a project ID.
* This is different from detectProjectId as this method is supposed to be used by
* handwritten clients delegating their auth process to GAX.
*
* Process:
* 1. If $config['projectId'] is set, use that.
* 2. If an emulator is enabled, return a dummy value.
* 3. If $config['credentials'] is set, attempt to retrieve a project ID from
* that.
* 4. If code is running on compute engine, try to get the project ID from
* the metadata store.
* 5. Throw exception.
*
* @param array $config
* @return string|int|null
* @throws GoogleException
*/
private function detectProjectId(array $config)
{
$config += ['httpHandler' => null, 'projectId' => null, 'projectIdRequired' => \false, 'hasEmulator' => \false, 'credentials' => null];
if ($config['projectId']) {
return $config['projectId'];
}
if ($config['hasEmulator']) {
return 'emulator-project';
}
if ($config['credentials'] && $config['credentials'] instanceof ProjectIdProviderInterface && ($projectId = $config['credentials']->getProjectId())) {
return $projectId;
}
if (\getenv('GOOGLE_CLOUD_PROJECT')) {
return \getenv('GOOGLE_CLOUD_PROJECT');
}
if (\getenv('GCLOUD_PROJECT')) {
return \getenv('GCLOUD_PROJECT');
}
$this->throwExceptionIfProjectIdRequired($config);
return '';
}
/**
* Throws an exception if project id is required.
* @param array $config
* @throws GoogleException
*/
private function throwExceptionIfProjectIdRequired(array $config)
{
if ($config['projectIdRequired']) {
throw new GoogleException('No project ID was provided, ' . 'and we were unable to detect a default project ID.');
}
}
}

View File

@@ -0,0 +1,95 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Represents a Duration type.
*
* Example:
* ```
* use Google\Cloud\Core\Duration;
*
* $seconds = 100;
* $nanoSeconds = 1000;
* $duration = new Duration($seconds, $nanoSeconds);
* ```
*
* ```
* // Duration objects can be cast to json-encoded strings.
* echo (string) $duration;
* ```
*/
class Duration
{
const TYPE = 'DURATION';
/**
* @var int
*/
private $seconds;
/**
* @var int
*/
private $nanos;
/**
* @param int $seconds The number of seconds in the duration.
* @param int $nanos [optional] The number of nanoseconds in the duration.
*/
public function __construct($seconds, $nanos = 0)
{
$this->seconds = $seconds;
$this->nanos = $nanos;
}
/**
* Get the duration
*
* Example:
* ```
* $res = $duration->get();
* ```
*
* @return array
*/
public function get()
{
return ['seconds' => $this->seconds, 'nanos' => $this->nanos];
}
/**
* Format the value as a string.
*
* Example:
* ```
* echo $duration->formatAsString();
* ```
*
* @return string
*/
public function formatAsString()
{
return \json_encode($this->get());
}
/**
* Format the value as a string.
*
* @return string
* @access private
*/
public function __toString()
{
return $this->formatAsString();
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Provides common logic for configuring the usage of an emulator.
*/
trait EmulatorTrait
{
/**
* Configure the gapic configuration to use a service emulator.
*
* @param string $emulatorHost
* @return array
*/
private function emulatorGapicConfig($emulatorHost)
{
// Strip the URL scheme from the input, if it was provided.
if ($scheme = \parse_url($emulatorHost, \PHP_URL_SCHEME)) {
$search = $scheme . '://';
$emulatorHost = \str_replace($search, '', $emulatorHost);
}
return ['apiEndpoint' => $emulatorHost, 'transportConfig' => ['grpc' => ['stubOpts' => ['credentials' => \Grpc\ChannelCredentials::createInsecure()]]], 'credentials' => new InsecureCredentialsWrapper()];
}
/**
* Retrieve a valid base uri for a service emulator.
*
* @param string $emulatorHost
* @return string
*/
private function emulatorBaseUri($emulatorHost)
{
$emulatorUriComponents = \parse_url($emulatorHost);
$emulatorUriComponents = \array_merge(['scheme' => 'http', 'port' => ''], $emulatorUriComponents);
$baseUri = "{$emulatorUriComponents['scheme']}://{$emulatorUriComponents['host']}";
$baseUri .= $emulatorUriComponents['port'] ? ":{$emulatorUriComponents['port']}/" : '/';
return $baseUri;
}
/**
* When emulators are enabled, use them as the service host.
*
* This method is deprecated and will be removed in a future major release.
*
* @param string $baseUri
* @param string $emulatorHost [optional]
* @return string
*
* @deprecated
* @access private
*/
public function getEmulatorBaseUri($baseUri, $emulatorHost = null)
{
if ($emulatorHost) {
$baseUri = $this->emulatorBaseUri($emulatorHost);
}
return $baseUri;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a transaction is aborted.
*/
class AbortedException extends ServiceException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a request fails due to an error in the request.
* In REST context, this exception indicates a status code 400.
*/
class BadRequestException extends ServiceException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a request fails due to a conflict.
* In REST context, this exception indicates a status code 409.
*/
class ConflictException extends ServiceException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a request takes too long to complete.
* In REST context, this exception indicates a status code 504.
*/
class DeadlineExceededException extends ServiceException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a request fails due to a failed precondition.
* In REST context, this exception indicates a status code 412.
*/
class FailedPreconditionException extends ServiceException
{
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
use Exception;
/**
* Exception thrown when a request fails.
*/
class GoogleException extends Exception
{
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a resource is not found.
*/
class NotFoundException extends ServiceException
{
/**
* Allows overriding message for injection of Whitelist Notice.
*
* @param string $message the new message
* @return void
* @access private
*/
public function setMessage($message)
{
$this->message = $message;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
/**
* Exception thrown when a request fails due to an error on the server.
* In REST context, this exception indicates a status code 500.
*/
class ServerException extends ServiceException
{
}

View File

@@ -0,0 +1,172 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
use Exception;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\ApiException;
/**
* Exception thrown when a request fails.
*/
class ServiceException extends GoogleException
{
// This is the value of the '@type' key for an ErrorInfo object returned
// in the exception response
const ERRORINFO_TYPE_REST = 'type.googleapis.com/google.rpc.ErrorInfo';
/**
* @var Exception|null
*/
private $serviceException;
/**
* @var array
*/
protected $metadata;
/**
* @var array|null
*/
private $errorInfo;
/**
* @var array|null
*/
private $errorInfoMetadata;
/**
* @var string|null
*/
private $errorReason;
/**
* Handle previous exceptions differently here.
*
* @param string|null $message
* @param int $code
* @param Exception|null $serviceException
* @param array $metadata [optional] Exception metadata.
*/
public function __construct($message = null, $code = 0, Exception $serviceException = null, array $metadata = [])
{
$this->serviceException = $serviceException;
$this->metadata = $metadata;
$this->errorInfo = null;
$this->errorInfoMetadata = null;
$this->errorReason = null;
parent::__construct($message ?: '', $code);
}
/**
* If $serviceException is set, return true.
*
* @return bool
*/
public function hasServiceException()
{
return (bool) $this->serviceException;
}
/**
* Return the service exception object.
*
* @return Exception|null
*/
public function getServiceException()
{
return $this->serviceException;
}
/**
* Get exception metadata.
*/
public function getMetadata()
{
return $this->metadata;
}
/**
* Returns the metadata from the ErrorInfo part of the exception
*
* @return array
*/
public function getErrorInfoMetadata()
{
// Only calc the metadata if we haven't cached it
if (\is_null($this->errorInfoMetadata)) {
// For response originated from the GAPIC layer, the current exception would have
// an ApiException within itself
if ($this->getServiceException() instanceof ApiException) {
return $this->getServiceException()->getErrorInfoMetadata();
}
$errorInfo = $this->getErrorInfoFromRestException();
// Cache the result to be reused if needed
$this->errorInfoMetadata = $errorInfo['metadata'] ?? [];
}
return $this->errorInfoMetadata;
}
/**
* Returns the reason from the ErrorInfo part of the exception
*
* @return string
*/
public function getReason()
{
// Only calc the errorReason if we haven't cached it
if (\is_null($this->errorReason)) {
// For a response originated from the GAPIC layer, the current exception would have
// an ApiException within itself
if ($this->getServiceException() instanceof ApiException) {
return $this->getServiceException()->getReason();
}
$errorInfo = $this->getErrorInfoFromRestException();
// Cache the result to be reused if needed
$this->errorReason = $errorInfo['reason'] ?? '';
}
return $this->errorReason;
}
/**
* Return the delay in seconds and nanos before retrying the failed request.
*
* @return array
*/
public function getRetryDelay()
{
$metadata = \array_filter($this->metadata, function ($metadataItem) {
return \array_key_exists('retryDelay', $metadataItem);
});
if (\count($metadata) === 0) {
return ['seconds' => 0, 'nanos' => 0];
}
return $metadata[0]['retryDelay'] + ['seconds' => 0, 'nanos' => 0];
}
/**
* Helper to return the error info from an exception
* which is not raised from the GAPIC layer
*
* @return array
*/
private function getErrorInfoFromRestException()
{
// Only calc errorInfo if it isn't cached
if (\is_null($this->errorInfo)) {
$this->errorInfo = [];
$arr = \json_decode($this->getMessage(), \true);
if (!isset($arr['error']['details'])) {
return $this->errorInfo;
}
foreach ($arr['error']['details'] as $row) {
if (isset($row['@type']) && $row['@type'] === self::ERRORINFO_TYPE_REST) {
// save this in cache
$this->errorInfo = $row;
break;
}
}
}
return $this->errorInfo;
}
}

View File

@@ -0,0 +1,139 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Exponential backoff implementation.
*/
class ExponentialBackoff
{
const MAX_DELAY_MICROSECONDS = 60000000;
/**
* @var int
*/
private $retries;
/**
* @var callable|null
*/
private $retryFunction;
/**
* @var callable
*/
private $delayFunction;
/**
* @var callable|null
*/
private $calcDelayFunction;
/**
* @var callable|null
*/
private $retryListener;
/**
* @param int $retries [optional] Number of retries for a failed request.
* @param callable $retryFunction [optional] returns bool for whether or not
* to retry
* @param callable $retryListener [optional] Runs after the
* $retryFunction. Unlike the $retryFunction,this function isn't
* responsible to decide if a retry should happen or not, but it gives the
* users flexibility to consume exception messages and add custom logic.
* Function definition should match:
* function (\Exception $e, int $attempt, array $arguments): array
* Ex: One might want to change headers on every retry, this function can
* be used to achieve such a functionality.
*/
public function __construct($retries = null, callable $retryFunction = null, callable $retryListener = null)
{
$this->retries = $retries !== null ? (int) $retries : 3;
$this->retryFunction = $retryFunction;
$this->retryListener = $retryListener;
// @todo revisit this approach
// @codeCoverageIgnoreStart
$this->delayFunction = static function ($delay) {
\usleep($delay);
};
// @codeCoverageIgnoreEnd
}
/**
* Executes the retry process.
*
* @param callable $function
* @param array $arguments [optional]
* @return mixed
* @throws \Exception The last exception caught while retrying.
*/
public function execute(callable $function, array $arguments = [])
{
$delayFunction = $this->delayFunction;
$calcDelayFunction = $this->calcDelayFunction ?: [$this, 'calculateDelay'];
$retryAttempt = 0;
$exception = null;
while (\true) {
try {
return \call_user_func_array($function, $arguments);
} catch (\Exception $exception) {
if ($this->retryFunction) {
if (!\call_user_func($this->retryFunction, $exception, $retryAttempt)) {
throw $exception;
}
}
if ($retryAttempt >= $this->retries) {
break;
}
$delayFunction($calcDelayFunction($retryAttempt));
$retryAttempt++;
if ($this->retryListener) {
// Developer can modify the $arguments using the retryListener
// callback.
\call_user_func_array($this->retryListener, [$exception, $retryAttempt, &$arguments]);
}
}
}
throw $exception;
}
/**
* If not set, defaults to using `usleep`.
*
* @param callable $delayFunction
* @return void
*/
public function setDelayFunction(callable $delayFunction)
{
$this->delayFunction = $delayFunction;
}
/**
* If not set, defaults to using
* {@see \Google\Cloud\Core\ExponentialBackoff::calculateDelay()}.
*
* @param callable $calcDelayFunction
* @return void
*/
public function setCalcDelayFunction(callable $calcDelayFunction)
{
$this->calcDelayFunction = $calcDelayFunction;
}
/**
* Calculates exponential delay.
*
* @param int $attempt The attempt number used to calculate the delay.
* @return int
*/
public static function calculateDelay($attempt)
{
return \min(\mt_rand(0, 1000000) + \pow(2, $attempt) * 1000000, self::MAX_DELAY_MICROSECONDS);
}
}

View File

@@ -0,0 +1,201 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use InvalidArgumentException;
/**
* Represents a geographical point.
*
* Unless specified otherwise, this must conform to the
* [WGS84](http://www.unoosa.org/pdf/icg/2012/template/WGS_84.pdf) standard.
* Values must be within normalized ranges.
*
* Example:
* ```
* use Google\Cloud\Core\GeoPoint;
*
* $point = new GeoPoint(37.423147, -122.085015);
* ```
*/
class GeoPoint implements \JsonSerializable
{
/**
* @var float
*/
private $latitude;
/**
* @var float
*/
private $longitude;
/**
* Create a GeoPoint.
*
* Ints will be converted to floats. Values not passing the `is_numeric()`
* check will result in an exception.
*
* @param float|int|null $latitude The GeoPoint Latitude. **Note** that
* `null` is not a generally valid value, and will throw an
* `InvalidArgumentException` unless `$allowNull` is set to `true`.
* @param float|int|null $longitude The GeoPoint Longitude. **Note** that
* `null` is not a generally valid value, and will throw an
* `InvalidArgumentException` unless `$allowNull` is set to `true`.
* @param bool $allowNull [optional] If true, null values will be allowed
* in the constructor only. This switch exists to handle a rare case
* wherein a geopoint may be empty and is not intended for use from
* outside the client. **Defaults to** `false`.
* @throws \InvalidArgumentException
*/
public function __construct($latitude, $longitude, $allowNull = \false)
{
$this->latitude = $this->validateValue($latitude, 'latitude', $allowNull);
$this->longitude = $this->validateValue($longitude, 'longitude', $allowNull);
}
/**
* Get the latitude
*
* Example:
* ```
* $latitude = $point->latitude();
* ```
*
* @return float|null
*/
public function latitude()
{
$this->checkContext('latitude', \func_get_args());
return $this->latitude;
}
/**
* Set the latitude
*
* Non-numeric values will result in an exception
*
* Example:
* ```
* $point->setLatitude(42.279594);
* ```
*
* @param int|float $latitude The new latitude
* @return GeoPoint
* @throws \InvalidArgumentException
*/
public function setLatitude($latitude)
{
$this->latitude = $this->validateValue($latitude, 'latitude');
return $this;
}
/**
* Get the longitude
*
* Example:
* ```
* $longitude = $point->longitude();
* ```
*
* @return float|null
*/
public function longitude()
{
$this->checkContext('longitude', \func_get_args());
return $this->longitude;
}
/**
* Set the longitude
*
* Non-numeric values will result in an exception.
*
* Example:
* ```
* $point->setLongitude(-83.732124);
* ```
*
* @param float|int $longitude The new longitude value
* @return GeoPoint
* @throws \InvalidArgumentException
*/
public function setLongitude($longitude)
{
$this->longitude = $this->validateValue($longitude, 'longitude');
return $this;
}
/**
* Return a GeoPoint
*
* Example:
* ```
* $point = $point->point();
* ```
*
* @return array [LatLng](https://cloud.google.com/datastore/reference/rest/Shared.Types/LatLng)
*/
public function point()
{
return ['latitude' => $this->latitude, 'longitude' => $this->longitude];
}
/**
* Let people know if they accidentally use the getter in setter context.
*
* @param string $method the method name
* @param array $args The method arguments
* @throws \InvalidArgumentException
* @return void
*/
private function checkContext($method, array $args)
{
if (\count($args) > 0) {
throw new InvalidArgumentException(\sprintf('Calling method %s with arguments is unsupported.', $method));
}
}
/**
* Check a given value's validity as a coordinate.
*
* Numeric values will be cast to type `float`. All other values will raise
* an exception with the exception of `null`, if `$allowNull` is set to true.
*
* @param mixed $value The coordinate value.
* @param string $type The coordinate type for error reporting.
* @param bool $allowNull [optional] Whether null values should be allowed.
* **Defaults to** `false`.
* @return float|null
*/
private function validateValue($value, $type, $allowNull = \false)
{
if (!\is_numeric($value) && (!$allowNull || $allowNull && $value !== null)) {
throw new InvalidArgumentException(\sprintf('Given %s must be a numeric value.', $type));
}
return $allowNull && $value === null ? $value : (float) $value;
}
/**
* Implement JsonSerializable by representing GeoPoint as a JSON-object:
*
* ```
* {
* latitude: 31.778333
* longitude: 35.229722
* }
* ```
*
* @return object
* @access private
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return (object) $this->point();
}
}

View File

@@ -0,0 +1,119 @@
<?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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpHandlerFactory;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\ApiException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\Serializer;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Rpc\Code;
/**
* The GrpcRequestWrapper is responsible for delivering gRPC requests.
*/
class GrpcRequestWrapper
{
use RequestWrapperTrait;
use RequestProcessorTrait;
/**
* @var callable A handler used to deliver Psr7 requests specifically for
* authentication.
*/
private $authHttpHandler;
/**
* @var Serializer A serializer used to encode responses.
*/
private $serializer;
/**
* @var array gRPC specific configuration options passed off to the ApiCore
* library.
*/
private $grpcOptions;
/**
* @var array gRPC retry codes.
*/
private $grpcRetryCodes = [Code::UNKNOWN, Code::INTERNAL, Code::UNAVAILABLE, Code::DATA_LOSS];
/**
* @param array $config [optional] {
* Configuration options. Please see
* {@see \Google\Cloud\Core\RequestWrapperTrait::setCommonDefaults()} for
* the other available options.
*
* @type callable $authHttpHandler A handler used to deliver Psr7
* requests specifically for authentication.
* @type Serializer $serializer A serializer used to encode responses.
* @type array $grpcOptions gRPC specific configuration options passed
* off to the ApiCore library.
* }
*/
public function __construct(array $config = [])
{
$this->setCommonDefaults($config);
$config += ['authHttpHandler' => null, 'serializer' => new Serializer(), 'grpcOptions' => []];
$this->authHttpHandler = $config['authHttpHandler'] ?: HttpHandlerFactory::build();
$this->serializer = $config['serializer'];
$this->grpcOptions = $config['grpcOptions'];
}
/**
* Deliver the request.
*
* @param callable $request The request to execute.
* @param array $args The arguments for the request.
* @param array $options [optional] {
* Request options.
*
* @type float $requestTimeout Seconds to wait before timing out the
* request. **Defaults to** `60`.
* @type int $retries Number of retries for a failed request.
* **Defaults to** `3`.
* @type callable $grpcRetryFunction Sets the conditions for whether or
* not a request should attempt to retry. Function signature should
* match: `function (\Exception $ex) : bool`.
* @type array $grpcOptions gRPC specific configuration options.
* }
* @return array
* @throws Exception\ServiceException
*/
public function send(callable $request, array $args, array $options = [])
{
$retries = $options['retries'] ?? $this->retries;
$retryFunction = $options['grpcRetryFunction'] ?? function (\Exception $ex) {
$statusCode = $ex->getCode();
return \in_array($statusCode, $this->grpcRetryCodes);
};
$grpcOptions = $options['grpcOptions'] ?? $this->grpcOptions;
$timeout = $options['requestTimeout'] ?? $this->requestTimeout;
$backoff = new ExponentialBackoff($retries, $retryFunction);
if (!isset($grpcOptions['retrySettings'])) {
$retrySettings = ['retriesEnabled' => \false];
if ($timeout) {
$retrySettings['noRetriesRpcTimeoutMillis'] = $timeout * 1000;
}
$grpcOptions['retrySettings'] = $retrySettings;
}
$optionalArgs =& $args[\count($args) - 1];
$optionalArgs += $grpcOptions;
try {
return $this->handleResponse($backoff->execute($request, $args));
} catch (\Exception $ex) {
if ($ex instanceof ApiException) {
throw $this->convertToGoogleException($ex);
}
throw $ex;
}
}
}

View File

@@ -0,0 +1,265 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\GetUniverseDomainInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\CredentialsWrapper;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\NotFoundException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\GrpcRequestWrapper;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Protobuf\NullValue;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Duration;
/**
* Provides shared functionality for gRPC service implementations.
*/
trait GrpcTrait
{
use WhitelistTrait;
use ArrayTrait;
/**
* @var GrpcRequestWrapper Wrapper used to handle sending requests to the
* gRPC API.
*/
private $requestWrapper;
/**
* Sets the request wrapper.
*
* @param GrpcRequestWrapper $requestWrapper
*/
public function setRequestWrapper(GrpcRequestWrapper $requestWrapper)
{
$this->requestWrapper = $requestWrapper;
}
/**
* Get the GrpcRequestWrapper.
*
* @return GrpcRequestWrapper|null
*/
public function requestWrapper()
{
return $this->requestWrapper;
}
/**
* Delivers a request.
*
* @param callable $request
* @param array $args
* @param bool $whitelisted
* @return \Generator|array
* @throws ServiceException
*/
public function send(callable $request, array $args, $whitelisted = \false)
{
$requestOptions = $this->pluckArray(['grpcOptions', 'retries', 'requestTimeout', 'grpcRetryFunction'], $args[\count($args) - 1]);
try {
return $this->requestWrapper->send($request, $args, $requestOptions);
} catch (NotFoundException $e) {
if ($whitelisted) {
throw $this->modifyWhitelistedError($e);
}
throw $e;
}
}
/**
* Gets the default configuration for generated clients.
*
* @param string $version
* @param callable|null $authHttpHandler
* @param string|null $universeDomain
* @return array
*/
private function getGaxConfig($version, callable $authHttpHandler = null, string $universeDomain = null)
{
$config = ['libName' => 'gccl', 'libVersion' => $version, 'transport' => 'grpc'];
// GAX v0.32.0 introduced the CredentialsWrapper class and a different
// way to configure credentials. If the class exists, use this new method
// otherwise default to legacy usage.
if (\class_exists(CredentialsWrapper::class)) {
$config['credentials'] = new CredentialsWrapper(
$this->requestWrapper->getCredentialsFetcher(),
$authHttpHandler,
// If the universe domain hasn't been explicitly set, check the the environment variable,
// otherwise assume GDU ("googleapis.com").
($universeDomain ?: \getenv('GOOGLE_CLOUD_UNIVERSE_DOMAIN')) ?: GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN
);
} else {
$config += ['credentialsLoader' => $this->requestWrapper->getCredentialsFetcher(), 'authHttpHandler' => $authHttpHandler, 'enableCaching' => \false];
}
return $config;
}
use TimeTrait;
/**
* Format a struct for the API.
*
* @param array $fields
* @return array
*/
private function formatStructForApi(array $fields)
{
$fFields = [];
foreach ($fields as $key => $value) {
$fFields[$key] = $this->formatValueForApi($value);
}
return ['fields' => $fFields];
}
private function unpackStructFromApi(array $struct)
{
$vals = [];
foreach ($struct['fields'] as $key => $val) {
$vals[$key] = $this->unpackValue($val);
}
return $vals;
}
private function unpackValue($value)
{
if (\count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: {$value}");
}
foreach ($value as $setField => $setValue) {
switch ($setField) {
case 'listValue':
$valueList = [];
foreach ($setValue['values'] as $innerValue) {
$valueList[] = $this->unpackValue($innerValue);
}
return $valueList;
case 'structValue':
return $this->unpackStructFromApi($value['structValue']);
default:
return $setValue;
}
}
}
private function flattenStruct(array $struct)
{
return $struct['fields'];
}
private function flattenValue(array $value)
{
if (\count($value) > 1) {
throw new \RuntimeException("Unexpected fields in struct: {$value}");
}
if (isset($value['nullValue'])) {
return null;
}
return \array_pop($value);
}
private function flattenListValue(array $value)
{
return $value['values'];
}
/**
* Format a list for the API.
*
* @param array $list
* @return array
*/
private function formatListForApi(array $list)
{
$values = [];
foreach ($list as $value) {
$values[] = $this->formatValueForApi($value);
}
return ['values' => $values];
}
/**
* Format a value for the API.
*
* @param mixed $value
* @return array
*/
private function formatValueForApi($value)
{
$type = \gettype($value);
switch ($type) {
case 'string':
return ['string_value' => $value];
case 'double':
case 'integer':
return ['number_value' => $value];
case 'boolean':
return ['bool_value' => $value];
case 'NULL':
return ['null_value' => NullValue::NULL_VALUE];
case 'array':
if (!empty($value) && $this->isAssoc($value)) {
return ['struct_value' => $this->formatStructForApi($value)];
}
return ['list_value' => $this->formatListForApi($value)];
}
return [];
}
/**
* Format a gRPC timestamp to match the format returned by the REST API.
*
* @param array $timestamp
* @return string
*/
private function formatTimestampFromApi(array $timestamp)
{
$timestamp += ['seconds' => 0, 'nanos' => 0];
$dt = $this->createDateTimeFromSeconds($timestamp['seconds']);
return $this->formatTimeAsString($dt, $timestamp['nanos']);
}
/**
* Format a timestamp for the API with nanosecond precision.
*
* @param string $value
* @return array
*/
private function formatTimestampForApi($value)
{
list($dt, $nanos) = $this->parseTimeString($value);
return ['seconds' => (int) $dt->format('U'), 'nanos' => (int) $nanos];
}
/**
* Format a duration for the API.
*
* @param string|mixed $value
* @return array
*/
private function formatDurationForApi($value)
{
if (\is_string($value)) {
$d = \explode('.', \trim($value, 's'));
if (\count($d) < 2) {
$seconds = $d[0];
$nanos = 0;
} else {
$seconds = (int) $d[0];
$nanos = $this->convertFractionToNanoSeconds($d[1]);
}
} elseif ($value instanceof Duration) {
$d = $value->get();
$seconds = $d['seconds'];
$nanos = $d['nanos'];
}
return ['seconds' => $seconds, 'nanos' => $nanos];
}
/**
* Construct a gapic client. Allows for tests to intercept.
*
* @param string $gapicName
* @param array $config
* @return mixed
*/
protected function constructGapic($gapicName, array $config)
{
return new $gapicName($config);
}
}

View File

@@ -0,0 +1,186 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iam;
/**
* IAM Manager
*
* This class is not meant to be used directly. It should be accessed
* through other objects which support IAM.
*
* Policies can be created using the {@see \Google\Cloud\Core\Iam\PolicyBuilder}
* to help ensure their validity.
*
* Example:
* ```
* // IAM policies are obtained via resources which implement IAM.
* // In this example, we'll use PubSub topics to demonstrate
* // how IAM policies are managed.
*
* use Google\Cloud\Spanner\SpannerClient;
*
* $spanner = new SpannerClient();
* $instance = $spanner->instance('my-new-instance');
*
* $iam = $instance->iam();
* ```
*/
class Iam
{
/**
* @var IamConnectionInterface
*/
private $connection;
/**
* @var string
*/
private $resource;
/**
* @var array
*/
private $policy;
/**
* @var array
*/
private $args;
/**
* @var array
*/
private $options;
/**
* @param IamConnectionInterface $connection
* @param string $resource
* @param array $options [optional] {
* Configuration Options
*
* @type string|null $parent The parent request parameter for the policy.
* If set, policy data will be sent as `request.{$parent}`.
* Otherwise, policy will be sent in request root. **Defaults to**
* `policy`.
* @type array $args Arbitrary data to be sent with the request.
* }
* @access private
*/
public function __construct(IamConnectionInterface $connection, $resource, array $options = [])
{
$options += ['parent' => 'policy', 'args' => []];
$this->connection = $connection;
$this->resource = $resource;
$this->options = $options;
}
/**
* Get the existing IAM policy for this resource.
*
* If a policy has already been retrieved from the API, it will be returned.
* To fetch a fresh copy of the policy, use
* {@see \Google\Cloud\Core\Iam\Iam::reload()}.
*
* Example:
* ```
* $policy = $iam->policy();
* ```
*
* @param array $options Configuration Options
* @param int $options['requestedPolicyVersion'] Specify the policy version to
* request from the server. Please see
* [policy versioning](https://cloud.google.com/iam/docs/policies#versions)
* for more information.
* @return array An array of policy data
*/
public function policy(array $options = [])
{
if (!$this->policy) {
$this->reload($options);
}
return $this->policy;
}
/**
* Set the IAM policy for this resource.
*
* Bindings with invalid roles, or non-existent members will raise a server
* error.
*
* Example:
* ```
* $oldPolicy = $iam->policy();
* $oldPolicy['bindings'][0]['members'] = 'user:test@example.com';
*
* $policy = $iam->setPolicy($oldPolicy);
* ```
*
* @param array|PolicyBuilder $policy The new policy, as an array or an
* instance of {@see \Google\Cloud\Core\Iam\PolicyBuilder}.
* @param array $options Configuration Options
* @return array An array of policy data
* @throws \InvalidArgumentException If the given policy is not an array or PolicyBuilder.
*/
public function setPolicy($policy, array $options = [])
{
if ($policy instanceof PolicyBuilder) {
$policy = $policy->result();
}
if (!\is_array($policy)) {
throw new \InvalidArgumentException('Given policy data must be an array or an instance of PolicyBuilder.');
}
$request = [];
if ($this->options['parent']) {
$parent = $this->options['parent'];
$request[$parent] = $policy;
} else {
$request = $policy;
}
return $this->policy = $this->connection->setPolicy(['resource' => $this->resource] + $request + $options + $this->options['args']);
}
/**
* Test if the current user has the given permissions on this resource.
*
* Invalid permissions will raise a BadRequestException.
*
* Example:
* ```
* $allowedPermissions = $iam->testPermissions([
* 'pubsub.topics.publish',
* 'pubsub.topics.attachSubscription'
* ]);
* ```
*
* @param array $permissions A list of permissions to test
* @param array $options Configuration Options
* @return array A subset of $permissions, with only those allowed included.
*/
public function testPermissions(array $permissions, array $options = [])
{
$res = $this->connection->testPermissions(['permissions' => $permissions, 'resource' => $this->resource] + $options + $this->options['args']);
return isset($res['permissions']) ? $res['permissions'] : [];
}
/**
* Refresh the IAM policy for this resource.
*
* Example:
* ```
* $policy = $iam->reload();
* ```
*
* @param array $options Configuration Options
* @return array An array of policy data
*/
public function reload(array $options = [])
{
return $this->policy = $this->connection->getPolicy(['resource' => $this->resource] + $options + $this->options['args']);
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iam;
/**
* An interface defining how wrappers interact with their IAM implementations.
*
* Some services, such as PubSub, have multiple entities in their API which each
* support IAM for access control. Since we use a single implementation for all
* service interaction with a service, IamConnectionInterface is used to proxy
* requests to the correct method on the service connection.
*
* By delegating control of the request to each service, we can reliably offer a
* single entry point for dealing with IAM in a standard way.
*/
interface IamConnectionInterface
{
/**
* @param array $args
*/
public function getPolicy(array $args);
/**
* @param array $args
*/
public function setPolicy(array $args);
/**
* @param array $args
*/
public function testPermissions(array $args);
}

View File

@@ -0,0 +1,193 @@
<?php
/**
* Copyright 2024 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iam;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\Serializer;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ArrayTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iam\PolicyBuilder;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Iam\V1\Policy;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\RequestHandler;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Iam\V1\GetIamPolicyRequest;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Iam\V1\GetPolicyOptions;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Iam\V1\SetIamPolicyRequest;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Iam\V1\TestIamPermissionsRequest;
use InvalidArgumentException;
/**
* IAM Manager
*
* This class is not meant to be used directly. It should be accessed
* through other objects which support IAM.
*
* Policies can be created using the {@see PolicyBuilder}
* to help ensure their validity.
*
* Example:
* ```
* // IAM policies are obtained via resources which implement IAM.
* // In this example, we'll use PubSub topics to demonstrate
* // how IAM policies are managed.
*
* use Google\Cloud\PubSub\PubSubClient;
*
* $pubsub = new PubSubClient();
* $topic = $pubsub->topic('my-new-topic');
*
* $iam = $topic->iam();
* ```
*
* @internal
*/
class IamManager
{
use ArrayTrait;
private RequestHandler $requestHandler;
private Serializer $serializer;
private string $clientClass;
private string $resource;
private ?array $policy;
/**
* @param RequestHandler $requestHandler
* @param Serializer $serializer The serializer instance to encode/decode messages.
* @param string $clientClass The client class that will be passed on to the
* sendRequest method of the $requestHandler.
* @param string $resource
* @access private
*/
public function __construct(RequestHandler $requestHandler, Serializer $serializer, string $clientClass, string $resource)
{
$this->requestHandler = $requestHandler;
$this->serializer = $serializer;
$this->clientClass = $clientClass;
$this->resource = $resource;
$this->policy = null;
}
/**
* Get the existing IAM policy for this resource.
*
* If a policy has already been retrieved from the API, it will be returned.
* To fetch a fresh copy of the policy, use
* {@see IamManager::reload()}.
*
* Example:
* ```
* $policy = $iam->policy();
* ```
*
* @param array $options Configuration Options
* @param int $options['requestedPolicyVersion'] Specify the policy version to
* request from the server. Please see
* [policy versioning](https://cloud.google.com/iam/docs/policies#versions)
* for more information.
* @return array An array of policy data
*/
public function policy(array $options = [])
{
if (!$this->policy) {
$this->reload($options);
}
return $this->policy;
}
/**
* Set the IAM policy for this resource.
*
* Bindings with invalid roles, or non-existent members will raise a server
* error.
*
* Example:
* ```
* $policy = [
* 'bindings' => [[
* 'role' => 'roles/editor',
* 'members' => ['user:test@example.com'],
* ]]
* ];
* $policy = $iam->setPolicy($policy);
* ```
* ```
* $oldPolicy = $iam->policy();
* $oldPolicy['bindings'][0]['members'] = 'user:test@example.com';
*
* $policy = $iam->setPolicy($oldPolicy);
* ```
*
* @param array|PolicyBuilder $policy The new policy, as an array or an
* instance of {@see PolicyBuilder}.
* @param array $options Configuration Options
* @return array An array of policy data
* @throws InvalidArgumentException If the given policy is not an array or PolicyBuilder.
*/
public function setPolicy($policy, array $options = [])
{
if ($policy instanceof PolicyBuilder) {
$policy = $policy->result();
}
if (!\is_array($policy)) {
throw new InvalidArgumentException('Given policy data must be an array or an instance of PolicyBuilder.');
}
$policy = $this->serializer->decodeMessage(new Policy(), $policy);
$updateMask = $options['updateMask'] ?? [];
$data = ['resource' => $this->resource, 'policy' => $policy, 'updateMask' => $updateMask];
$request = $this->serializer->decodeMessage(new SetIamPolicyRequest(), $data);
$this->policy = $this->requestHandler->sendRequest($this->clientClass, 'setIamPolicy', $request, $options);
return $this->policy;
}
/**
* Test if the current user has the given permissions on this resource.
*
* Invalid permissions will raise a BadRequestException.
*
* Example:
* ```
* $allowedPermissions = $iam->testPermissions([
* 'pubsub.topics.publish',
* 'pubsub.topics.attachSubscription'
* ]);
* ```
*
* @param array $permissions A list of permissions to test
* @param array $options Configuration Options
* @return array A subset of $permissions, with only those allowed included.
*/
public function testPermissions(array $permissions, array $options = [])
{
$data = ['resource' => $this->resource, 'permissions' => $permissions];
$request = $this->serializer->decodeMessage(new TestIamPermissionsRequest(), $data);
$res = $this->requestHandler->sendRequest($this->clientClass, 'testIamPermissions', $request, $options);
return isset($res['permissions']) ? $res['permissions'] : [];
}
/**
* Refresh the IAM policy for this resource.
*
* Example:
* ```
* $policy = $iam->reload();
* ```
*
* @param array $options Configuration Options
* @return array An array of policy data
*/
public function reload(array $options = [])
{
$policyOptions = $this->pluck('policyOptions', $options, \false) ?: [];
$policyOptions = $this->serializer->decodeMessage(new GetPolicyOptions(), $policyOptions);
$data = ['resource' => $this->resource, 'options' => $policyOptions];
$request = $this->serializer->decodeMessage(new GetIamPolicyRequest(), $data);
$this->policy = $this->requestHandler->sendRequest($this->clientClass, 'getIamPolicy', $request, $options);
return $this->policy;
}
}

View File

@@ -0,0 +1,275 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iam;
use InvalidArgumentException;
use BadMethodCallException;
/**
* Helper class for creating valid IAM policies
*
* Example:
* ```
* use Google\Cloud\Core\Iam\PolicyBuilder;
*
* $builder = new PolicyBuilder();
* $builder->addBinding('roles/admin', [ 'user:admin@domain.com' ]);
* $result = $builder->result();
* ```
*/
class PolicyBuilder
{
/**
* @var array
*/
private $bindings;
/**
* @var string
*/
private $etag;
/**
* @var int
*/
private $version;
/**
* Create a PolicyBuilder.
*
* To use conditions in the bindings, the version of the policy must be set
* to 3.
*
* @see https://cloud.google.com/iam/docs/policies#versions Policy versioning
* @see https://cloud-dot-devsite.googleplex.com/storage/docs/access-control/using-iam-permissions#conditions-iam
* Using Cloud IAM Conditions on buckets
*
* Example:
* ```
* $policy = [
* 'etag' => 'AgIc==',
* 'version' => 3,
* 'bindings' => [
* [
* 'role' => 'roles/admin',
* 'members' => [
* 'user:admin@domain.com',
* 'user2:admin@domain.com'
* ],
* 'condition' => [
* 'title' => 'match-prefix',
* 'description' => 'Applies to objects matching a prefix',
* 'expression' =>
* 'resource.name.startsWith("projects/_/buckets/bucket-name/objects/prefix-a-")'
* ]
* ]
* ],
* ];
*
* $builder = new PolicyBuilder($policy);
* ```
*
* @param array $policy A policy array
* @throws \InvalidArgumentException
*/
public function __construct(array $policy = [])
{
if (isset($policy['bindings'])) {
$this->setBindings($policy['bindings']);
} elseif (!empty($policy)) {
throw new InvalidArgumentException('Invalid Policy');
}
if (isset($policy['etag'])) {
$this->setEtag($policy['etag']);
}
if (isset($policy['version'])) {
$this->setVersion($policy['version']);
}
}
/**
* Override all stored bindings on the policy.
*
* Example:
* ```
* $builder->setBindings([
* [
* 'role' => 'roles/admin',
* 'members' => [
* 'user:admin@domain.com'
* ],
* 'condition' => [
* 'expression' =>
* 'request.time < timestamp("2020-07-01T00:00:00.000Z")'
* ]
* ]
* ]);
* ```
*
* @param array $bindings [optional] An array of bindings
* @return PolicyBuilder
* @throws \InvalidArgumentException
*/
public function setBindings(array $bindings = [])
{
$this->bindings = $bindings;
return $this;
}
/**
* Add a new binding to the policy.
*
* This method will fail with an InvalidOpereationException if it is
* called on a Policy with a version greater than 1 as that indicates
* a more complicated policy than this method is prepared to handle.
* Changes to such policies must be made manually by the setBindings()
* method.
*
*
* Example:
* ```
* $builder->addBinding('roles/admin', [ 'user:admin@domain.com' ]);
* ```
*
* @param string $role A valid role for the service
* @param array $members An array of members to assign to the binding
* @return PolicyBuilder
* @throws \InvalidArgumentException
* @throws BadMethodCallException if the policy's version is greater than 1.
* @deprecated
*/
public function addBinding($role, array $members)
{
$this->validatePolicyVersion();
$this->bindings[] = ['role' => $role, 'members' => $members];
return $this;
}
/**
* Remove a binding from the policy.
*
* This method will fail with a BadMethodCallException if it is
* called on a Policy with a version greater than 1 as that indicates
* a more complicated policy than this method is prepared to handle.
* Changes to such policies must be made manually by the setBindings()
* method.
*
* Example:
* ```
* $builder->setBindings([
* [
* 'role' => 'roles/admin',
* 'members' => [
* 'user:admin@domain.com',
* 'user2:admin@domain.com'
* ]
* ]
* ]);
* $builder->removeBinding('roles/admin', [ 'user:admin@domain.com' ]);
* ```
*
* @param string $role A valid role for the service
* @param array $members An array of members to remove from the role
* @return PolicyBuilder
* @throws \InvalidArgumentException
* @throws BadMethodCallException if the policy's version is greater than 1.
* @deprecated
*/
public function removeBinding($role, array $members)
{
$this->validatePolicyVersion();
$bindings = $this->bindings;
foreach ((array) $bindings as $i => $binding) {
if ($binding['role'] == $role) {
$newMembers = \array_diff($binding['members'], $members);
if (\count($newMembers) != \count($binding['members']) - \count($members)) {
throw new InvalidArgumentException('One or more role-members were not found.');
}
if (empty($newMembers)) {
unset($bindings[$i]);
$bindings = \array_values($bindings);
} else {
$binding['members'] = \array_values($newMembers);
$bindings[$i] = $binding;
}
$this->bindings = $bindings;
return $this;
}
}
throw new InvalidArgumentException('The role was not found.');
}
/**
* Update the etag on the policy.
*
* Example:
* ```
* $builder->setEtag($oldPolicy['etag']);
* ```
*
* @param string $etag used for optimistic concurrency control as a way to help prevent simultaneous updates of a
* policy from overwriting each other. It is strongly suggested that updates to existing policies make use
* of the etag to avoid race conditions.
* @return PolicyBuilder
*/
public function setEtag($etag)
{
$this->etag = $etag;
return $this;
}
/**
* Update the version of the policy.
*
* Example:
* ```
* $builder->setVersion(1);
* ```
*
* @param int $version Version of the Policy. **Defaults to** `0`.
* @return PolicyBuilder
*/
public function setVersion($version)
{
$this->version = $version;
return $this;
}
/**
* Create a policy array with data in the correct format.
*
* Example:
* ```
* $policy = $builder->result();
* ```
*
* @return array An array of policy data
*/
public function result()
{
return \array_filter(['etag' => $this->etag, 'bindings' => $this->bindings, 'version' => $this->version]);
}
private function validatePolicyVersion()
{
if (isset($this->version) && $this->version > 1) {
throw new BadMethodCallException("Helper methods cannot be " . "invoked on policies with version {$this->version}.");
}
$this->validateConditions();
}
private function validateConditions()
{
if (!$this->bindings) {
return;
}
foreach ($this->bindings as $binding) {
if (isset($binding['condition'])) {
throw new BadMethodCallException("Helper methods cannot " . "be invoked on policies containing conditions.");
}
}
}
}

View File

@@ -0,0 +1,36 @@
<?php
/**
* Copyright 2022 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\CredentialsWrapper;
/**
* For connect to emulator.
*/
class InsecureCredentialsWrapper extends CredentialsWrapper
{
public function __construct()
{
}
public function getAuthorizationHeaderCallback($audience = null)
{
return null;
}
public function checkUniverseDomain()
{
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Represents a 64 bit integer. This can be useful when working on a 32 bit
* platform.
*
* Example:
* ```
* $int64 = new Int64('9223372036854775807');
* ```
*/
class Int64 implements \JsonSerializable
{
/**
* @var string
*/
private $value;
/**
* @param string $value The 64 bit integer value in string format.
*/
public function __construct($value)
{
$this->value = $value;
}
/**
* Get the value.
*
* Example:
* ```
* $value = $int64->get();
* ```
*
* @return string
*/
public function get()
{
return $this->value;
}
/**
* Provides a convenient way to access the value.
*
* @access private
*/
public function __toString()
{
return $this->value;
}
/**
* Implement JsonSerializable by returning the 64 bit integer as a string
*
* @return string
* @access private
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return $this->value;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator;
/**
* Iterates over a set of items.
*/
class ItemIterator implements \Iterator
{
use ItemIteratorTrait;
}

View File

@@ -0,0 +1,135 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator;
/**
* This trait fulfills the
* [\Iterator](http://php.net/manual/en/class.iterator.php) interface and
* returns results from a paged set one at a time.
*/
trait ItemIteratorTrait
{
/**
* @var \Iterator
*/
private $pageIterator;
/**
* @var int
*/
private $pageIndex = 0;
/**
* @var int
*/
private $position = 0;
/**
* @param \Iterator $pageIterator
*/
public function __construct(\Iterator $pageIterator)
{
$this->pageIterator = $pageIterator;
}
/**
* Fetch the token used to get the next set of results.
*
* @return string|null
*/
public function nextResultToken()
{
return \method_exists($this->pageIterator, 'nextResultToken') ? $this->pageIterator->nextResultToken() : null;
}
/**
* Iterate over the results on a per page basis.
*
* @return \Iterator
*/
public function iterateByPage()
{
return $this->pageIterator;
}
/**
* Rewind the iterator.
*
* @return null
*/
#[\ReturnTypeWillChange]
public function rewind()
{
$this->pageIndex = 0;
$this->position = 0;
$this->pageIterator->rewind();
}
/**
* Get the current item.
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function current()
{
$page = $this->pageIterator->current();
return isset($page[$this->pageIndex]) ? $page[$this->pageIndex] : null;
}
/**
* Get the key current item's key.
*
* @return int
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
}
/**
* Advances to the next item.
*
* @return null
*/
#[\ReturnTypeWillChange]
public function next()
{
$this->pageIndex++;
$this->position++;
if (\count($this->pageIterator->current()) <= $this->pageIndex && $this->nextResultToken()) {
$this->pageIterator->next();
$this->pageIndex = 0;
}
}
/**
* Determines if the current position is valid.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
$page = $this->pageIterator->current();
if (isset($page[$this->pageIndex])) {
return \true;
}
// If there are no results, but a token for the next page
// exists let's continue paging until there are results.
while ($this->nextResultToken()) {
$this->pageIterator->next();
$page = $this->pageIterator->current();
if (isset($page[$this->pageIndex])) {
return \true;
}
}
return \false;
}
}

View File

@@ -0,0 +1,26 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator;
/**
* Iterates over a set of pages.
*/
class PageIterator implements \Iterator
{
use PageIteratorTrait;
}

View File

@@ -0,0 +1,264 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ArrayTrait;
/**
* This trait fulfills the
* [\Iterator](http://php.net/manual/en/class.iterator.php) interface and
* returns results as a page of items.
*/
trait PageIteratorTrait
{
use ArrayTrait;
/**
* @var array|null
*/
private $page;
/**
* @var callable
*/
private $resultMapper;
/**
* @var callable
*/
private $call;
/**
* @var array
*/
private $callOptions;
/**
* @var array
*/
private $config;
/**
* @var int
*/
private $position = 0;
/**
* @var int
*/
private $itemCount = 0;
/**
* @var array
*/
private $resultTokenPath;
/**
* @var array
*/
private $nextResultTokenPath;
/**
* @var array
*/
private $itemsPath;
/**
* @var string|null
*/
private $initialResultToken;
/**
* @param callable $resultMapper Maps a result.
* @param callable $call The call to execute.
* @param array $callOptions Options to use with the call.
* @param array $config [optional] {
* Configuration options.
*
* @type string $itemsKey The key for the items to iterate over from the
* response. **Defaults to** `"items"`.
* @type string $nextResultTokenKey The key for the next result token in
* the response. **Defaults to** `"nextPageToken"`.
* @type string $resultTokenKey The key for the results token set in the
* request. **Defaults too** `"pageToken"`.
* @type array $firstPage The first page of results. If set, this data
* will be used for the first page of results instead of making
* a network request.
* @type callable $setNextResultTokenCondition If this condition passes
* then it should be considered safe to set the token to get the
* next set of results.
* @type int $resultLimit Limit the number of results returned in total.
* **Defaults to** `0` (return all results).
* }
*/
public function __construct(callable $resultMapper, callable $call, array $callOptions, array $config = [])
{
$this->resultMapper = $resultMapper;
$this->call = $call;
$this->config = $config + ['itemsKey' => 'items', 'nextResultTokenKey' => 'nextPageToken', 'resultTokenKey' => 'pageToken', 'firstPage' => null, 'resultLimit' => 0, 'setNextResultTokenCondition' => function () {
return \true;
}];
$this->callOptions = $callOptions;
$this->resultTokenPath = \explode('.', $this->config['resultTokenKey']);
$this->nextResultTokenPath = \explode('.', $this->config['nextResultTokenKey']);
$this->itemsPath = \explode('.', $this->config['itemsKey']);
$this->initialResultToken = $this->nextResultToken();
}
/**
* Fetch the token used to get the next set of results.
*
* @return string|null
*/
public function nextResultToken()
{
return $this->get($this->resultTokenPath, $this->callOptions);
}
/**
* Rewind the iterator.
*
* @return null
*/
#[\ReturnTypeWillChange]
public function rewind()
{
$this->itemCount = 0;
$this->position = 0;
if ($this->config['firstPage']) {
list($this->page, $shouldContinue) = $this->mapResults($this->config['firstPage']);
$nextResultToken = $this->determineNextResultToken($this->page, $shouldContinue);
} else {
$this->page = null;
$nextResultToken = $this->initialResultToken;
}
if ($nextResultToken) {
$this->set($this->resultTokenPath, $this->callOptions, $nextResultToken);
}
}
/**
* Get the current page.
*
* @return array|null
*/
#[\ReturnTypeWillChange]
public function current()
{
if ($this->page === null) {
$this->page = $this->executeCall();
}
$page = $this->get($this->itemsPath, $this->page);
if ($this->nextResultToken()) {
return $page ?: [];
}
return $page;
}
/**
* Get the key current page's key.
*
* @return int
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->position;
}
/**
* Advances to the next page.
*
* @return null
*/
#[\ReturnTypeWillChange]
public function next()
{
$this->position++;
$this->page = $this->nextResultToken() ? $this->executeCall() : null;
}
/**
* Determines if the current position is valid.
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
if (!$this->page && $this->position) {
return \false;
}
return \true;
}
/**
* Executes the provided call to get a set of results.
*
* @return array
*/
private function executeCall()
{
$call = $this->call;
list($results, $shouldContinue) = $this->mapResults($call($this->callOptions));
$this->set($this->resultTokenPath, $this->callOptions, $this->determineNextResultToken($results, $shouldContinue));
return $results;
}
/**
* @param array $results
* @return array
*/
private function mapResults(array $results)
{
$items = $this->get($this->itemsPath, $results);
$resultMapper = $this->resultMapper;
$shouldContinue = \true;
if ($items) {
foreach ($items as $key => $item) {
$items[$key] = $resultMapper($item);
$this->itemCount++;
if ($this->config['resultLimit'] && $this->config['resultLimit'] <= $this->itemCount) {
$items = \array_slice($items, 0, $key + 1);
$shouldContinue = \false;
break;
}
}
$this->set($this->itemsPath, $results, $items);
}
return [$results, $shouldContinue];
}
/**
* @param array $results
* @param bool $shouldContinue
* @return null
*/
private function determineNextResultToken(array $results, $shouldContinue = \true)
{
return $shouldContinue && $this->config['setNextResultTokenCondition']($results) ? $this->get($this->nextResultTokenPath, $results) : null;
}
/**
* @param array $path
* @param array $array
* @return mixed
*/
private function get(array $path, array $array)
{
$result = $array;
foreach ($path as $key) {
if (!isset($result[$key])) {
return null;
}
$result = $result[$key];
}
return $result;
}
/**
* @param array $path
* @param array $array
* @param mixed $value
* @return null
*/
private function set(array $path, array &$array, $value)
{
$temp =& $array;
foreach ($path as $key) {
$temp =& $temp[$key];
}
$temp = $value;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
/**
* Provides wrappers for json_encode/json_decode that throw exceptions when an
* error is encountered.
*/
trait JsonTrait
{
/**
* @param string $json The json string being decoded.
* @param bool $assoc When true, returned objects will be converted into
* associative arrays.
* @param int $depth User specified recursion depth.
* @param int $options Bitmask of JSON decode options.
* @return mixed
* @throws \InvalidArgumentException
*/
private static function jsonDecode($json, $assoc = \false, $depth = 512, $options = 0)
{
$data = \json_decode($json, $assoc, $depth, $options);
if (\JSON_ERROR_NONE !== \json_last_error()) {
throw new \InvalidArgumentException('json_decode error: ' . \json_last_error_msg());
}
return $data;
}
/**
* @param mixed $value The value being encoded. Can be any type except a
* resource.
* @param int $options Bitmask of JSON encode options.
* @param int $depth Set the maximum depth. Must be greater than zero.
* @return string
* @throws \InvalidArgumentException
*/
private static function jsonEncode($value, $options = 0, $depth = 512)
{
$json = \json_encode($value, $options, $depth);
if (\JSON_ERROR_NONE !== \json_last_error()) {
throw new \InvalidArgumentException('json_encode error: ' . \json_last_error_msg());
}
return $json;
}
}

View File

@@ -0,0 +1,128 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Lock;
/**
* Flock based lock implementation.
*
* @see http://php.net/manual/en/function.flock.php
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class FlockLock implements LockInterface
{
use LockTrait;
const FILE_PATH_TEMPLATE = '%s/%s.lock';
/**
* @var string
*/
private $filePath;
/**
* @var resource|null
*/
private $handle;
/**
* @var bool If true, we should acquire an exclusive lock.
*/
private $exclusive;
/**
* @param string $fileName The name of the file to use as a lock.
* @param array $options [optional] {
* Configuration options.
*
* @type bool $exclusive If true, acquire an excluse (write) lock. If
* false, acquire a shared (read) lock. **Defaults to** true.
* }
* @throws \InvalidArgumentException If an invalid fileName is provided.
*/
public function __construct($fileName, array $options = [])
{
if (!\is_string($fileName)) {
throw new \InvalidArgumentException('$fileName must be a string.');
}
$options += ['exclusive' => \true];
$this->exclusive = $options['exclusive'];
$this->filePath = \sprintf(self::FILE_PATH_TEMPLATE, \sys_get_temp_dir(), $fileName);
}
/**
* Acquires a lock that will block until released.
*
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public function acquire(array $options = [])
{
if ($this->handle) {
return \true;
}
$this->handle = $this->initializeHandle();
if (!\flock($this->handle, $this->lockType($options))) {
\fclose($this->handle);
$this->handle = null;
throw new \RuntimeException('Failed to acquire lock.');
}
return \true;
}
/**
* Releases the lock.
*
* @throws \RuntimeException If the lock fails to release.
*/
public function release()
{
if ($this->handle) {
$released = \flock($this->handle, \LOCK_UN);
\fclose($this->handle);
$this->handle = null;
if (!$released) {
throw new \RuntimeException('Failed to release lock.');
}
}
}
/**
* Initializes the handle.
*
* @return resource
* @throws \RuntimeException If the lock file fails to open.
*/
private function initializeHandle()
{
$handle = @\fopen($this->filePath, 'c');
if (!$handle) {
throw new \RuntimeException('Failed to open lock file.');
}
return $handle;
}
private function lockType(array $options)
{
$options += ['blocking' => \true];
$lockType = $this->exclusive ? \LOCK_EX : \LOCK_SH;
if (!$options['blocking']) {
$lockType |= \LOCK_UN;
}
return $lockType;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Lock;
/**
* Contract for a basic locking mechanism.
*/
interface LockInterface
{
/**
* Acquires a lock.
*
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public function acquire(array $options = []);
/**
* Releases the lock.
*
* @throws \RuntimeException
*/
public function release();
/**
* Execute a callable within a lock.
*
* @param callable $func The callable to execute.
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return mixed
*/
public function synchronize(callable $func, array $options = []);
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Lock;
/**
* Utility trait for locks.
*/
trait LockTrait
{
/**
* Acquires a lock that will block until released.
*
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public abstract function acquire(array $options = []);
/**
* Releases the lock.
*
* @throws \RuntimeException
*/
public abstract function release();
/**
* Execute a callable within a lock. If an exception is caught during
* execution of the callable the lock will first be released before throwing
* it.
*
* @param callable $func The callable to execute.
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return mixed
*/
public function synchronize(callable $func, array $options = [])
{
$result = null;
$exception = null;
if ($this->acquire($options)) {
try {
$result = $func();
} catch (\Exception $ex) {
$exception = $ex;
}
$this->release();
}
if ($exception) {
throw $exception;
}
return $result;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Lock;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\SysvTrait;
/**
* Semaphore based lock implementation.
*
* @see http://php.net/manual/en/book.sem.php
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
class SemaphoreLock implements LockInterface
{
use LockTrait;
use SysvTrait;
/**
* @var int
*/
private $key;
/**
* @var resource|null
*/
private $semaphoreId;
/**
* @param int $key A key.
* @throws \InvalidArgumentException If an invalid key is provided.
* @throws \RuntimeException If the System V IPC extensions are missing.
*/
public function __construct($key)
{
if (!$this->isSysvIPCLoaded()) {
throw new \RuntimeException('SystemV IPC extensions are required.');
}
if (!\is_int($key)) {
throw new \InvalidArgumentException('The provided key must be an integer.');
}
$this->key = $key;
}
/**
* Acquires a lock that will block until released.
*
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public function acquire(array $options = [])
{
$options += ['blocking' => \true];
if ($this->semaphoreId) {
return \true;
}
$this->semaphoreId = $this->initializeId();
if (!\sem_acquire($this->semaphoreId, !$options['blocking'])) {
$this->semaphoreId = null;
throw new \RuntimeException('Failed to acquire lock.');
}
return \true;
}
/**
* Releases the lock.
*
* @throws \RuntimeException If the lock fails to release.
*/
public function release()
{
if ($this->semaphoreId) {
$released = \sem_release($this->semaphoreId);
$this->semaphoreId = null;
if (!$released) {
throw new \RuntimeException('Failed to release lock.');
}
}
}
/**
* Initializes the semaphore ID.
*
* @return resource
* @throws \RuntimeException If semaphore ID fails to generate.
*/
private function initializeId()
{
$semaphoreId = \sem_get($this->key);
if (!$semaphoreId) {
throw new \RuntimeException('Failed to generate semaphore ID.');
}
return $semaphoreId;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Lock;
use DeliciousBrains\WP_Offload_Media\Gcp\Symfony\Component\Lock\LockInterface as SymfonyLockInterface;
/**
* Symfony lock component adapter.
* @deprecated
*/
class SymfonyLockAdapter implements LockInterface
{
use LockTrait;
/**
* @var SymfonyLockInterface
*/
private $lock;
/**
* @param SymfonyLockInterface $lock
*/
public function __construct(SymfonyLockInterface $lock)
{
$this->lock = $lock;
}
/**
* Acquires a lock that will block until released.
*
* @param array $options [optional] {
* Configuration options.
*
* @type bool $blocking Whether the process should block while waiting
* to acquire the lock. **Defaults to** true.
* }
* @return bool
* @throws \RuntimeException If the lock fails to be acquired.
*/
public function acquire(array $options = [])
{
$options += ['blocking' => \true];
try {
return $this->lock->acquire($options['blocking']);
} catch (\Exception $ex) {
throw new \RuntimeException(\sprintf('Acquiring the lock failed with the following message: %s', $ex->getMessage()), 0, $ex);
}
}
/**
* Releases the lock.
*
* @throws \RuntimeException
*/
public function release()
{
try {
$this->lock->release();
} catch (\Exception $ex) {
throw new \RuntimeException(\sprintf('Releasing the lock failed with the following message: %s', $ex->getMessage()), 0, $ex);
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\LineFormatter;
/**
* Monolog 1.x formatter for formatting logs on App Engine flexible environment.
*
* If you are using Monolog 2.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatterV2} instead.
* If you are using Monolog 3.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatterV3} instead.
*/
class AppEngineFlexFormatter extends LineFormatter
{
use FormatterTrait;
/**
* @param string $format [optional] The format of the message
* @param string $dateFormat [optional] The format of the timestamp
* @param bool $ignoreEmptyContextAndExtra [optional]
*/
public function __construct($format = null, $dateFormat = null, $ignoreEmptyContextAndExtra = \false)
{
parent::__construct($format, $dateFormat, \true, $ignoreEmptyContextAndExtra);
}
/**
* Get the plain text message with LineFormatter's format method and add
* metadata including the trace id then return the json string.
*
* @param array $record A record to format
* @return string The formatted record
*/
public function format(array $record)
{
return $this->formatPayload($record, parent::format($record));
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* Copyright 2019 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\LineFormatter;
/**
* Monolog 2.x formatter for formatting logs on App Engine flexible environment.
*
* If you are using Monolog 1.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatter} instead.
* If you are using Monolog 3.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatterV3} instead.
*/
class AppEngineFlexFormatterV2 extends LineFormatter
{
use FormatterTrait;
/**
* @param string $format [optional] The format of the message
* @param string $dateFormat [optional] The format of the timestamp
* @param bool $ignoreEmptyContextAndExtra [optional]
*/
public function __construct($format = null, $dateFormat = null, $ignoreEmptyContextAndExtra = \false)
{
parent::__construct($format, $dateFormat, \true, $ignoreEmptyContextAndExtra);
}
/**
* Get the plain text message with LineFormatter's format method and add
* metadata including the trace id then return the json string.
*
* @param array $record A record to format
* @return string The formatted record
*/
public function format(array $record) : string
{
return $this->formatPayload($record, parent::format($record));
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* Copyright 2022 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\LineFormatter;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\LogRecord;
/**
* Monolog 3.x formatter for formatting logs on App Engine flexible environment.
*
* If you are using Monolog 1.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatter} instead.
* If you are using Monolog 2.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexFormatterV2} instead.
*/
class AppEngineFlexFormatterV3 extends LineFormatter
{
use FormatterTrait;
/**
* @param string $format [optional] The format of the message
* @param string $dateFormat [optional] The format of the timestamp
* @param bool $ignoreEmptyContextAndExtra [optional]
*/
public function __construct($format = null, $dateFormat = null, $ignoreEmptyContextAndExtra = \false)
{
parent::__construct($format, $dateFormat, \true, $ignoreEmptyContextAndExtra);
}
/**
* Get the plain text message with LineFormatter's format method and add
* metadata including the trace id then return the json string.
*
* @param LogRecord $record A record to format
* @return string The formatted record
*/
public function format(LogRecord $record) : string
{
return $this->formatPayload($record, parent::format($record));
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\FormatterInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Handler\StreamHandler;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Logger;
/**
* Monolog 1.x handler for logging on App Engine flexible environment.
*
* If you are using Monolog 2.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandlerV2} instead.
* If you are using Monolog 3.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandlerV3} instead.
* @internal
*/
class AppEngineFlexHandler extends StreamHandler
{
/**
* @param int $level [optional] The minimum logging level at which this
* handler will be triggered.
* @param Boolean $bubble [optional] Whether the messages that are handled
* can bubble up the stack or not.
* @param int|null $filePermission [optional] Optional file permissions
* (default (0640) are only for owner read/write).
* @param Boolean $useLocking [optional] Try to lock log file before doing
* any writes.
* @param resource|string|null $stream [optional]
*/
public function __construct($level = Logger::INFO, $bubble = \true, $filePermission = 0640, $useLocking = \false, $stream = null)
{
if ($stream === null) {
$pid = \getmypid();
$stream = "file:///var/log/app_engine/app.{$pid}.json";
}
parent::__construct($stream, $level, $bubble, $filePermission, $useLocking);
}
/**
* Get the default formatter.
*
* @return FormatterInterface
*/
protected function getDefaultFormatter()
{
return new AppEngineFlexFormatter();
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* Copyright 2019 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use Exception;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Logger;
/**
* Factory to build out an AppEngineFlexHandler for the installed version of Monolog.
*/
class AppEngineFlexHandlerFactory
{
/**
* Builds out an AppEngineFlexHandler for the installed version of Monolog.
*
* @param int $level [optional] The minimum logging level at which this
* handler will be triggered.
* @param Boolean $bubble [optional] Whether the messages that are handled
* can bubble up the stack or not.
* @param int|null $filePermission [optional] Optional file permissions
* (default (0640) are only for owner read/write).
* @param Boolean $useLocking [optional] Try to lock log file before doing
* any writes.
* @param resource|string|null $stream [optional]
*
* @throws Exception
*
* @return AppEngineFlexHandler|AppEngineFlexHandlerV2|AppEngineFlexHandlerV3
*/
public static function build($level = Logger::INFO, $bubble = \true, $filePermission = 0640, $useLocking = \false, $stream = null)
{
$version = \defined('DeliciousBrains\\WP_Offload_Media\\Gcp\\Monolog\\Logger::API') ? Logger::API : 1;
switch ($version) {
case 1:
return new AppEngineFlexHandler($level, $bubble, $filePermission, $useLocking, $stream);
case 2:
return new AppEngineFlexHandlerV2($level, $bubble, $filePermission, $useLocking, $stream);
case 3:
return new AppEngineFlexHandlerV3($level, $bubble, $filePermission, $useLocking, $stream);
default:
throw new Exception('Version not supported');
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* Copyright 2019 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\FormatterInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Handler\StreamHandler;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Logger;
/**
* Monolog 2.x handler for logging on App Engine flexible environment.
*
* If you are using Monolog 1.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandler} instead.
* If you are using Monolog 3.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandlerV3} instead.
* @internal
*/
class AppEngineFlexHandlerV2 extends StreamHandler
{
/**
* @param int $level [optional] The minimum logging level at which this
* handler will be triggered.
* @param Boolean $bubble [optional] Whether the messages that are handled
* can bubble up the stack or not.
* @param int|null $filePermission [optional] Optional file permissions
* (default (0640) are only for owner read/write).
* @param Boolean $useLocking [optional] Try to lock log file before doing
* any writes.
* @param resource|string|null $stream [optional]
*/
public function __construct($level = Logger::INFO, $bubble = \true, $filePermission = 0640, $useLocking = \false, $stream = null)
{
if ($stream === null) {
$pid = \getmypid();
$stream = "file:///var/log/app_engine/app.{$pid}.json";
}
parent::__construct($stream, $level, $bubble, $filePermission, $useLocking);
}
protected function getDefaultFormatter() : FormatterInterface
{
return new AppEngineFlexFormatterV2();
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* Copyright 2022 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Formatter\FormatterInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Handler\StreamHandler;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\Logger;
/**
* Monolog 3.x handler for logging on App Engine flexible environment.
*
* If you are using Monolog 1.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandler} instead.
* If you are using Monolog 2.x, use {@see \Google\Cloud\Core\Logger\AppEngineFlexHandlerV2} instead.
* @internal
*/
class AppEngineFlexHandlerV3 extends StreamHandler
{
/**
* @param int $level [optional] The minimum logging level at which this
* handler will be triggered.
* @param Boolean $bubble [optional] Whether the messages that are handled
* can bubble up the stack or not.
* @param int|null $filePermission [optional] Optional file permissions
* (default (0640) are only for owner read/write).
* @param Boolean $useLocking [optional] Try to lock log file before doing
* any writes.
* @param resource|string|null $stream [optional]
*/
public function __construct($level = Logger::INFO, $bubble = \true, $filePermission = 0640, $useLocking = \false, $stream = null)
{
if ($stream === null) {
$pid = \getmypid();
$stream = "file:///var/log/app_engine/app.{$pid}.json";
}
parent::__construct($stream, $level, $bubble, $filePermission, $useLocking);
}
protected function getDefaultFormatter() : FormatterInterface
{
return new AppEngineFlexFormatterV3();
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* Copyright 2019 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Logger;
use DeliciousBrains\WP_Offload_Media\Gcp\Monolog\LogRecord;
/**
* Shared trait to enrich and format a record with
* App Engine Flex specific information.
*/
trait FormatterTrait
{
/**
* @param array|LogRecord $record
* @param string $message
* @return string
*/
protected function formatPayload($record, $message)
{
if ($record instanceof LogRecord) {
$record = $record->toArray();
}
list($usec, $sec) = \explode(' ', \microtime());
$usec = (int) ((float) $usec * 1000000000);
$sec = (int) $sec;
$payload = ['message' => $message, 'timestamp' => ['seconds' => $sec, 'nanos' => $usec], 'thread' => '', 'severity' => $record['level_name']];
if (isset($_SERVER['HTTP_X_CLOUD_TRACE_CONTEXT'])) {
$payload['traceId'] = \explode('/', $_SERVER['HTTP_X_CLOUD_TRACE_CONTEXT'])[0];
}
return "\n" . \json_encode($payload);
}
}

View File

@@ -0,0 +1,101 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\LongRunning;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator\ItemIterator;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Iterator\PageIterator;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\LongRunning\LongRunningConnectionInterface;
/**
* Provide Long Running Operation support to Google Cloud PHP Clients.
*
* This trait should be used by a user-facing client which implements LRO.
*/
trait LROTrait
{
/**
* @var LongRunningConnectionInterface
* @internal
*/
private $lroConnection;
/**
* @var array
*/
private $lroCallables;
/**
* @var string
*/
private $lroResource;
/**
* Populate required LRO properties.
*
* @param LongRunningConnectionInterface $lroConnection The LRO Connection.
* This object is created by internal classes,
* and should not be instantiated outside of this context.
* @param array $callablesMap An collection of form [(string) typeUrl, (callable) callable]
* providing a function to invoke when an operation completes. The
* callable Type should correspond to an expected value of
* operation.metadata.typeUrl.
* @param string $lroResource [optional] The resource for which operations
* may be listed.
*/
private function setLroProperties(LongRunningConnectionInterface $lroConnection, array $lroCallables, $resource = null)
{
$this->lroConnection = $lroConnection;
$this->lroCallables = $lroCallables;
$this->lroResource = $resource;
}
/**
* Resume a Long Running Operation
*
* @param string $operationName The Long Running Operation name.
* @param array $info [optional] The operation data.
* @return LongRunningOperation
*/
public function resumeOperation($operationName, array $info = [])
{
return new LongRunningOperation($this->lroConnection, $operationName, $this->lroCallables, $info);
}
/**
* List long running operations.
*
* @param array $options [optional] {
* Configuration Options.
*
* @type string $name The name of the operation collection.
* @type string $filter The standard list filter.
* @type int $pageSize Maximum number of results to return per
* request.
* @type int $resultLimit Limit the number of results returned in total.
* **Defaults to** `0` (return all results).
* @type string $pageToken A previously-returned page token used to
* resume the loading of results from a specific point.
* }
* @return ItemIterator<LongRunningOperation>
*/
public function longRunningOperations(array $options = [])
{
if (\is_null($this->lroResource)) {
throw new \BadMethodCallException('This service does list support listing operations.');
}
$resultLimit = $this->pluck('resultLimit', $options, \false) ?: 0;
$options['name'] = $this->lroResource . '/operations';
return new ItemIterator(new PageIterator(function (array $operation) {
return $this->resumeOperation($operation['name'], $operation);
}, [$this->lroConnection, 'operations'], $options, ['itemsKey' => 'operations', 'resultLimit' => $resultLimit]));
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\LongRunning;
/**
* Defines the calls required to manage Long Running Operations.
*
* This interface should be implemented in a service's Connection namespace.
* @internal
*/
interface LongRunningConnectionInterface
{
/**
* @param array $args
*/
public function get(array $args);
/**
* @param array $args
*/
public function cancel(array $args);
/**
* @param array $args
*/
public function delete(array $args);
/**
* @param array $args
*/
public function operations(array $args);
}

View File

@@ -0,0 +1,331 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\LongRunning;
/**
* Represent and interact with a Long Running Operation.
*/
class LongRunningOperation
{
const WAIT_INTERVAL = 1.0;
const STATE_IN_PROGRESS = 'inProgress';
const STATE_SUCCESS = 'success';
const STATE_ERROR = 'error';
/**
* @var LongRunningConnectionInterface
* @internal
*/
private $connection;
/**
* @var string
*/
private $name;
/**
* @var array
*/
private $info = [];
/**
* @var array|null
*/
private $result;
/**
* @var array|null
*/
private $error;
/**
* @var array
*/
private $callablesMap;
/**
* @param LongRunningConnectionInterface $connection An implementation
* mapping to methods which handle LRO resolution in the service.
* This object is created by internal classes,
* and should not be instantiated outside of this context.
* @param string $name The Operation name.
* @param array $callablesMap An collection of form [(string) typeUrl, (callable) callable]
* providing a function to invoke when an operation completes. The
* callable Type should correspond to an expected value of
* operation.metadata.typeUrl.
* @param array $info [optional] The operation info.
*/
public function __construct(LongRunningConnectionInterface $connection, $name, array $callablesMap, array $info = [])
{
$this->connection = $connection;
$this->name = $name;
$this->callablesMap = $callablesMap;
$this->info = $info;
}
/**
* Return the Operation name.
*
* Example:
* ```
* $name = $operation->name();
* ```
*
* @return string
*/
public function name()
{
return $this->name;
}
/**
* Check if the Operation is done.
*
* If the Operation state is not available, a service request may be executed
* by this method.
*
* Example:
* ```
* if ($operation->done()) {
* echo "The operation is done!";
* }
* ```
*
* @param array $options [optional] Configuration options.
* @return bool
*/
public function done(array $options = [])
{
return isset($this->info($options)['done']) ? $this->info['done'] : \false;
}
/**
* Get the state of the Operation.
*
* Return value will be one of `LongRunningOperation::STATE_IN_PROGRESS`,
* `LongRunningOperation::STATE_SUCCESS` or
* `LongRunningOperation::STATE_ERROR`.
*
* If the Operation state is not available, a service request may be executed
* by this method.
*
* Example:
* ```
* switch ($operation->state()) {
* case LongRunningOperation::STATE_IN_PROGRESS:
* echo "Operation is in progress";
* break;
*
* case LongRunningOperation::STATE_SUCCESS:
* echo "Operation succeeded";
* break;
*
* case LongRunningOperation::STATE_ERROR:
* echo "Operation failed";
* break;
* }
* ```
*
* @param array $options [optional] Configuration options.
* @return string
*/
public function state(array $options = [])
{
if (!$this->done($options)) {
return self::STATE_IN_PROGRESS;
}
if ($this->done() && $this->result()) {
return self::STATE_SUCCESS;
}
return self::STATE_ERROR;
}
/**
* Get the Operation result.
*
* The return type of this method is dictated by the type of Operation.
*
* Returns null if the Operation is not yet complete, or if an error occurred.
*
* If the Operation state is not available, a service request may be executed
* by this method.
*
* Example:
* ```
* $result = $operation->result();
* ```
*
* @param array $options [optional] Configuration options.
* @return mixed|null
*/
public function result(array $options = [])
{
$this->info($options);
return $this->result;
}
/**
* Get the Operation error.
*
* Returns null if the Operation is not yet complete, or if no error occurred.
*
* If the Operation state is not available, a service request may be executed
* by this method.
*
* Example:
* ```
* $error = $operation->error();
* ```
*
* @param array $options [optional] Configuration options.
* @return array|null
*/
public function error(array $options = [])
{
$this->info($options);
return $this->error;
}
/**
* Get the Operation info.
*
* If the Operation state is not available, a service request may be executed
* by this method.
*
* Example:
* ```
* $info = $operation->info();
* ```
*
* @codingStandardsIgnoreStart
* @param array $options [optional] Configuration options.
* @return array [google.longrunning.Operation](https://cloud.google.com/spanner/docs/reference/rpc/google.longrunning#google.longrunning.Operation)
* @codingStandardsIgnoreEnd
*/
public function info(array $options = [])
{
return $this->info ?: $this->reload($options);
}
/**
* Reload the Operation to check its status.
*
* Example:
* ```
* $result = $operation->reload();
* ```
*
* @codingStandardsIgnoreStart
* @param array $options [optional] Configuration Options.
* @return array [google.longrunning.Operation](https://cloud.google.com/spanner/docs/reference/rpc/google.longrunning#google.longrunning.Operation)
* @codingStandardsIgnoreEnd
*/
public function reload(array $options = [])
{
$res = $this->connection->get(['name' => $this->name] + $options);
$this->result = null;
$this->error = null;
if (isset($res['done']) && $res['done']) {
$type = $res['metadata']['typeUrl'];
$this->result = $this->executeDoneCallback($type, $res['response']);
$this->error = isset($res['error']) ? $res['error'] : null;
}
return $this->info = $res;
}
/**
* Reload the operation until it is complete.
*
* The return type of this method is dictated by the type of Operation. If
* `$options.maxPollingDurationSeconds` is set, and the poll exceeds the
* limit, the return will be `null`.
*
* Example:
* ```
* $result = $operation->pollUntilComplete();
* ```
*
* @param array $options {
* Configuration Options
*
* @type float $pollingIntervalSeconds The polling interval to use, in
* seconds. **Defaults to** `1.0`.
* @type float $maxPollingDurationSeconds The maximum amount of time to
* continue polling. **Defaults to** `0.0`.
* }
* @return mixed|null
*/
public function pollUntilComplete(array $options = [])
{
$options += ['pollingIntervalSeconds' => $this::WAIT_INTERVAL, 'maxPollingDurationSeconds' => 0.0];
$pollingIntervalMicros = $options['pollingIntervalSeconds'] * 1000000;
$maxPollingDuration = $options['maxPollingDurationSeconds'];
$hasMaxPollingDuration = $maxPollingDuration > 0.0;
$endTime = \microtime(\true) + $maxPollingDuration;
do {
\usleep($pollingIntervalMicros);
$this->reload($options);
} while (!$this->done() && (!$hasMaxPollingDuration || \microtime(\true) < $endTime));
return $this->result;
}
/**
* Cancel a Long Running Operation.
*
* Example:
* ```
* $operation->cancel();
* ```
*
* @param array $options Configuration options.
* @return void
*/
public function cancel(array $options = [])
{
$this->connection->cancel(['name' => $this->name]);
}
/**
* Delete a Long Running Operation.
*
* Example:
* ```
* $operation->delete();
* ```
*
* @param array $options Configuration Options.
* @return void
*/
public function delete(array $options = [])
{
$this->connection->delete(['name' => $this->name]);
}
/**
* When the Operation is complete, there may be a callback enqueued to
* handle the response. If so, execute it and return the result.
*
* @param string $type The response type.
* @param mixed $response The response data.
* @return mixed
*/
private function executeDoneCallback($type, $response)
{
if (\is_null($response)) {
return null;
}
$callables = \array_filter($this->callablesMap, function ($callable) use($type) {
return $callable['typeUrl'] === $type;
});
if (\count($callables) === 0) {
return $response;
}
$callable = \current($callables);
$fn = $callable['callable'];
return \call_user_func($fn, $response);
}
/**
* @access private
*/
public function __debugInfo()
{
return ['connection' => \get_class($this->connection), 'name' => $this->name, 'callablesMap' => \array_keys($this->callablesMap), 'info' => $this->info];
}
}

View File

@@ -0,0 +1,136 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\LongRunning;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\OperationResponse;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\Serializer;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\GAX\OperationResponse as GaxOperationResponse;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\GAX\Serializer as GaxSerialzer;
/**
* Serializes and deserializes ApiCore LRO Response objects.
*
* This trait should be used in a gRPC Connection class to normalize responses.
*/
trait OperationResponseTrait
{
/**
* Convert a ApiCore OperationResponse object to an array.
*
* @param OperationResponse|GaxOperationResponse $operation The operation response
* @param Serializer|GaxSerializer $serializer The serializer to use for gRPC serialization/deserialization.
* @param array $lroMappers A list of mappers for deserializing operation results.
* @return array
*/
private function operationToArray($operation, $serializer, array $lroMappers)
{
$response = $operation->getLastProtoResponse();
if (\is_null($response)) {
return null;
}
$response = $serializer->encodeMessage($response);
$result = null;
if ($operation->isDone() && isset($response['response']['typeUrl'])) {
$type = $response['response']['typeUrl'];
$result = $this->deserializeResult($operation, $type, $serializer, $lroMappers);
}
$metaType = $response['metadata']['typeUrl'];
$metaResult = $this->deserializeMetadata($operation, $metaType, $serializer, $lroMappers);
/** @see LongRunningOperation#reload() */
$metaResult += ['typeUrl' => $metaType];
$error = $operation->getError();
if (!\is_null($error)) {
$error = $serializer->encodeMessage($error);
}
$response['response'] = $result;
$response['metadata'] = $metaResult;
$response['error'] = $error;
return $response;
}
/**
* Fetch an OperationResponse object from a gapic client.
*
* @param mixed $client A generated client with a `resumeOperation` method.
* @param string $name The Operation name.
* @param string|null $method The method name.
* @return OperationResponse
*/
private function getOperationByName($client, $name, $method = null)
{
return $client->resumeOperation($name, $method);
}
/**
* Convert an operation response to an array
*
* @param OperationResponse|GaxOperationResponse $operation The operation to
* serialize.
* @param string $type The Operation type. The type should correspond to a
* member of $mappers.typeUrl.
* @param Serializer|GaxSerializer $serializer The gRPC serializer to use
* for the deserialization.
* @param array $mappers A list of mappers.
* @return array|null
*/
private function deserializeResult($operation, $type, $serializer, array $mappers)
{
$mappers = \array_filter($mappers, function ($mapper) use($type) {
return $mapper['typeUrl'] === $type;
});
if (\count($mappers) === 0) {
throw new \RuntimeException(\sprintf('No mapper exists for operation response type %s.', $type));
}
$mapper = \current($mappers);
$message = $mapper['message'];
$response = new $message();
$anyResponse = $operation->getLastProtoResponse()->getResponse();
if (\is_null($anyResponse)) {
return null;
}
$response->mergeFromString($anyResponse->getValue());
return $serializer->encodeMessage($response);
}
/**
* Convert an operation metadata to an array
*
* @param OperationResponse|GaxOperationResponse $operation The operation to
* serialize.
* @param string $type The Operation type. The type should correspond to a
* member of $mappers.typeUrl.
* @param Serializer|GaxSerializer $serializer The gRPC serializer to use
* for the deserialization.
* @param array $mappers A list of mappers.
* @return array|null
*/
private function deserializeMetadata($operation, $type, $serializer, array $mappers)
{
$mappers = \array_filter($mappers, function ($mapper) use($type) {
return $mapper['typeUrl'] === $type;
});
if (\count($mappers) === 0) {
throw new \RuntimeException(\sprintf('No mapper exists for operation metadata type %s.', $type));
}
$mapper = \current($mappers);
$message = $mapper['message'];
$response = new $message();
$anyResponse = $operation->getLastProtoResponse()->getMetadata();
if (\is_null($anyResponse)) {
return null;
}
$response->mergeFromString($anyResponse->getValue());
return $serializer->encodeMessage($response);
}
}

View File

@@ -0,0 +1,185 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\DrSlump\Protobuf;
use DeliciousBrains\WP_Offload_Media\Gcp\google\protobuf\Value;
use DeliciousBrains\WP_Offload_Media\Gcp\google\protobuf\ListValue;
use DeliciousBrains\WP_Offload_Media\Gcp\google\protobuf\NullValue;
use DeliciousBrains\WP_Offload_Media\Gcp\google\protobuf\Struct;
/**
* Extend the Protobuf-PHP array codec to allow messages to match the format
* used for REST.
* @deprecated
* @codeCoverageIgnore
* @internal
*/
class PhpArray extends Protobuf\Codec\PhpArray
{
/**
* @var array
*/
private $customFilters;
/**
* @var bool
*/
private $useCamelCase;
/**
* @param array $customFilters A set of callbacks to apply to properties in
* a gRPC response.
* @param bool $useCamelCase Whether to convert key casing to camelCase.
* }
*/
public function __construct(array $customFilters = [], $useCamelCase = \true)
{
$this->customFilters = $customFilters;
$this->useCamelCase = $useCamelCase;
}
/**
* Borrowed heavily from {@see DrSlump\Protobuf\Codec\PhpArray::encodeMessage()}.
* With this approach we are able to transform the response with minimal
* overhead.
*/
protected function encodeMessage(Protobuf\Message $message)
{
$descriptor = Protobuf::getRegistry()->getDescriptor($message);
$data = [];
foreach ($descriptor->getFields() as $tag => $field) {
$empty = !$message->_has($tag);
if ($field->isRequired() && $empty) {
throw new \UnexpectedValueException(\sprintf('Message %s\'s field tag %s(%s) is required but has no value', \get_class($message), $tag, $field->getName()));
}
if ($empty) {
continue;
}
$key = $this->useTagNumber ? $field->getNumber() : $field->getName();
$v = $message->_get($tag);
if ($field->isRepeated()) {
// Make sure the value is an array of values
$v = \is_array($v) ? $v : array($v);
$arr = [];
foreach ($v as $k => $vv) {
// Skip nullified repeated values
if (null === $vv) {
continue;
}
$filteredValue = $this->filterValue($vv, $field);
if ($this->isKeyValueMessage($vv)) {
$arr[\key($filteredValue)] = \current($filteredValue);
} else {
$arr[$k] = $filteredValue;
}
$v = $arr;
}
} else {
$v = $this->filterValue($v, $field);
}
$key = $this->useCamelCase ? $this->toCamelCase($key) : $key;
if (isset($this->customFilters[$key])) {
$v = \call_user_func($this->customFilters[$key], $v);
}
$data[$key] = $v;
}
return $data;
}
/**
* Borrowed heavily from {@see DrSlump\Protobuf\Codec\PhpArray::decodeMessage()}.
* The only addition here is converting camel case field names to snake case.
*/
protected function decodeMessage(Protobuf\Message $message, $data)
{
// Get message descriptor
$descriptor = Protobuf::getRegistry()->getDescriptor($message);
foreach ($data as $key => $v) {
// Get the field by tag number or name
$field = $this->useTagNumber ? $descriptor->getField($key) : $descriptor->getFieldByName($this->toSnakeCase($key));
// Unknown field found
if (!$field) {
$unknown = new Protobuf\Codec\PhpArray\Unknown($key, \gettype($v), $v);
$message->addUnknown($unknown);
continue;
}
if ($field->isRepeated()) {
// Make sure the value is an array of values
$v = \is_array($v) && \is_int(\key($v)) ? $v : array($v);
foreach ($v as $k => $vv) {
$v[$k] = $this->filterValue($vv, $field);
}
} else {
$v = $this->filterValue($v, $field);
}
$message->_set($field->getNumber(), $v);
}
return $message;
}
protected function filterValue($value, Protobuf\Field $field)
{
if (\trim($field->getReference(), '\\') === NullValue::class) {
return null;
}
if ($value instanceof Protobuf\Message) {
if ($this->isKeyValueMessage($value)) {
$v = $value->getValue();
return [$value->getKey() => $v instanceof Protobuf\Message ? $this->encodeMessage($v) : $v];
}
if ($value instanceof Struct) {
$vals = [];
foreach ($value->getFields() as $field) {
$val = $this->filterValue($field->getValue(), $field->descriptor()->getFieldByName('value'));
$vals[$field->getKey()] = $val;
}
return $vals;
}
if ($value instanceof ListValue) {
$vals = [];
foreach ($value->getValuesList() as $val) {
$fields = $val->descriptor()->getFields();
foreach ($fields as $field) {
$name = $field->getName();
if ($val->{$name} !== null) {
$vals[] = $this->filterValue($val->{$name}, $field);
}
}
}
return $vals;
}
if ($value instanceof Value) {
$fields = $value->descriptor()->getFields();
foreach ($fields as $field) {
$name = $field->getName();
if ($value->{$name} !== null) {
return $this->filterValue($value->{$name}, $field);
}
}
}
}
return parent::filterValue($value, $field);
}
private function toSnakeCase($key)
{
return \strtolower(\preg_replace(['/([a-z\\d])([A-Z])/', '/([^_])([A-Z][a-z])/'], '$1_$2', $key));
}
private function toCamelCase($key)
{
return \lcfirst(\str_replace(' ', '', \ucwords(\str_replace('_', ' ', $key))));
}
private function isKeyValueMessage($value)
{
return \property_exists($value, 'key') && \property_exists($value, 'value');
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Copyright 2023 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Compute\Metadata;
/**
* A MetadataProvider for Cloud Run.
*/
class CloudRunMetadataProvider implements MetadataProviderInterface
{
/**
* @var Metadata
*/
private $metadata;
/**
* @var string
*/
private $serviceId;
/**
* @var string
*/
private $revisionId;
public function __construct(array $env)
{
$this->serviceId = isset($env['K_SERVICE']) ? $env['K_SERVICE'] : 'unknown-service';
$this->revisionId = isset($env['K_REVISION']) ? $env['K_REVISION'] : 'unknown-revision';
$this->metadata = new Metadata();
}
/**
* not implemented
* @TODO
*/
public function monitoredResource()
{
return [];
}
/**
* not implemented
* @TODO
*/
public function projectId()
{
return $this->metadata->getProjectId();
}
/**
* Return the service id.
* @return string
*/
public function serviceId()
{
return $this->serviceId;
}
/**
* Return the version id.
* @return string
*/
public function versionId()
{
return $this->revisionId;
}
/**
* not implemented
* @TODO
*/
public function labels()
{
return [];
}
}

View File

@@ -0,0 +1,68 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* An empty MetadataProvider.
*/
class EmptyMetadataProvider implements MetadataProviderInterface
{
/**
* Return an array representing MonitoredResource.
*
* @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
*
* @return array
*/
public function monitoredResource()
{
return [];
}
/**
* Return the project id.
* @return string
*/
public function projectId()
{
return '';
}
/**
* Return the service id.
* @return string
*/
public function serviceId()
{
return '';
}
/**
* Return the version id.
* @return string
*/
public function versionId()
{
return '';
}
/**
* Return the labels.
* @return array
*/
public function labels()
{
return [];
}
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* An MetadataProvider for GAE Flex.
*/
class GAEFlexMetadataProvider extends GAEMetadataProvider
{
protected function getTraceValue($server)
{
return \substr($server['HTTP_X_CLOUD_TRACE_CONTEXT'], 0, 32);
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* MetadataProvider for GAE.
*/
abstract class GAEMetadataProvider implements MetadataProviderInterface
{
protected abstract function getTraceValue($server);
/** @var array */
private $data;
/**
* @param array $server An array for holding the values. Normally just use
* $_SERVER.
*/
public function __construct(array $server)
{
$projectId = $server['GOOGLE_CLOUD_PROJECT'] ?? 'unknown-projectid';
$serviceId = $server['GAE_SERVICE'] ?? 'unknown-service';
$versionId = $server['GAE_VERSION'] ?? 'unknown-version';
$labels = isset($server['HTTP_X_CLOUD_TRACE_CONTEXT']) ? ['appengine.googleapis.com/trace_id' => $this->getTraceValue($server)] : [];
$this->data = ['resource' => ['type' => 'gae_app', 'labels' => ['project_id' => $projectId, 'version_id' => $versionId, 'module_id' => $serviceId]], 'projectId' => $projectId, 'serviceId' => $serviceId, 'versionId' => $versionId, 'labels' => $labels];
}
/**
* Return an array representing MonitoredResource.
*
* @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
*
* @return array
*/
public function monitoredResource()
{
return $this->data['resource'];
}
/**
* Return the project id.
* @return string
*/
public function projectId()
{
return $this->data['projectId'];
}
/**
* Return the service id.
* @return string
*/
public function serviceId()
{
return $this->data['serviceId'];
}
/**
* Return the version id.
* @return string
*/
public function versionId()
{
return $this->data['versionId'];
}
/**
* Return the labels. We need to evaluate $_SERVER for each request.
* @return array
*/
public function labels()
{
return $this->data['labels'];
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Copyright 2018 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* An MetadataProvider for GAE Standard.
*/
class GAEStandardMetadataProvider extends GAEMetadataProvider
{
protected function getTraceValue($server)
{
$traceId = \substr($server['HTTP_X_CLOUD_TRACE_CONTEXT'], 0, 32);
if (isset($server['GOOGLE_CLOUD_PROJECT'])) {
return \sprintf('projects/%s/traces/%s', $server['GOOGLE_CLOUD_PROJECT'], $traceId);
}
return $traceId;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* An interface for provide some metadata for reports.
*/
interface MetadataProviderInterface
{
/**
* Return an array representing MonitoredResource.
*
* @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
*
* @return array
*/
public function monitoredResource();
/**
* Return the project id.
* @return string
*/
public function projectId();
/**
* Return the service id.
* @return string
*/
public function serviceId();
/**
* Return the version id.
* @return string
*/
public function versionId();
/**
* Return the labels.
* @return array
*/
public function labels();
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* Utility class for MetadataProvider.
*/
class MetadataProviderUtils
{
/**
* Automatically choose the most appropriate MetadataProvider and return it.
*
* @param array $server Normally pass the $_SERVER.
* @return MetadataProviderInterface
*/
public static function autoSelect($server)
{
if (isset($server['GAE_SERVICE'])) {
if (isset($server['GAE_ENV']) && $server['GAE_ENV'] === 'standard') {
return new GAEStandardMetadataProvider($server);
}
return new GAEFlexMetadataProvider($server);
}
if (!empty(\getenv('K_CONFIGURATION'))) {
return new CloudRunMetadataProvider(\getenv());
}
return new EmptyMetadataProvider();
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* Copyright 2017 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Report;
/**
* Simple MetadataProvider.
*/
class SimpleMetadataProvider implements MetadataProviderInterface
{
/** @var array */
private $data = [];
/**
* @param array $monitoredResource.
* {@see https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource}
* @param string $projectId [optional] **Defaults to** ''
* @param string $serviceId [optional] **Defaults to** ''
* @param string $versionId [optional] **Defaults to** ''
* @param array $labels [optional] **Defaults to** []
*/
public function __construct($monitoredResource = [], $projectId = '', $serviceId = '', $versionId = '', $labels = [])
{
$this->data['monitoredResource'] = $monitoredResource;
$this->data['projectId'] = $projectId;
$this->data['serviceId'] = $serviceId;
$this->data['versionId'] = $versionId;
$this->data['labels'] = $labels;
}
/**
* Return an array representing MonitoredResource.
*
* @see https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource
*
* @return array
*/
public function monitoredResource()
{
return $this->data['monitoredResource'];
}
/**
* Return the project id.
* @return string
*/
public function projectId()
{
return $this->data['projectId'];
}
/**
* Return the service id.
* @return string
*/
public function serviceId()
{
return $this->data['serviceId'];
}
/**
* Return the version id.
* @return string
*/
public function versionId()
{
return $this->data['versionId'];
}
/**
* Return the labels.
* @return array
*/
public function labels()
{
return $this->data['labels'];
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\Request;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\Uri;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\RequestInterface;
/**
* Builds a PSR7 request from a service definition.
*/
class RequestBuilder
{
use JsonTrait;
use UriTrait;
/**
* @var string
*/
private $servicePath;
/**
* @var string
*/
private $baseUri;
/**
* @var array
*/
private $resourceRoot;
/**
* @var array
*/
private $service;
/**
* @param string $servicePath
* @param string $baseUri
* @param array $resourceRoot [optional]
*/
public function __construct($servicePath, $baseUri, array $resourceRoot = [])
{
$this->service = $this->loadServiceDefinition($servicePath);
$this->resourceRoot = $resourceRoot;
// Append service definition base path if bare apiEndpoint domain is given.
if (isset($this->service['basePath'])) {
$uriParts = \parse_url($baseUri) + ['path' => null];
if (!$uriParts['path'] || $uriParts['path'] === '/') {
$uriParts['path'] = $this->service['basePath'];
// Recreate the URI from its modified parts and ensure it ends in a single slash.
$this->baseUri = \rtrim((string) Uri::fromParts($uriParts), '/') . '/';
return;
}
}
$this->baseUri = \rtrim($baseUri, '/') . '/';
}
/**
* Build the request.
*
* @param string $resource
* @param string $method
* @param array $options [optional]
* @return RequestInterface
* @todo complexity high, revisit
* @todo consider validating against the schemas
*/
public function build($resource, $method, array $options = [])
{
$root = $this->resourceRoot;
\array_push($root, 'resources');
$root = \array_merge($root, \explode('.', $resource));
\array_push($root, 'methods', $method);
$action = $this->service;
foreach ($root as $rootItem) {
if (!isset($action[$rootItem])) {
throw new \InvalidArgumentException('Provided path item ' . $rootItem . ' does not exist.');
}
$action = $action[$rootItem];
}
$path = [];
$query = [];
$body = [];
if (isset($action['parameters'])) {
foreach ($action['parameters'] as $parameter => $parameterOptions) {
if ($parameterOptions['location'] === 'path' && \array_key_exists($parameter, $options)) {
$path[$parameter] = $options[$parameter];
unset($options[$parameter]);
}
if ($parameterOptions['location'] === 'query' && \array_key_exists($parameter, $options)) {
$query[$parameter] = $options[$parameter];
}
}
}
if (isset($this->service['parameters'])) {
foreach ($this->service['parameters'] as $parameter => $parameterOptions) {
if ($parameterOptions['location'] === 'query' && \array_key_exists($parameter, $options)) {
$query[$parameter] = $options[$parameter];
}
}
}
if (isset($action['request'])) {
$schema = $action['request']['$ref'];
foreach ($this->service['schemas'][$schema]['properties'] as $property => $propertyOptions) {
if (\array_key_exists($property, $options)) {
$body[$property] = $options[$property];
}
}
}
$uri = $this->buildUriWithQuery($this->expandUri($this->baseUri . $action['path'], $path), $query);
return new Request($action['httpMethod'], $uri, ['Content-Type' => 'application/json'], $body ? $this->jsonEncode($body) : null);
}
/**
* @param string $servicePath
* @return array
*/
private function loadServiceDefinition($servicePath)
{
return $this->jsonDecode(\file_get_contents($servicePath, \true), \true);
}
}

View File

@@ -0,0 +1,114 @@
<?php
/**
* Copyright 2023 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\Serializer;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\ArrayTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\NotFoundException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\TimeTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\WhitelistTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Protobuf\Internal\Message;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\ApiException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\OperationResponse;
/**
* @internal
* Responsible for forwarding the requests to their
* respective client methdos via the request wrapper.
*/
class RequestHandler
{
use EmulatorTrait;
use ArrayTrait;
use TimeTrait;
use WhitelistTrait;
use RequestProcessorTrait;
/**
* @var Serializer
*/
private Serializer $serializer;
private array $clients;
/**
* @param Serializer $serializer
* @param array $clientClasses
* @param array $clientConfig
*/
public function __construct(Serializer $serializer, array $clientClasses, array $clientConfig = [])
{
//@codeCoverageIgnoreStart
$this->serializer = $serializer;
$clientConfig['serializer'] = $serializer;
// Adds some defaults
// gccl needs to be present for handwritten clients
$clientConfig += ['libName' => 'gccl', 'emulatorHost' => null];
if ((bool) $clientConfig['emulatorHost']) {
$emulatorConfig = $this->emulatorGapicConfig($clientConfig['emulatorHost']);
$clientConfig = \array_merge($clientConfig, $emulatorConfig);
}
//@codeCoverageIgnoreEnd
// Initialize the client classes and store them in memory
$this->clients = [];
foreach ($clientClasses as $className) {
$this->clients[$className] = new $className($clientConfig);
}
}
/**
* Helper function that forwards the request to a client obj.
*
* @param string $clientClass The request will be forwarded to this client class.
* @param string $method This method needs to be called on the client obj.
* @param Message $request The protobuf Request instance to pass as the first argument to the $method.
* @param array $optionalArgs The optional args.
* @param bool $whitelisted This decides the behaviour when a NotFoundException is encountered.
*
* @return \Generator|OperationResponse|array|null
*
* @throws ServiceException
*/
public function sendRequest(string $clientClass, string $method, Message $request, array $optionalArgs, bool $whitelisted = \false)
{
$clientObj = $this->getClientObject($clientClass);
if (!$clientObj) {
return null;
}
$allArgs = [$request];
$allArgs[] = $optionalArgs;
try {
$callable = [$clientObj, $method];
$response = \call_user_func_array($callable, $allArgs);
return $this->handleResponse($response);
} catch (ApiException $ex) {
throw $this->convertToGoogleException($ex);
} catch (NotFoundException $e) {
if ($whitelisted) {
throw $this->modifyWhitelistedError($e);
}
throw $e;
}
}
/**
* Helper function that returns a client object stored in memory
* using the client class as key.
* @param $clientClass The client class whose object we need.
* @return mixed
*/
private function getClientObject(string $clientClass)
{
return $this->clients[$clientClass] ?? null;
}
}

View File

@@ -0,0 +1,130 @@
<?php
/**
* Copyright 2024 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\ServerStream;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Rpc\Code;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\PagedListResponse;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\ApiCore\OperationResponse;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Protobuf\Internal\Message;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Rpc\RetryInfo;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Rpc\BadRequest;
/**
* @internal
* Encapsulates shared functionality of classes that need to send
* and process requests and responses.
*/
trait RequestProcessorTrait
{
/**
* @var array Map of error metadata types to RPC wrappers.
*/
private $metadataTypes = ['google.rpc.retryinfo-bin' => RetryInfo::class, 'google.rpc.badrequest-bin' => BadRequest::class];
/**
* Serializes a gRPC response.
*
* @param mixed $response
* @return \Generator|OperationResponse|array|null
*/
private function handleResponse($response)
{
if ($response instanceof PagedListResponse) {
$response = $response->getPage()->getResponseObject();
}
if ($response instanceof Message) {
return $this->serializer->encodeMessage($response);
}
if ($response instanceof OperationResponse) {
return $response;
}
if ($response instanceof ServerStream) {
return $this->handleStream($response);
}
return null;
}
/**
* Handles a streaming response.
*
* @param ServerStream $response
* @return \Generator|array|null
* @throws Exception\ServiceException
*/
private function handleStream(ServerStream $response)
{
try {
foreach ($response->readAll() as $count => $result) {
$res = $this->serializer->encodeMessage($result);
(yield $res);
}
} catch (\Exception $ex) {
throw $this->convertToGoogleException($ex);
}
}
/**
* Convert a ApiCore exception to a Google Exception.
*
* @param \Exception $ex
* @return ServiceException
*/
private function convertToGoogleException(\Exception $ex) : ServiceException
{
switch ($ex->getCode()) {
case Code::INVALID_ARGUMENT:
$exception = Exception\BadRequestException::class;
break;
case Code::NOT_FOUND:
case Code::UNIMPLEMENTED:
$exception = Exception\NotFoundException::class;
break;
case Code::ALREADY_EXISTS:
$exception = Exception\ConflictException::class;
break;
case Code::FAILED_PRECONDITION:
$exception = Exception\FailedPreconditionException::class;
break;
case Code::UNKNOWN:
$exception = Exception\ServerException::class;
break;
case Code::INTERNAL:
$exception = Exception\ServerException::class;
break;
case Code::ABORTED:
$exception = Exception\AbortedException::class;
break;
case Code::DEADLINE_EXCEEDED:
$exception = Exception\DeadlineExceededException::class;
break;
default:
$exception = Exception\ServiceException::class;
break;
}
$metadata = [];
if (\method_exists($ex, 'getMetadata') && $ex->getMetadata()) {
foreach ($ex->getMetadata() as $type => $binaryValue) {
if (!isset($this->metadataTypes[$type])) {
continue;
}
$metadataElement = new $this->metadataTypes[$type]();
$metadataElement->mergeFromString($binaryValue[0]);
$metadata[] = $this->serializer->encodeMessage($metadataElement);
}
}
return new $exception($ex->getMessage(), $ex->getCode(), $ex, $metadata);
}
}

View File

@@ -0,0 +1,423 @@
<?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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenCache;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\GetQuotaProjectInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\GetUniverseDomainInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\Guzzle6HttpHandler;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\HttpHandler\HttpHandlerFactory;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\UpdateMetadataInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\RequestWrapperTrait;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\GoogleException;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Exception\RequestException;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Promise\PromiseInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\GuzzleHttp\Psr7\Utils;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\RequestInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\ResponseInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Http\Message\StreamInterface;
/**
* The RequestWrapper is responsible for delivering and signing requests.
*/
class RequestWrapper
{
use RequestWrapperTrait;
use RetryDeciderTrait;
/**
* @var string|null The current version of the component from which the request
* originated.
*/
private $componentVersion;
/**
* @var string|null Access token used to sign requests.
*/
private $accessToken;
/**
* @var callable A handler used to deliver PSR-7 requests specifically for
* authentication.
*/
private $authHttpHandler;
/**
* @var callable A handler used to deliver PSR-7 requests.
*/
private $httpHandler;
/**
* @var callable A handler used to deliver PSR-7 requests asynchronously.
*/
private $asyncHttpHandler;
/**
* @var array HTTP client specific configuration options.
*/
private $restOptions;
/**
* @var bool Whether to enable request signing.
*/
private $shouldSignRequest;
/**
* @var callable Sets the conditions for whether or not a
* request should attempt to retry.
*/
private $retryFunction;
/**
* @var callable Executes a delay.
*/
private $delayFunction;
/**
* @var callable|null Sets the conditions for determining how long to wait
* between attempts to retry.
*/
private $calcDelayFunction;
/**
* @var string The universe domain to verify against the credentials.
*/
private string $universeDomain;
/**
* @var bool Ensure we only check the universe domain once.
*/
private bool $hasCheckedUniverse = \false;
/**
* @param array $config [optional] {
* Configuration options. Please see
* {@see \Google\Cloud\Core\RequestWrapperTrait::setCommonDefaults()} for
* the other available options.
*
* @type string $componentVersion The current version of the component from
* which the request originated.
* @type string $accessToken Access token used to sign requests.
* Deprecated: This option is no longer supported. Use the `$credentialsFetcher` option instead.
* @type callable $asyncHttpHandler *Experimental* A handler used to
* deliver PSR-7 requests asynchronously. Function signature should match:
* `function (RequestInterface $request, array $options = []) : PromiseInterface<ResponseInterface>`.
* @type callable $authHttpHandler A handler used to deliver PSR-7
* requests specifically for authentication. Function signature
* should match:
* `function (RequestInterface $request, array $options = []) : ResponseInterface`.
* @type callable $httpHandler A handler used to deliver PSR-7 requests.
* Function signature should match:
* `function (RequestInterface $request, array $options = []) : ResponseInterface`.
* @type array $restOptions HTTP client specific configuration options.
* @type bool $shouldSignRequest Whether to enable request signing.
* @type callable $restRetryFunction Sets the conditions for whether or
* not a request should attempt to retry. Function signature should
* match: `function (\Exception $ex) : bool`.
* @type callable $restDelayFunction Executes a delay, defaults to
* utilizing `usleep`. Function signature should match:
* `function (int $delay) : void`.
* @type callable $restCalcDelayFunction Sets the conditions for
* determining how long to wait between attempts to retry. Function
* signature should match: `function (int $attempt) : int`.
* @type string $universeDomain The expected universe of the credentials. Defaults to "googleapis.com".
* }
*/
public function __construct(array $config = [])
{
$this->setCommonDefaults($config);
$config += ['accessToken' => null, 'asyncHttpHandler' => null, 'authHttpHandler' => null, 'httpHandler' => null, 'restOptions' => [], 'shouldSignRequest' => \true, 'componentVersion' => null, 'restRetryFunction' => null, 'restDelayFunction' => null, 'restCalcDelayFunction' => null, 'universeDomain' => GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN];
$this->componentVersion = $config['componentVersion'];
$this->accessToken = $config['accessToken'];
$this->restOptions = $config['restOptions'];
$this->shouldSignRequest = $config['shouldSignRequest'];
$this->retryFunction = $config['restRetryFunction'] ?: $this->getRetryFunction();
$this->delayFunction = $config['restDelayFunction'] ?: function ($delay) {
\usleep($delay);
};
$this->calcDelayFunction = $config['restCalcDelayFunction'];
$this->httpHandler = $config['httpHandler'] ?: HttpHandlerFactory::build();
$this->authHttpHandler = $config['authHttpHandler'] ?: $this->httpHandler;
$this->asyncHttpHandler = $config['asyncHttpHandler'] ?: $this->buildDefaultAsyncHandler();
$this->universeDomain = $config['universeDomain'];
if ($this->credentialsFetcher instanceof AnonymousCredentials) {
$this->shouldSignRequest = \false;
}
}
/**
* Deliver the request.
*
* @param RequestInterface $request A PSR-7 request.
* @param array $options [optional] {
* Request options.
*
* @type float $requestTimeout Seconds to wait before timing out the
* request. **Defaults to** `0`.
* @type int $retries Number of retries for a failed request.
* **Defaults to** `3`.
* @type callable $restRetryFunction Sets the conditions for whether or
* not a request should attempt to retry. Function signature should
* match: `function (\Exception $ex) : bool`.
* @type callable $restRetryListener Runs after the restRetryFunction.
* This might be used to simply consume the exception and
* $arguments b/w retries. This returns the new $arguments thus
* allowing modification on demand for $arguments. For ex:
* changing the headers in b/w retries.
* @type callable $restDelayFunction Executes a delay, defaults to
* utilizing `usleep`. Function signature should match:
* `function (int $delay) : void`.
* @type callable $restCalcDelayFunction Sets the conditions for
* determining how long to wait between attempts to retry. Function
* signature should match: `function (int $attempt) : int`.
* @type array $restOptions HTTP client specific configuration options.
* }
* @return ResponseInterface
* @throws ServiceException
*/
public function send(RequestInterface $request, array $options = [])
{
$retryOptions = $this->getRetryOptions($options);
$backoff = new ExponentialBackoff($retryOptions['retries'], $retryOptions['retryFunction'], $retryOptions['retryListener']);
if ($retryOptions['delayFunction']) {
$backoff->setDelayFunction($retryOptions['delayFunction']);
}
if ($retryOptions['calcDelayFunction']) {
$backoff->setCalcDelayFunction($retryOptions['calcDelayFunction']);
}
try {
return $backoff->execute($this->httpHandler, [$this->applyHeaders($request, $options), $this->getRequestOptions($options)]);
} catch (\Exception $ex) {
throw $this->convertToGoogleException($ex);
}
}
/**
* Deliver the request asynchronously.
*
* @param RequestInterface $request A PSR-7 request.
* @param array $options [optional] {
* Request options.
*
* @type float $requestTimeout Seconds to wait before timing out the
* request. **Defaults to** `0`.
* @type int $retries Number of retries for a failed request.
* **Defaults to** `3`.
* @type callable $restRetryFunction Sets the conditions for whether or
* not a request should attempt to retry. Function signature should
* match: `function (\Exception $ex, int $retryAttempt) : bool`.
* @type callable $restDelayFunction Executes a delay, defaults to
* utilizing `usleep`. Function signature should match:
* `function (int $delay) : void`.
* @type callable $restCalcDelayFunction Sets the conditions for
* determining how long to wait between attempts to retry. Function
* signature should match: `function (int $attempt) : int`.
* @type array $restOptions HTTP client specific configuration options.
* }
* @return PromiseInterface<ResponseInterface>
* @throws ServiceException
* @experimental The experimental flag means that while we believe this method
* or class is ready for use, it may change before release in backwards-
* incompatible ways. Please use with caution, and test thoroughly when
* upgrading.
*/
public function sendAsync(RequestInterface $request, array $options = [])
{
// Unfortunately, the current ExponentialBackoff implementation doesn't
// play nicely with promises.
$retryAttempt = 0;
$fn = function ($retryAttempt) use(&$fn, $request, $options) {
$asyncHttpHandler = $this->asyncHttpHandler;
$retryOptions = $this->getRetryOptions($options);
if (!$retryOptions['calcDelayFunction']) {
$retryOptions['calcDelayFunction'] = [ExponentialBackoff::class, 'calculateDelay'];
}
return $asyncHttpHandler($this->applyHeaders($request, $options), $this->getRequestOptions($options))->then(null, function (\Exception $ex) use($fn, $retryAttempt, $retryOptions) {
$shouldRetry = $retryOptions['retryFunction']($ex, $retryAttempt);
if ($shouldRetry === \false || $retryAttempt >= $retryOptions['retries']) {
throw $this->convertToGoogleException($ex);
}
$delay = $retryOptions['calcDelayFunction']($retryAttempt);
$retryOptions['delayFunction']($delay);
$retryAttempt++;
return $fn($retryAttempt);
});
};
return $fn($retryAttempt);
}
/**
* Applies headers to the request.
*
* @param RequestInterface $request A PSR-7 request.
* @param array $options
* @return RequestInterface
*/
private function applyHeaders(RequestInterface $request, array $options = [])
{
$headers = ['User-Agent' => 'gcloud-php/' . $this->componentVersion, Retry::RETRY_HEADER_KEY => \sprintf('gl-php/%s gccl/%s', \PHP_VERSION, $this->componentVersion)];
if (isset($options['retryHeaders'])) {
$headers[Retry::RETRY_HEADER_KEY] = \sprintf('%s %s', $headers[Retry::RETRY_HEADER_KEY], \implode(' ', $options['retryHeaders']));
unset($options['retryHeaders']);
}
$request = Utils::modifyRequest($request, ['set_headers' => $headers]);
if ($this->shouldSignRequest) {
$quotaProject = $this->quotaProject;
if ($this->accessToken) {
// if an access token is provided, check the universe domain against "googleapis.com"
$this->checkUniverseDomain(null);
$request = $request->withHeader('authorization', 'Bearer ' . $this->accessToken);
} else {
// if a credentials fetcher is provided, check the universe domain against the
// credential's universe domain
$credentialsFetcher = $this->getCredentialsFetcher();
$this->checkUniverseDomain($credentialsFetcher);
$request = $this->addAuthHeaders($request, $credentialsFetcher);
if ($credentialsFetcher instanceof GetQuotaProjectInterface) {
$quotaProject = $credentialsFetcher->getQuotaProject();
}
}
if ($quotaProject) {
$request = $request->withHeader('X-Goog-User-Project', $quotaProject);
}
} else {
// If we are not signing the request, check the universe domain against "googleapis.com"
$this->checkUniverseDomain(null);
}
return $request;
}
/**
* Adds auth headers to the request.
*
* @param RequestInterface $request
* @param FetchAuthTokenInterface $fetcher
* @return array
* @throws ServiceException
*/
private function addAuthHeaders(RequestInterface $request, FetchAuthTokenInterface $fetcher)
{
$backoff = new ExponentialBackoff($this->retries, $this->getRetryFunction());
try {
return $backoff->execute(function () use($request, $fetcher) {
if (!$fetcher instanceof UpdateMetadataInterface || $fetcher instanceof FetchAuthTokenCache && !$fetcher->getFetcher() instanceof UpdateMetadataInterface) {
// This covers an edge case where the token fetcher does not implement UpdateMetadataInterface,
// which only would happen if a user implemented a custom fetcher
if ($token = $fetcher->fetchAuthToken($this->authHttpHandler)) {
return $request->withHeader('authorization', 'Bearer ' . $token['access_token']);
}
} else {
$headers = $fetcher->updateMetadata($request->getHeaders(), null, $this->authHttpHandler);
return Utils::modifyRequest($request, ['set_headers' => $headers]);
}
// As we do not know the reason the credentials fetcher could not fetch the
// token, we should not retry.
throw new \RuntimeException('Unable to fetch token');
});
} catch (\Exception $ex) {
throw $this->convertToGoogleException($ex);
}
}
/**
* Convert any exception to a Google Exception.
*
* @param \Exception $ex
* @return Exception\ServiceException
*/
private function convertToGoogleException(\Exception $ex)
{
switch ($ex->getCode()) {
case 400:
$exception = Exception\BadRequestException::class;
break;
case 404:
$exception = Exception\NotFoundException::class;
break;
case 409:
$exception = Exception\ConflictException::class;
break;
case 412:
$exception = Exception\FailedPreconditionException::class;
break;
case 500:
$exception = Exception\ServerException::class;
break;
case 504:
$exception = Exception\DeadlineExceededException::class;
break;
default:
$exception = Exception\ServiceException::class;
break;
}
return new $exception($this->getExceptionMessage($ex), $ex->getCode(), $ex);
}
/**
* Gets the exception message.
*
* @access private
* @param \Exception $ex
* @return string
*/
private function getExceptionMessage(\Exception $ex)
{
if ($ex instanceof RequestException && $ex->hasResponse()) {
return (string) $ex->getResponse()->getBody();
}
return $ex->getMessage();
}
/**
* Gets a set of request options.
*
* @param array $options
* @return array
*/
private function getRequestOptions(array $options)
{
$restOptions = $options['restOptions'] ?? $this->restOptions;
$timeout = $options['requestTimeout'] ?? $this->requestTimeout;
if ($timeout && !\array_key_exists('timeout', $restOptions)) {
$restOptions['timeout'] = $timeout;
}
return $restOptions;
}
/**
* Gets a set of retry options.
*
* @param array $options
* @return array
*/
private function getRetryOptions(array $options)
{
return ['retries' => isset($options['retries']) ? $options['retries'] : $this->retries, 'retryFunction' => isset($options['restRetryFunction']) ? $options['restRetryFunction'] : $this->retryFunction, 'retryListener' => isset($options['restRetryListener']) ? $options['restRetryListener'] : null, 'delayFunction' => isset($options['restDelayFunction']) ? $options['restDelayFunction'] : $this->delayFunction, 'calcDelayFunction' => isset($options['restCalcDelayFunction']) ? $options['restCalcDelayFunction'] : $this->calcDelayFunction];
}
/**
* Builds the default async HTTP handler.
*
* @return callable
*/
private function buildDefaultAsyncHandler()
{
return $this->httpHandler instanceof Guzzle6HttpHandler ? [$this->httpHandler, 'async'] : [HttpHandlerFactory::build(), 'async'];
}
/**
* Verify that the expected universe domain matches the universe domain from the credentials.
*/
private function checkUniverseDomain(FetchAuthTokenInterface $credentialsFetcher = null)
{
if (\false === $this->hasCheckedUniverse) {
if ($this->universeDomain === '') {
throw new GoogleException('The universe domain cannot be empty.');
}
if (\is_null($credentialsFetcher)) {
if ($this->universeDomain !== GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN) {
throw new GoogleException(\sprintf('The accessToken option is not supported outside of the default universe domain (%s).', GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN));
}
} else {
$credentialsUniverse = $credentialsFetcher instanceof GetUniverseDomainInterface ? $credentialsFetcher->getUniverseDomain() : GetUniverseDomainInterface::DEFAULT_UNIVERSE_DOMAIN;
if ($credentialsUniverse !== $this->universeDomain) {
throw new GoogleException(\sprintf('The configured universe domain (%s) does not match the credential universe domain (%s)', $this->universeDomain, $credentialsUniverse));
}
}
$this->hasCheckedUniverse = \true;
}
}
}

View File

@@ -0,0 +1,183 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\ApplicationDefaultCredentials;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\Cache\MemoryCacheItemPool;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\Credentials\ServiceAccountCredentials;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\CredentialsLoader;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenCache;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Auth\FetchAuthTokenInterface;
use DeliciousBrains\WP_Offload_Media\Gcp\Psr\Cache\CacheItemPoolInterface;
/**
* Encapsulates shared functionality of request wrappers.
*/
trait RequestWrapperTrait
{
/**
* @var CacheItemPoolInterface A cache used for storing tokens.
*/
private $authCache;
/**
* @var array Cache configuration options.
*/
private $authCacheOptions;
/**
* @var FetchAuthTokenInterface|null Fetches credentials.
*/
private $credentialsFetcher;
/**
* @var array The contents of the service account credentials .json file
* retrieved from the Google Developers Console.
*/
private $keyFile;
/**
* @var float Seconds to wait before timing out the request. **Defaults to**
* `0` with REST and `60` with gRPC.
*/
private $requestTimeout;
/**
* @var int Number of retries for a failed request. **Defaults to** `3`.
*/
private $retries;
/**
* @var array Scopes to be used for the request.
*/
private $scopes = [];
/**
* @var string|null The user project to bill for access charges associated
* with the request.
*/
private $quotaProject;
/**
* Sets common defaults between request wrappers.
*
* @param array $config {
* Configuration options.
*
* @type CacheItemPoolInterface $authCache A cache for storing access
* tokens. **Defaults to** a simple in memory implementation.
* @type array $authCacheOptions Cache configuration options.
* @type FetchAuthTokenInterface $credentialsFetcher A credentials
* fetcher instance.
* @type array $keyFile The contents of the service account credentials
* .json file retrieved from the Google Developer's Console.
* Ex: `json_decode(file_get_contents($path), true)`.
* @type float $requestTimeout Seconds to wait before timing out the
* request. **Defaults to** `0` with REST and `60` with gRPC.
* @type int $retries Number of retries for a failed request.
* **Defaults to** `3`.
* @type array $scopes Scopes to be used for the request.
* @type string $quotaProject Specifies a user project to bill for
* access charges associated with the request.
* }
* @throws \InvalidArgumentException
*/
public function setCommonDefaults(array $config)
{
$config += ['authCache' => new MemoryCacheItemPool(), 'authCacheOptions' => [], 'credentialsFetcher' => null, 'keyFile' => null, 'requestTimeout' => null, 'retries' => 3, 'scopes' => null, 'quotaProject' => null];
if ($config['credentialsFetcher'] && !$config['credentialsFetcher'] instanceof FetchAuthTokenInterface) {
throw new \InvalidArgumentException('credentialsFetcher must implement FetchAuthTokenInterface.');
}
if (!$config['authCache'] instanceof CacheItemPoolInterface) {
throw new \InvalidArgumentException('authCache must implement CacheItemPoolInterface.');
}
$this->authCache = $config['authCache'];
$this->authCacheOptions = $config['authCacheOptions'];
$this->credentialsFetcher = $config['credentialsFetcher'];
$this->retries = $config['retries'];
$this->scopes = $config['scopes'];
$this->keyFile = $config['keyFile'];
$this->requestTimeout = $config['requestTimeout'];
$this->quotaProject = $config['quotaProject'];
}
/**
* Get the Keyfile.
*
* @return array
*/
public function keyFile()
{
return $this->keyFile;
}
/**
* Get the scopes
*
* @return array
*/
public function scopes()
{
return $this->scopes;
}
/**
* Gets the credentials fetcher and sets up caching. Precedence is as
* follows:
*
* - A user supplied credentials fetcher instance.
* - Credentials created from a keyfile.
* - Application default credentials.
* - Anonymous credentials.
*
* @return FetchAuthTokenInterface
*/
public function getCredentialsFetcher()
{
$fetcher = null;
if ($this->credentialsFetcher) {
$fetcher = $this->credentialsFetcher;
} else {
if ($this->keyFile) {
if ($this->quotaProject) {
$this->keyFile['quota_project_id'] = $this->quotaProject;
}
$fetcher = CredentialsLoader::makeCredentials($this->scopes, $this->keyFile);
} else {
try {
$fetcher = $this->getADC();
} catch (\DomainException $ex) {
$fetcher = new AnonymousCredentials();
}
}
// Note: If authCache is set and keyFile is not set, the resulting
// credentials instance will be FetchAuthTokenCache, and we will be
// unable to enable "useJwtAccessWithScope". This is unlikely, as
// keyFile is automatically set in ClientTrait::configureAuthentication,
// and so should always exist when ServiceAccountCredentials are in use.
if ($fetcher instanceof ServiceAccountCredentials) {
$fetcher->useJwtAccessWithScope();
}
}
if ($fetcher instanceof FetchAuthTokenCache) {
// The fetcher has already been wrapped in a cache by `ApplicationDefaultCredentials`;
// no need to wrap it another time.
return $fetcher;
} else {
return new FetchAuthTokenCache($fetcher, $this->authCacheOptions, $this->authCache);
}
}
/**
* Returns application default credentials. Abstracted out for unit testing.
*
* @return FetchAuthTokenInterface
* @throws \DomainException
*/
protected function getADC()
{
return ApplicationDefaultCredentials::getCredentials($this->scopes, $this->authHttpHandler, $this->authCacheOptions, $this->authCache, $this->quotaProject);
}
}

View File

@@ -0,0 +1,124 @@
<?php
/**
* Copyright 2016 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 DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\NotFoundException;
use DeliciousBrains\WP_Offload_Media\Gcp\Google\Cloud\Core\Exception\ServiceException;
use UnexpectedValueException;
/**
* Provides shared functionality for REST service implementations.
*/
trait RestTrait
{
use ArrayTrait;
use JsonTrait;
use WhitelistTrait;
/**
* @var RequestBuilder Builds PSR7 requests from a service definition.
*/
private $requestBuilder;
/**
* @var RequestWrapper Wrapper used to handle sending requests to the
* JSON API.
*/
private $requestWrapper;
/**
* Sets the request builder.
*
* @param RequestBuilder $requestBuilder Builds PSR7 requests from a service
* definition.
*/
public function setRequestBuilder(RequestBuilder $requestBuilder)
{
$this->requestBuilder = $requestBuilder;
}
/**
* Sets the request wrapper.
*
* @param RequestWrapper $requestWrapper Wrapper used to handle sending
* requests to the JSON API.
*/
public function setRequestWrapper(RequestWrapper $requestWrapper)
{
$this->requestWrapper = $requestWrapper;
}
/**
* Get the RequestWrapper.
*
* @return RequestWrapper|null
*/
public function requestWrapper()
{
return $this->requestWrapper;
}
/**
* Delivers a request built from the service definition.
*
* @param string $resource The resource type used for the request.
* @param string $method The method used for the request.
* @param array $options [optional] Options used to build out the request.
* @param array $whitelisted [optional]
* @return array
* @throws ServiceException
*/
public function send($resource, $method, array $options = [], $whitelisted = \false)
{
$options += ['prettyPrint' => \false];
$requestOptions = $this->pluckArray(['restOptions', 'retries', 'retryHeaders', 'requestTimeout', 'restRetryFunction', 'restRetryListener'], $options);
try {
return \json_decode($this->requestWrapper->send($this->requestBuilder->build($resource, $method, $options), $requestOptions)->getBody(), \true);
} catch (NotFoundException $e) {
if ($whitelisted) {
throw $this->modifyWhitelistedError($e);
}
throw $e;
}
}
/**
* Return a custom API endpoint in the proper format, or default if none provided.
*
* @param string $default
* @param array $config
* @param string $apiEndpointTemplate
* @return string
*/
private function getApiEndpoint($default, array $config, string $apiEndpointTemplate = null)
{
// If the $default parameter is provided, or the user has set an "apiEndoint" config option,
// fall back to the previous behavior.
if ($res = $config['apiEndpoint'] ?? $default) {
if (\substr($res, -1) !== '/') {
$res = $res . '/';
}
if (\strpos($res, '//') === \false) {
$res = 'https://' . $res;
}
return $res;
}
// One of the $default or the $template must always be set
if (!$apiEndpointTemplate) {
throw new UnexpectedValueException('An API endpoint template must be provided if no "apiEndpoint" or default endpoint is set.');
}
if (!isset($config['universeDomain'])) {
throw new UnexpectedValueException('The "universeDomain" config value must be set to use the default API endpoint template.');
}
$apiEndpoint = \str_replace('UNIVERSE_DOMAIN', $config['universeDomain'], $apiEndpointTemplate);
// Preserve the behavior of guaranteeing a trailing "/"
return $apiEndpoint . (\substr($apiEndpoint, -1) !== '/' ? '/' : '');
}
}

Some files were not shown because too many files have changed in this diff Show More