diff options
| author | manzerbredes <loic.guegan_secondary@yahoo.fr> | 2016-03-15 16:17:39 +0100 |
|---|---|---|
| committer | manzerbredes <loic.guegan_secondary@yahoo.fr> | 2016-03-15 16:17:39 +0100 |
| commit | 26d10bc0fa4befbac54453228ae1ce89021bdec2 (patch) | |
| tree | 029d7240ecf7416205e5f76cf9107a6b5bdf8ca3 /server/vendor/php-opencloud/common/src | |
| parent | 8ad216dedf017f3d6de047a25d08db3b98e16361 (diff) | |
| parent | 03ef74d0cfe675a6e18a91f039182ca1b248d8f5 (diff) | |
Merge branch 'develop' into loic
Diffstat (limited to 'server/vendor/php-opencloud/common/src')
39 files changed, 2926 insertions, 0 deletions
diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php new file mode 100644 index 0000000..beee5e8 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php @@ -0,0 +1,33 @@ +<?php + +namespace OpenCloud\Common\Api; + +abstract class AbstractApi implements ApiInterface +{ + protected $params; + + protected function isRequired(array $param) + { + return array_merge($param, ['required' => true]); + } + + protected function notRequired(array $param) + { + return array_merge($param, ['required' => false]); + } + + protected function query(array $param) + { + return array_merge($param, ['location' => AbstractParams::QUERY]); + } + + protected function url(array $param) + { + return array_merge($param, ['location' => AbstractParams::URL]); + } + + public function documented(array $param) + { + return array_merge($param, ['required' => true]); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php new file mode 100644 index 0000000..225e025 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php @@ -0,0 +1,100 @@ +<?php + +namespace OpenCloud\Common\Api; + +abstract class AbstractParams +{ + // locations + const QUERY = 'query'; + const HEADER = 'header'; + const URL = 'url'; + const JSON = 'json'; + const RAW = 'raw'; + + // types + const STRING_TYPE = "string"; + const BOOL_TYPE = "boolean"; + const BOOLEAN_TYPE = self::BOOL_TYPE; + const OBJECT_TYPE = "object"; + const ARRAY_TYPE = "array"; + const NULL_TYPE = "NULL"; + const INT_TYPE = 'integer'; + const INTEGER_TYPE = self::INT_TYPE; + + public static function isSupportedLocation($val) + { + return in_array($val, [self::QUERY, self::HEADER, self::URL, self::JSON, self::RAW]); + } + + public function limit() + { + return [ + 'type' => self::INT_TYPE, + 'location' => 'query', + 'description' => <<<DESC +This will limit the total amount of elements returned in a list up to the number specified. For example, specifying a +limit of 10 will return 10 elements, regardless of the actual count. +DESC + ]; + } + + public function marker() + { + return [ + 'type' => 'string', + 'location' => 'query', + 'description' => <<<DESC +Specifying a marker will begin the list from the value specified. Elements will have a particular attribute that +identifies them, such as a name or ID. The marker value will search for an element whose identifying attribute matches +the marker value, and begin the list from there. +DESC + ]; + } + + public function id($type) + { + return [ + 'description' => sprintf("The unique ID, or identifier, for the %s", $type), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + public function idPath() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::URL, + 'description' => 'The unique ID of the resource', + ]; + } + + public function name($resource) + { + return [ + 'description' => sprintf("The name of the %s", $resource), + 'type' => self::STRING_TYPE, + 'location' => self::JSON, + ]; + } + + + public function sortDir() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + 'enum' => ['asc', 'desc'] + ]; + } + + public function sortKey() + { + return [ + 'type' => self::STRING_TYPE, + 'location' => self::QUERY, + 'description' => "Sorts by one or more sets of attribute and sort direction combinations.", + ]; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php new file mode 100644 index 0000000..d5d26a0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php @@ -0,0 +1,20 @@ +<?php + +namespace OpenCloud\Common\Api; + +/** + * All classes which implement this interface are a data representation of a remote OpenCloud API. + * They do not execute functionality, but instead return data for each API operation for other parts + * of the SDK to use. Usually, the data is injected into {@see OpenCloud\Common\Api\Operation} objects. + * The operation is then serialized into a {@see GuzzleHttp\Message\Request} and sent to the API. + * + * The reason for storing all the API-specific data is to decouple service information from client + * HTTP functionality. Too often it is mixed all across different layers, leading to duplication and + * no separation of concerns. The choice was made for storage in PHP classes, rather than YAML or JSON + * syntax, due to performance concerns. + * + * @package OpenCloud\Common\Api + */ +interface ApiInterface +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Operation.php b/server/vendor/php-opencloud/common/src/Common/Api/Operation.php new file mode 100644 index 0000000..3155ca4 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Operation.php @@ -0,0 +1,136 @@ +<?php + +namespace OpenCloud\Common\Api; + +use GuzzleHttp\Utils; + +/** + * This class represents an OpenCloud API operation. It encapsulates most aspects of the REST operation: its HTTP + * method, the URL path, its top-level JSON key, and all of its {@see Parameter} objects. + * + * An operation not only represents a remote operation, but it also provides the mechanism for executing it + * over HTTP. To do this, it uses a {@see ClientInterface} that allows a {@see GuzzleHttp\Message\Request} + * to be created from the user values provided. Once this request is assembled, it is then sent to the + * remote API and the response is returned to whoever first invoked the Operation class. + * + * @package OpenCloud\Common\Api + */ +class Operation +{ + /** @var string The HTTP method */ + private $method; + + /** @var string The URL path */ + private $path; + + /** @var string The top-level JSON key */ + private $jsonKey; + + /** @var []Parameter The parameters of this operation */ + private $params; + + /** + * @param array $definition The data definition (in array form) that will populate this + * operation. Usually this is retrieved from an {@see ApiInterface} + * object method. + */ + public function __construct(array $definition) + { + $this->method = $definition['method']; + $this->path = $definition['path']; + + if (isset($definition['jsonKey'])) { + $this->jsonKey = $definition['jsonKey']; + } + + $this->params = self::toParamArray($definition['params']); + } + + /** + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * @return string + */ + public function getMethod() + { + return $this->method; + } + + /** + * Indicates whether this operation supports a parameter. + * + * @param $key The name of a parameter + * + * @return bool + */ + public function hasParam($key) + { + return isset($this->params[$key]); + } + + /** + * @param $name + * + * @return Parameter + */ + public function getParam($name) + { + return isset($this->params[$name]) ? $this->params[$name] : null; + } + + /** + * @return string + */ + public function getJsonKey() + { + return $this->jsonKey; + } + + /** + * A convenience method that will take a generic array of data and convert it into an array of + * {@see Parameter} objects. + * + * @param array $data A generic data array + * + * @return array + */ + public static function toParamArray(array $data) + { + $params = []; + + foreach ($data as $name => $param) { + $params[$name] = new Parameter($param + ['name' => $name]); + } + + return $params; + } + + /** + * This method will validate all of the user-provided values and throw an exception if any + * failures are detected. This is useful for basic sanity-checking before a request is + * serialized and sent to the API. + * + * @param array $userValues The user-defined values + * + * @return bool TRUE if validation passes + * @throws \Exception If validate fails + */ + public function validate(array $userValues) + { + foreach ($this->params as $paramName => $param) { + if (array_key_exists($paramName, $userValues)) { + $param->validate($userValues[$paramName]); + } elseif ($param->isRequired()) { + throw new \Exception(sprintf('"%s" is a required option, but it was not provided', $paramName)); + } + } + + return true; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Operator.php b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php new file mode 100644 index 0000000..4325b69 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Operator.php @@ -0,0 +1,173 @@ +<?php + +namespace OpenCloud\Common\Api; + +use function GuzzleHttp\uri_template; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Promise\Promise; +use OpenCloud\Common\Resource\ResourceInterface; +use OpenCloud\Common\Transport\RequestSerializer; +use Psr\Http\Message\ResponseInterface; + +/** + * {@inheritDoc} + */ +abstract class Operator implements OperatorInterface +{ + /** @var ClientInterface */ + protected $client; + + /** @var ApiInterface */ + protected $api; + + /** + * {@inheritDoc} + */ + public function __construct(ClientInterface $client, ApiInterface $api) + { + $this->client = $client; + $this->api = $api; + } + + /** + * Magic method for dictating how objects are rendered when var_dump is called. + * For the benefit of users, extremely verbose and heavy properties (such as HTTP clients) are + * removed to provide easier access to normal state, such as resource attributes. + * + * @codeCoverageIgnore + * @return array + */ + public function __debugInfo() + { + $excludedVars = ['client', 'errorBuilder', 'api']; + + $output = []; + + foreach (get_object_vars($this) as $key => $val) { + if (!in_array($key, $excludedVars)) { + $output[$key] = $val; + } + } + + return $output; + } + + /** + * Retrieves a populated Operation according to the definition and values provided. A + * HTTP client is also injected into the object to allow it to communicate with the remote API. + * + * @param array $definition The data that dictates how the operation works + * + * @return Operation + */ + public function getOperation(array $definition) + { + return new Operation($definition); + } + + protected function sendRequest(Operation $operation, array $userValues = [], $async = false) + { + $operation->validate($userValues); + + $options = (new RequestSerializer)->serializeOptions($operation, $userValues); + $method = $async ? 'requestAsync' : 'request'; + $uri = uri_template($operation->getPath(), $userValues); + + return $this->client->$method($operation->getMethod(), $uri, $options); + } + + /** + * {@inheritDoc} + */ + public function execute(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues); + } + + /** + * {@inheritDoc} + */ + public function executeAsync(array $definition, array $userValues = []) + { + return $this->sendRequest($this->getOperation($definition), $userValues, true); + } + + /** + * {@inheritDoc} + */ + public function model($class, $data = null) + { + $model = new $class($this->client, $this->api); + + // @codeCoverageIgnoreStart + if (!$model instanceof ResourceInterface) { + throw new \RuntimeException(sprintf('%s does not implement %s', $class, ResourceInterface::class)); + } + // @codeCoverageIgnoreEnd + + if ($data instanceof ResponseInterface) { + $model->populateFromResponse($data); + } elseif (is_array($data)) { + $model->populateFromArray($data); + } + + return $model; + } + + /** + * Will create a new instance of this class with the current HTTP client and API injected in. This + * is useful when enumerating over a collection since multiple copies of the same resource class + * are needed. + * + * @return static + */ + public function newInstance() + { + return new static($this->client, $this->api); + } + + /** + * @return \GuzzleHttp\Psr7\Uri + */ + protected function getHttpBaseUrl() + { + return $this->client->getConfig('base_uri'); + } + + /** + * Magic method which intercepts async calls, finds the sequential version, and wraps it in a + * {@see Promise} object. In order for this to happen, the called methods need to be in the + * following format: `createAsync`, where `create` is the sequential method being wrapped. + * + * @param $methodName The name of the method being invoked. + * @param $args The arguments to be passed to the sequential method. + * + * @throws \RuntimeException If method does not exist + * + * @return Promise + */ + public function __call($methodName, $args) + { + $e = function ($name) { + return new \RuntimeException(sprintf('%s::%s is not defined', get_class($this), $name)); + }; + + if (substr($methodName, -5) === 'Async') { + $realMethod = substr($methodName, 0, -5); + if (!method_exists($this, $realMethod)) { + throw $e($realMethod); + } + + $promise = new Promise( + function () use (&$promise, $realMethod, $args) { + $value = call_user_func_array([$this, $realMethod], $args); + $promise->resolve($value); + } + ); + + return $promise; + } + + throw $e($methodName); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php new file mode 100644 index 0000000..43c6ce2 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php @@ -0,0 +1,51 @@ +<?php + +namespace OpenCloud\Common\Api; + +use GuzzleHttp\ClientInterface; + +/** + * An operator is any resource or service that can invoke and send REST operations. In other words, it + * is any class that can send requests and receive responses with a HTTP client. To do this + * it needs two things: a {@see ClientInterface} for handling HTTP transactions and an {@see ApiInterface} + * for handling how operations are created. + * + * @package OpenCloud\Common\Api + */ +interface OperatorInterface +{ + /** + * @param ClientInterface $client The HTTP client responsible for handling HTTP transactions + * @param ApiInterface $api The data API class that dictates how REST operations are structured + */ + public function __construct(ClientInterface $client, ApiInterface $api); + + /** + * A convenience method that assembles an operation and sends it to the remote API + * + * @param array $definition The data that dictates how the operation works + * @param array $userValues The user-defined values that populate the request + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function execute(array $definition, array $userValues = []); + + /** + * A convenience method that assembles an operation and asynchronously sends it to the remote API + * + * @param array $definition The data that dictates how the operation works + * @param array $userValues The user-defined values that populate the request + * + * @return \GuzzleHttp\Promise\PromiseInterface + */ + public function executeAsync(array $definition, array $userValues = []); + + /** + * @param string $name The name of the model class. + * @param mixed $data Either a {@see ResponseInterface} or data array that will populate the newly + * created model class. + * + * @return \OpenCloud\Common\Resource\ResourceInterface + */ + public function model($name, $data = null); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php b/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php new file mode 100644 index 0000000..97330a4 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Api/Parameter.php @@ -0,0 +1,389 @@ +<?php + +namespace OpenCloud\Common\Api; + +use OpenCloud\Common\HydratorStrategyTrait; + +/** + * Represents an individual request parameter in a RESTful operation. A parameter can take on many forms: + * in a URL path, in a URL query, in a JSON body, and in a HTTP header. It is worth documenting brifly each + * variety of parameter: + * + * * Header parameters are those which populate a HTTP header in a request. Header parameters can have + * aliases; for example, a user-facing name of "Foo" can be sent over the wire as "X-Foo_Bar", as defined + * by ``sentAs``. Prefixes can also be used. + * + * * Query parameters are those which populate a URL query parameter. The value is therefore usually + * confined to a string. + * + * * JSON parameters are those which populate a JSON request body. These are the most complex variety + * of Parameter, since there are so many different ways a JSON document can be constructed. The SDK + * supports deep-nesting according to a XPath syntax; for more information, see {@see \OpenCloud\Common\JsonPath}. + * Nested object and array properties are also supported since JSON is a recursive data type. What + * this means is that a Parameter can have an assortment of child Parameters, one for each object + * property or array element. + * + * * Raw parameters are those which populate a non-JSON request body. This is typically used for + * uploading payloads (such as Swift object data) to a remote API. + * + * * Path parameters are those which populate a URL path. They are serialized according to URL + * placeholders. + * + * @package OpenCloud\Common\Api + */ +class Parameter +{ + use HydratorStrategyTrait; + + const DEFAULT_LOCATION = 'json'; + + /** + * The human-friendly name of the parameter. This is what the user will input. + * + * @var string + */ + private $name; + + /** + * The alias for this parameter. Although the user will always interact with the human-friendly $name property, + * the $sentAs is what's used over the wire. + * + * @var string + */ + private $sentAs; + + /** + * For array parameters (for example, an array of security group names when creating a server), each array element + * will need to adhere to a common schema. For the aforementioned example, each element will need to be a string. + * For more complicated parameters, you might be validated an array of complicated objects. + * + * @var Parameter + */ + private $itemSchema; + + /** + * For object parameters, each property will need to adhere to a specific schema. For every property in the + * object, it has its own schema - meaning that this property is a hash of name/schema pairs. + * + * The *only* exception to this rule is for metadata parameters, which are arbitrary key/value pairs. Since it does + * not make sense to have a schema for each metadata key, a common schema is use for every one. So instead of this + * property being a hash of schemas, it is a single Parameter object instead. This single Parameter schema will + * then be applied to each metadata key provided. + * + * @var []Parameter|Parameter + */ + private $properties; + + /** + * The value's PHP type which this parameter represents; either "string", "bool", "object", "array", "NULL". + * + * @var string + */ + private $type; + + /** + * Indicates whether this parameter requires a value from the user. + * + * @var bool + */ + private $required; + + /** + * The location in the HTTP request where this parameter will populate; either "header", "url", "query", "raw" or + * "json". + * + * @var string + */ + private $location; + + /** + * Relevant to "json" location parameters only. This property allows for deep nesting through the use of + * {@see OpenCloud\Common\JsonPath}. + * + * @var string + */ + private $path; + + /** + * Allows for the prefixing of parameter names. + * + * @var string + */ + private $prefix; + + /** + * The enum values for which this param is restricted. + * + * @var array + */ + private $enum; + + /** + * @param array $data + */ + public function __construct(array $data) + { + $this->hydrate($data); + + $this->required = (bool)$this->required; + + $this->stockLocation($data); + $this->stockItemSchema($data); + $this->stockProperties($data); + } + + private function stockLocation(array $data) + { + $this->location = isset($data['location']) ? $data['location'] : self::DEFAULT_LOCATION; + + if (!AbstractParams::isSupportedLocation($this->location)) { + throw new \RuntimeException(sprintf("%s is not a permitted location", $this->location)); + } + } + + private function stockItemSchema(array $data) + { + if (isset($data['items'])) { + $this->itemSchema = new Parameter($data['items']); + } + } + + private function stockProperties(array $data) + { + if (isset($data['properties'])) { + if (stripos($this->name, 'metadata') !== false) { + $this->properties = new Parameter($data['properties']); + } else { + foreach ($data['properties'] as $name => $property) { + $this->properties[$name] = new Parameter($property + ['name' => $name]); + } + } + } + } + + /** + * Retrieve the name that will be used over the wire. + * + * @return string + */ + public function getName() + { + return $this->sentAs ?: $this->name; + } + + /** + * Indicates whether the user must provide a value for this parameter. + * + * @return bool + */ + public function isRequired() + { + return $this->required === true; + } + + /** + * Validates a given user value and checks whether it passes basic sanity checking, such as types. + * + * @param $userValues The value provided by the user + * + * @return bool TRUE if the validation passes + * @throws \Exception If validation fails + */ + public function validate($userValues) + { + $this->validateEnums($userValues); + $this->validateType($userValues); + + if ($this->isArray()) { + $this->validateArray($userValues); + } elseif ($this->isObject()) { + $this->validateObject($userValues); + } + + return true; + } + + private function validateEnums($userValues) + { + if (!empty($this->enum) && $this->type == 'string' && !in_array($userValues, $this->enum)) { + throw new \Exception(sprintf( + 'The only permitted values are %s. You provided %s', implode(', ', $this->enum), print_r($userValues, true) + )); + } + } + + private function validateType($userValues) + { + if (!$this->hasCorrectType($userValues)) { + throw new \Exception(sprintf( + 'The key provided "%s" has the wrong value type. You provided %s (%s) but was expecting %s', + $this->name, print_r($userValues, true), gettype($userValues), $this->type + )); + } + } + + private function validateArray($userValues) + { + foreach ($userValues as $userValue) { + $this->itemSchema->validate($userValue); + } + } + + private function validateObject($userValues) + { + foreach ($userValues as $key => $userValue) { + $property = $this->getNestedProperty($key); + $property->validate($userValue); + } + } + + /** + * Internal method which retrieves a nested property for object parameters. + * + * @param $key The name of the child parameter + * + * @returns Parameter + * @throws \Exception + */ + private function getNestedProperty($key) + { + if (stripos($this->name, 'metadata') !== false && $this->properties instanceof Parameter) { + return $this->properties; + } elseif (isset($this->properties[$key])) { + return $this->properties[$key]; + } else { + throw new \Exception(sprintf('The key provided "%s" is not defined', $key)); + } + } + + /** + * Internal method which indicates whether the user value is of the same type as the one expected + * by this parameter. + * + * @param $userValue The value being checked + * + * @return bool + */ + private function hasCorrectType($userValue) + { + // Helper fn to see whether an array is associative (i.e. a JSON object) + $isAssociative = function ($value) { + return is_array($value) && array_keys($value) !== range(0, count($value) - 1); + }; + + // For params defined as objects, we'll let the user get away with + // passing in an associative array - since it's effectively a hash + if ($this->type == 'object' && $isAssociative($userValue)) { + return true; + } + + if (class_exists($this->type) || interface_exists($this->type)) { + return is_a($userValue, $this->type); + } + + if (!$this->type) { + return true; + } + + return gettype($userValue) == $this->type; + } + + /** + * Indicates whether this parameter represents an array type + * + * @return bool + */ + public function isArray() + { + return $this->type == 'array' && $this->itemSchema instanceof Parameter; + } + + /** + * Indicates whether this parameter represents an object type + * + * @return bool + */ + public function isObject() + { + return $this->type == 'object' && !empty($this->properties); + } + + public function getLocation() + { + return $this->location; + } + + /** + * Verifies whether the given location matches the parameter's location. + * + * @param $value + * + * @return bool + */ + public function hasLocation($value) + { + return $this->location == $value; + } + + /** + * Retrieves the parameter's path. + * + * @return string|null + */ + public function getPath() + { + return $this->path; + } + + /** + * Retrieves the common schema that an array parameter applies to all its child elements. + * + * @return Parameter + */ + public function getItemSchema() + { + return $this->itemSchema; + } + + /** + * Sets the name of the parameter to a new value + * + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Retrieves the child parameter for an object parameter. + * + * @param string $name The name of the child property + * + * @return null|Parameter + */ + public function getProperty($name) + { + if ($this->properties instanceof Parameter) { + $this->properties->setName($name); + return $this->properties; + } + + return isset($this->properties[$name]) ? $this->properties[$name] : null; + } + + /** + * Retrieves the prefix for a parameter, if any. + * + * @return string|null + */ + public function getPrefix() + { + return $this->prefix; + } + + public function getPrefixedName() + { + return $this->prefix . $this->getName(); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php b/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php new file mode 100644 index 0000000..72bcfce --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php @@ -0,0 +1,67 @@ +<?php + +namespace OpenCloud\Common; + +/** + * Encapsulates common logic for classes which implement the SPL \ArrayAccess interface. + * + * @package OpenCloud\Common + */ +trait ArrayAccessTrait +{ + /** + * The internal state that this object represents + * + * @var array + */ + private $internalState = []; + + /** + * Sets an internal key with a value. + * + * @param string $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + if (null === $offset) { + $this->internalState[] = $value; + } else { + $this->internalState[$offset] = $value; + } + } + + /** + * Checks whether an internal key exists. + * + * @param string $offset + * + * @return bool + */ + public function offsetExists($offset) + { + return isset($this->internalState[$offset]); + } + + /** + * Unsets an internal key. + * + * @param string $offset + */ + public function offsetUnset($offset) + { + unset($this->internalState[$offset]); + } + + /** + * Retrieves an internal key. + * + * @param string $offset + * + * @return mixed|null + */ + public function offsetGet($offset) + { + return $this->offsetExists($offset) ? $this->internalState[$offset] : null; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php b/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php new file mode 100644 index 0000000..1a36cc0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php @@ -0,0 +1,73 @@ +<?php + +namespace OpenCloud\Common\Auth; + +use function GuzzleHttp\Psr7\modify_request; +use Psr\Http\Message\RequestInterface; + +/** + * This class is responsible for three tasks: + * + * 1. performing the initial authentication for OpenCloud services + * 2. populating the ``X-Auth-Token`` header for every HTTP request + * 3. checking the token expiry before each request, and re-authenticating if necessary + */ +class AuthHandler +{ + private $nextHandler; + + private $tokenGenerator; + + private $token; + + /** + * @param callable $nextHandler + * @param callable $tokenGenerator + */ + public function __construct(callable $nextHandler, callable $tokenGenerator, Token $token = null) + { + $this->nextHandler = $nextHandler; + $this->tokenGenerator = $tokenGenerator; + $this->token = $token; + } + + /** + * This method is invoked before every HTTP request is sent to the API. When this happens, it + * checks to see whether a token is set and valid, and then sets the ``X-Auth-Token`` header + * for the HTTP request before letting it continue on its merry way. + * + * @param RequestInterface $request + * @param array $options + * + * @return mixed|void + */ + public function __invoke(RequestInterface $request, array $options) + { + $fn = $this->nextHandler; + + if ($this->shouldIgnore($request)) { + return $fn($request, $options); + } + + if (!$this->token || $this->token->hasExpired()) { + $this->token = call_user_func($this->tokenGenerator); + } + + $modify = ['set_headers' => ['X-Auth-Token' => $this->token->getId()]]; + + return $fn(modify_request($request, $modify), $options); + } + + /** + * Internal method which prevents infinite recursion. For certain requests, like the initial + * auth call itself, we do NOT want to send a token. + * + * @param RequestInterface $request + * + * @return bool + */ + private function shouldIgnore(RequestInterface $request) + { + return strpos((string) $request->getUri(), 'tokens') !== false && $request->getMethod() == 'POST'; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php b/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php new file mode 100644 index 0000000..b4ab381 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php @@ -0,0 +1,20 @@ +<?php + +namespace OpenCloud\Common\Auth; + +interface Catalog +{ + /** + * Attempts to retrieve the base URL for a service from the catalog according to the arguments provided. + * + * @param string $name The name of the service as it appears in the catalog + * @param string $type The type of the service as it appears in the catalog + * @param string $region The region of the service as it appears in the catalog + * @param string $urlType The URL type of the service as it appears in the catalog + * + * @throws \RuntimeException If no endpoint is matched + * + * @returns string + */ + public function getServiceUrl($name, $type, $region, $urlType); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/IdentityService.php b/server/vendor/php-opencloud/common/src/Common/Auth/IdentityService.php new file mode 100644 index 0000000..695e7b1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/IdentityService.php @@ -0,0 +1,13 @@ +<?php + +namespace OpenCloud\Common\Auth; + +interface IdentityService +{ + /** + * Authenticates and retrieves back a token and catalog. + * + * @return array The FIRST key is {@see Token} instance, the SECOND key is a {@see Catalog} instance + */ + public function authenticate(array $options); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Auth/Token.php b/server/vendor/php-opencloud/common/src/Common/Auth/Token.php new file mode 100644 index 0000000..200fcee --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Auth/Token.php @@ -0,0 +1,15 @@ +<?php + +namespace OpenCloud\Common\Auth; + +interface Token +{ + public function getId(); + + /** + * Indicates whether the token has expired or not. + * + * @return bool TRUE if the token has expired, FALSE if it is still valid + */ + public function hasExpired(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/BadResponseError.php b/server/vendor/php-opencloud/common/src/Common/Error/BadResponseError.php new file mode 100644 index 0000000..f7640ea --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/BadResponseError.php @@ -0,0 +1,40 @@ +<?php + +namespace OpenCloud\Common\Error; + +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a HTTP-specific error, caused by 4xx or 5xx response statuses. + * + * @package OpenCloud\Common\Error + */ +class BadResponseError extends BaseError +{ + /** @var RequestInterface */ + private $request; + + /** @var ResponseInterface */ + private $response; + + public function setRequest(RequestInterface $request) + { + $this->request = $request; + } + + public function setResponse(ResponseInterface $response) + { + $this->response = $response; + } + + public function getRequest() + { + return $this->request; + } + + public function getResponse() + { + return $this->response; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php b/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php new file mode 100644 index 0000000..a7cb26e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/BaseError.php @@ -0,0 +1,12 @@ +<?php + +namespace OpenCloud\Common\Error; + +/** + * Base error class. + * + * @package OpenCloud\Common\Error + */ +class BaseError extends \Exception +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/Builder.php b/server/vendor/php-opencloud/common/src/Common/Error/Builder.php new file mode 100644 index 0000000..e3ccdfe --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/Builder.php @@ -0,0 +1,179 @@ +<?php + +namespace OpenCloud\Common\Error; + +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Exception\ClientException; +use Psr\Http\Message\MessageInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Class responsible for building meaningful exceptions. For HTTP problems, it produces a {@see HttpError} + * exception, and supplies a error message with reasonable defaults. For user input problems, it produces a + * {@see UserInputError} exception. For both, the problem is described, a potential solution is offered and + * a link to further information is included. + * + * @package OpenCloud\Common\Error + */ +class Builder +{ + /** + * The default domain to use for further link documentation. + * + * @var string + */ + private $docDomain = 'http://docs.php-opencloud.com/en/latest/'; + + /** + * The HTTP client required to validate the further links. + * + * @var ClientInterface + */ + private $client; + + /** + * @param ClientInterface $client + */ + public function __construct(ClientInterface $client = null) + { + $this->client = $client ?: new Client(); + } + + /** + * Internal method used when outputting headers in the error description. + * + * @param $name + * + * @return string + */ + private function header($name) + { + return sprintf("%s\n%s\n", $name, str_repeat('~', strlen($name))); + } + + /** + * Before outputting custom links, it is validated to ensure that the user is not + * directed off to a broken link. If a 404 is detected, it is hidden. + * + * @param $link The proposed link + * + * @return bool + */ + private function linkIsValid($link) + { + $link = $this->docDomain . $link; + + try { + return $this->client->request('HEAD', $link)->getStatusCode() < 400; + } catch (ClientException $e) { + return false; + } + } + + /** + * @param MessageInterface $message + * + * @codeCoverageIgnore + * @return string + */ + public function str(MessageInterface $message) + { + if ($message instanceof RequestInterface) { + $msg = trim($message->getMethod() . ' ' + . $message->getRequestTarget()) + . ' HTTP/' . $message->getProtocolVersion(); + if (!$message->hasHeader('host')) { + $msg .= "\r\nHost: " . $message->getUri()->getHost(); + } + } elseif ($message instanceof ResponseInterface) { + $msg = 'HTTP/' . $message->getProtocolVersion() . ' ' + . $message->getStatusCode() . ' ' + . $message->getReasonPhrase(); + } + + foreach ($message->getHeaders() as $name => $values) { + $msg .= "\r\n{$name}: " . implode(', ', $values); + } + + if ($message->getBody()->getSize() < ini_get('memory_limit')) { + $msg .= "\r\n\r\n" . $message->getBody(); + } + + return $msg; + } + + /** + * Helper method responsible for constructing and returning {@see BadResponseError} exceptions. + * + * @param RequestInterface $request The faulty request + * @param ResponseInterface $response The error-filled response + * + * @return BadResponseError + */ + public function httpError(RequestInterface $request, ResponseInterface $response) + { + $message = $this->header('HTTP Error'); + + $message .= sprintf("The remote server returned a \"%d %s\" error for the following transaction:\n\n", + $response->getStatusCode(), $response->getReasonPhrase()); + + $message .= $this->header('Request'); + $message .= trim($this->str($request)) . PHP_EOL . PHP_EOL; + + $message .= $this->header('Response'); + $message .= trim($this->str($response)) . PHP_EOL . PHP_EOL; + + $message .= $this->header('Further information'); + $message .= $this->getStatusCodeMessage($response->getStatusCode()); + + $message .= "Visit http://docs.php-opencloud.com/en/latest/http-codes for more information about debugging " + . "HTTP status codes, or file a support issue on https://github.com/php-opencloud/openstack/issues."; + + $e = new BadResponseError($message); + $e->setRequest($request); + $e->setResponse($response); + + return $e; + } + + private function getStatusCodeMessage($statusCode) + { + $errors = [ + 400 => 'Please ensure that your input values are valid and well-formed. ', + 401 => 'Please ensure that your authentication credentials are valid. ', + 404 => "Please ensure that the resource you're trying to access actually exists. ", + 500 => 'Please try this operation again once you know the remote server is operational. ', + ]; + + return isset($errors[$statusCode]) ? $errors[$statusCode] : ''; + } + + /** + * Helper method responsible for constructing and returning {@see UserInputError} exceptions. + * + * @param string $expectedType The type that was expected from the user + * @param mixed $userValue The incorrect value the user actually provided + * @param string|null $furtherLink A link to further information if necessary (optional). + * + * @return UserInputError + */ + public function userInputError($expectedType, $userValue, $furtherLink = null) + { + $message = $this->header('User Input Error'); + + $message .= sprintf("%s was expected, but the following value was passed in:\n\n%s\n", + $expectedType, print_r($userValue, true)); + + $message .= "Please ensure that the value adheres to the expectation above. "; + + if ($furtherLink && $this->linkIsValid($furtherLink)) { + $message .= sprintf("Visit %s for more information about input arguments. ", $this->docDomain . $furtherLink); + } + + $message .= 'If you run into trouble, please open a support issue on https://github.com/php-opencloud/openstack/issues.'; + + return new UserInputError($message); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php b/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php new file mode 100644 index 0000000..3e01d74 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php @@ -0,0 +1,12 @@ +<?php + +namespace OpenCloud\Common\Error; + +/** + * Error to indicate functionality which has not been implemented yet. + * + * @package OpenCloud\Common\Error + */ +class NotImplementedError extends BaseError +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Error/UserInputError.php b/server/vendor/php-opencloud/common/src/Common/Error/UserInputError.php new file mode 100644 index 0000000..964875e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Error/UserInputError.php @@ -0,0 +1,12 @@ +<?php + +namespace OpenCloud\Common\Error; + +/** + * Represents a user input error, caused by an incorrect type or malformed value. + * + * @package OpenCloud\Common\Error + */ +class UserInputError extends BaseError +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/HydratorStrategyTrait.php b/server/vendor/php-opencloud/common/src/Common/HydratorStrategyTrait.php new file mode 100644 index 0000000..4995747 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/HydratorStrategyTrait.php @@ -0,0 +1,35 @@ +<?php + +namespace OpenCloud\Common; + +/** + * Represents common functionality for populating, or "hydrating", an object with arbitrary data. + * + * @package OpenCloud\Common + */ +trait HydratorStrategyTrait +{ + /** + * Hydrates an object with set data + * + * @param array $data The data to set + * @param array $aliases Any aliases + */ + private function hydrate(array $data, array $aliases = []) + { + foreach ($data as $key => $val) { + $key = isset($aliases[$key]) ? $aliases[$key] : $key; + if (property_exists($this, $key)) { + $this->$key = $val; + } + } + } + + private function set($key, $property, array $data, callable $fn = null) + { + if (isset($data[$key]) && property_exists($this, $property)) { + $value = $fn ? call_user_func($fn, $data[$key]) : $data[$key]; + $this->$property = $value; + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonPath.php b/server/vendor/php-opencloud/common/src/Common/JsonPath.php new file mode 100644 index 0000000..0a6372e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonPath.php @@ -0,0 +1,119 @@ +<?php + +namespace OpenCloud\Common; + +/** + * This class allows arbitrary data structures to be inserted into, and extracted from, deep arrays + * and JSON-serialized strings. Say, for example, that you have this array as an input: + * + * <pre><code>['foo' => ['bar' => ['baz' => 'some_value']]]</code></pre> + * + * and you wanted to insert or extract an element. Usually, you would use: + * + * <pre><code>$array['foo']['bar']['baz'] = 'new_value';</code></pre> + * + * but sometimes you do not have access to the variable - so a string representation is needed. Using + * XPath-like syntax, this class allows you to do this: + * + * <pre><code>$jsonPath = new JsonPath($array); + * $jsonPath->set('foo.bar.baz', 'new_value'); + * $val = $jsonPath->get('foo.bar.baz'); + * </code></pre> + * + * @package OpenCloud\Common + */ +class JsonPath +{ + /** @var array */ + private $jsonStructure; + + /** + * @param $structure The initial data structure to extract from and insert into. Typically this will be a + * multidimensional associative array; but well-formed JSON strings are also acceptable. + */ + public function __construct($structure) + { + $this->jsonStructure = is_string($structure) ? json_decode($structure, true) : $structure; + } + + /** + * Set a node in the structure + * + * @param $path The XPath to use + * @param $value The new value of the node + */ + public function set($path, $value) + { + $this->jsonStructure = $this->setPath($path, $value, $this->jsonStructure); + } + + /** + * Internal method for recursive calls. + * + * @param $path + * @param $value + * @param $json + * @return mixed + */ + private function setPath($path, $value, $json) + { + $nodes = explode('.', $path); + $point = array_shift($nodes); + + if (!isset($json[$point])) { + $json[$point] = []; + } + + if (!empty($nodes)) { + $json[$point] = $this->setPath(implode('.', $nodes), $value, $json[$point]); + } else { + $json[$point] = $value; + } + + return $json; + } + + /** + * Return the updated structure. + * + * @return mixed + */ + public function getStructure() + { + return $this->jsonStructure; + } + + /** + * Get a path's value. If no path can be matched, NULL is returned. + * + * @param $path + * @return mixed|null + */ + public function get($path) + { + return $this->getPath($path, $this->jsonStructure); + } + + /** + * Internal method for recursion. + * + * @param $path + * @param $json + * @return null + */ + private function getPath($path, $json) + { + $nodes = explode('.', $path); + $point = array_shift($nodes); + + if (!isset($json[$point])) { + return null; + } + + if (empty($nodes)) { + return $json[$point]; + } else { + return $this->getPath(implode('.', $nodes), $json[$point]); + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php b/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php new file mode 100644 index 0000000..2c12ec0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php @@ -0,0 +1,115 @@ +<?php + +namespace OpenCloud\Common\JsonSchema; + +class JsonPatch +{ + const OP_ADD = 'add'; + const OP_REPLACE = 'replace'; + const OP_REMOVE = 'remove'; + + public static function diff($src, $dest) + { + return (new static)->makeDiff($src, $dest); + } + + public function makeDiff($srcStruct, $desStruct, $path = '') + { + $changes = []; + + if (is_object($srcStruct)) { + $changes = $this->handleObject($srcStruct, $desStruct, $path); + } elseif (is_array($srcStruct)) { + $changes = $this->handleArray($srcStruct, $desStruct, $path); + } elseif ($srcStruct != $desStruct) { + $changes[] = $this->makePatch(self::OP_REPLACE, $path, $desStruct); + } + + return $changes; + } + + protected function handleArray($srcStruct, $desStruct, $path) + { + $changes = []; + + if ($diff = $this->arrayDiff($desStruct, $srcStruct)) { + foreach ($diff as $key => $val) { + if (is_object($val)) { + $changes = array_merge($changes, $this->makeDiff($srcStruct[$key], $val, $this->path($path, $key))); + } else { + $op = array_key_exists($key, $srcStruct) && !in_array($srcStruct[$key], $desStruct, true) + ? self::OP_REPLACE : self::OP_ADD; + $changes[] = $this->makePatch($op, $this->path($path, $key), $val); + } + } + } elseif ($srcStruct != $desStruct) { + foreach ($srcStruct as $key => $val) { + if (!in_array($val, $desStruct, true)) { + $changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key)); + } + } + } + + return $changes; + } + + protected function handleObject($srcStruct, $desStruct, $path) + { + $changes = []; + + if ($this->shouldPartiallyReplace($srcStruct, $desStruct)) { + foreach ($desStruct as $key => $val) { + if (!property_exists($srcStruct, $key)) { + $changes[] = $this->makePatch(self::OP_ADD, $this->path($path, $key), $val); + } elseif ($srcStruct->$key != $val) { + $changes = array_merge($changes, $this->makeDiff($srcStruct->$key, $val, $this->path($path, $key))); + } + } + } elseif ($this->shouldPartiallyReplace($desStruct, $srcStruct)) { + foreach ($srcStruct as $key => $val) { + if (!property_exists($desStruct, $key)) { + $changes[] = $this->makePatch(self::OP_REMOVE, $this->path($path, $key)); + } + } + } + + return $changes; + } + + protected function shouldPartiallyReplace($o1, $o2) + { + return count(array_diff_key((array) $o1, (array) $o2)) < count($o1); + } + + protected function arrayDiff(array $a1, array $a2) + { + $result = []; + + foreach ($a1 as $key => $val) { + if (!in_array($val, $a2, true)) { + $result[$key] = $val; + } + } + + return $result; + } + + protected function path($root, $path) + { + if ($path === '_empty_') { + $path = ''; + } + + return rtrim($root, '/') . '/' . ltrim($path, '/'); + } + + protected function makePatch($op, $path, $val = null) + { + switch ($op) { + default: + return ['op' => $op, 'path' => $path, 'value' => $val]; + case self::OP_REMOVE: + return ['op' => $op, 'path' => $path]; + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php b/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php new file mode 100644 index 0000000..a1cd380 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php @@ -0,0 +1,72 @@ +<?php + +namespace OpenCloud\Common\JsonSchema; + +use JsonSchema\Validator; + +class Schema +{ + private $body; + private $validator; + + public function __construct($body, Validator $validator = null) + { + $this->body = (object) $body; + $this->validator = $validator ?: new Validator(); + } + + public function getPropertyPaths() + { + $paths = []; + + foreach ($this->body->properties as $propertyName => $property) { + $paths[] = sprintf("/%s", $propertyName); + } + + return $paths; + } + + public function normalizeObject($subject, array $aliases) + { + $out = new \stdClass; + + foreach ($this->body->properties as $propertyName => $property) { + $name = isset($aliases[$propertyName]) ? $aliases[$propertyName] : $propertyName; + if (isset($property->readOnly) && $property->readOnly === true) { + continue; + } elseif (property_exists($subject, $name)) { + $out->$propertyName = $subject->$name; + } elseif (property_exists($subject, $propertyName)) { + $out->$propertyName = $subject->$propertyName; + } + } + + return $out; + } + + public function validate($data) + { + $this->validator->check($data, $this->body); + } + + public function isValid() + { + return $this->validator->isValid(); + } + + public function getErrors() + { + return $this->validator->getErrors(); + } + + public function getErrorString() + { + $msg = "Provided values do not validate. Errors:\n"; + + foreach ($this->getErrors() as $error) { + $msg .= sprintf("[%s] %s\n", $error['property'], $error['message']); + } + + return $msg; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php new file mode 100644 index 0000000..9f79b07 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php @@ -0,0 +1,243 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Api\Operator; +use OpenCloud\Common\Transport\Utils; +use Psr\Http\Message\ResponseInterface; + +/** + * Represents a top-level abstraction of a remote API resource. Usually a resource represents a discrete + * entity such as a Server, Container, Load Balancer. Apart from a representation of state, a resource can + * also execute RESTFul operations on itself (updating, deleting, listing) or on other models. + * + * @package OpenCloud\Common\Resource + */ +abstract class AbstractResource extends Operator implements ResourceInterface +{ + const DEFAULT_MARKER_KEY = 'id'; + + /** + * The JSON key that indicates how the API nests singular resources. For example, when + * performing a GET, it could respond with ``{"server": {"id": "12345"}}``. In this case, + * "server" is the resource key, since the essential state of the server is nested inside. + * + * @var string + */ + protected $resourceKey; + + /** + * The key that indicates how the API nests resource collections. For example, when + * performing a GET, it could respond with ``{"servers": [{}, {}]}``. In this case, "servers" + * is the resources key, since the array of servers is nested inside. + * + * @var string + */ + protected $resourcesKey; + + /** + * Indicates which attribute of the current resource should be used for pagination markers. + * + * @var string + */ + protected $markerKey; + + /** + * An array of aliases that will be checked when the resource is being populated. For example, + * + * 'FOO_BAR' => 'fooBar' + * + * will extract FOO_BAR from the response, and save it as 'fooBar' in the resource. + * + * @var array + */ + protected $aliases = []; + + /** + * Populates the current resource from a response object. + * + * @param ResponseInterface $response + * + * @return $this|ResourceInterface + */ + public function populateFromResponse(ResponseInterface $response) + { + if (strpos($response->getHeaderLine('Content-Type'), 'application/json') === 0) { + $json = Utils::jsonDecode($response); + if (!empty($json)) { + $this->populateFromArray(Utils::flattenJson($json, $this->resourceKey)); + } + } + + return $this; + } + + /** + * Populates the current resource from a data array. + * + * @param array $array + * + * @return mixed|void + */ + public function populateFromArray(array $array) + { + $reflClass = new \ReflectionClass($this); + + foreach ($array as $key => $val) { + $propertyName = isset($this->aliases[$key]) ? $this->aliases[$key] : $key; + + if (property_exists($this, $propertyName)) { + if ($type = $this->extractTypeFromDocBlock($reflClass, $propertyName)) { + $val = $this->parseDocBlockValue($type, $val); + } + + $this->$propertyName = $val; + } + } + } + + private function parseDocBlockValue($type, $val) + { + if (strpos($type, '[]') === 0 && is_array($val)) { + $array = []; + foreach ($val as $subVal) { + $array[] = $this->model($this->normalizeModelClass(substr($type, 2)), $subVal); + } + $val = $array; + } elseif (strcasecmp($type, '\datetimeimmutable') === 0) { + $val = new \DateTimeImmutable($val); + } elseif ($this->isNotNativeType($type)) { + $val = $this->model($this->normalizeModelClass($type), $val); + } + + return $val; + } + + private function isNotNativeType($type) + { + return !in_array($type, [ + 'string', 'bool', 'boolean', 'double', 'null', 'array', 'object', 'int', 'integer', 'float', 'numeric', + 'mixed' + ]); + } + + private function normalizeModelClass($class) + { + if (strpos($class, '\\') === false) { + $currentNamespace = (new \ReflectionClass($this))->getNamespaceName(); + $class = sprintf("%s\\%s", $currentNamespace, $class); + } + + return $class; + } + + private function extractTypeFromDocBlock(\ReflectionClass $reflClass, $propertyName) + { + $docComment = $reflClass->getProperty($propertyName)->getDocComment(); + + if (!$docComment) { + return false; + } + + $matches = []; + preg_match('#@var ((\[\])?[\w|\\\]+)#', $docComment, $matches); + return isset($matches[1]) ? $matches[1] : null; + } + + /** + * Internal method which retrieves the values of provided keys. + * + * @param array $keys + * + * @return array + */ + protected function getAttrs(array $keys) + { + $output = []; + + foreach ($keys as $key) { + if (property_exists($this, $key) && $this->$key !== null) { + $output[$key] = $this->$key; + } + } + + return $output; + } + + /** + * @param array $definition + * + * @return mixed + */ + public function executeWithState(array $definition) + { + return $this->execute($definition, $this->getAttrs(array_keys($definition['params']))); + } + + private function getResourcesKey() + { + $resourcesKey = $this->resourcesKey; + + if (!$resourcesKey) { + $class = substr(static::class, strrpos(static::class, '\\') + 1); + $resourcesKey = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $class)) . 's'; + } + + return $resourcesKey; + } + + /** + * {@inheritDoc} + */ + public function enumerate(array $def, array $userVals = [], callable $mapFn = null) + { + $operation = $this->getOperation($def); + + $requestFn = function ($marker) use ($operation, $userVals) { + if ($marker) { + $userVals['marker'] = $marker; + } + return $this->sendRequest($operation, $userVals); + }; + + $resourceFn = function (array $data) { + $resource = $this->newInstance(); + $resource->populateFromArray($data); + return $resource; + }; + + $opts = [ + 'limit' => isset($userVals['limit']) ? $userVals['limit'] : null, + 'resourcesKey' => $this->getResourcesKey(), + 'markerKey' => $this->markerKey, + 'mapFn' => $mapFn, + ]; + + $iterator = new Iterator($opts, $requestFn, $resourceFn); + return $iterator(); + } + + public function extractMultipleInstances(ResponseInterface $response, $key = null) + { + $key = $key ?: $this->getResourcesKey(); + $resourcesData = Utils::jsonDecode($response)[$key]; + + $resources = []; + + foreach ($resourcesData as $resourceData) { + $resource = $this->newInstance(); + $resource->populateFromArray($resourceData); + $resources[] = $resource; + } + + return $resources; + } + + protected function getService() + { + $class = static::class; + $service = substr($class, 0, strpos($class, 'Models') - 1) . '\\Service'; + + return new $service($this->client, $this->api); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php new file mode 100644 index 0000000..19579c1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php @@ -0,0 +1,19 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be created. + * + * @package OpenCloud\Common\Resource + */ +interface Creatable +{ + /** + * Create a new resource according to the configuration set in the options. + * + * @param array $userOptions + * @return self + */ + public function create(array $userOptions); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php new file mode 100644 index 0000000..eeb0602 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be deleted. + * + * @package OpenCloud\Common\Resource + */ +interface Deletable +{ + /** + * Permanently delete this resource. + * + * @return void + */ + public function delete(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php b/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php new file mode 100644 index 0000000..53b51fc --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php @@ -0,0 +1,67 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use Psr\Http\Message\ResponseInterface; + +interface HasMetadata +{ + /** + * Retrieves the metadata for the resource in the form of an associative array or hash. Each key represents the + * metadata item's name, and each value represents the metadata item's remote value. + * + * @return array + */ + public function getMetadata(); + + /** + * Merges a set of new values with those which already exist (on the remote API) for a resource. For example, if + * the resource has this metadata already set: + * + * Foo: val1 + * Bar: val2 + * + * and mergeMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Bar: val2 + * Baz: val4 + * + * You will notice that any metadata items which are not specified in the call are preserved. + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function mergeMetadata(array $metadata); + + /** + * Replaces all of the existing metadata items for a resource with a new set of values. Any metadata items which + * are not provided in the call are removed from the resource. For example, if the resource has this metadata + * already set: + * + * Foo: val1 + * Bar: val2 + * + * and resetMetadata(['Foo' => 'val3', 'Baz' => 'val4']); is called, then the resource will have the following + * metadata: + * + * Foo: val3 + * Baz: val4 + * + * @param array $metadata The new metadata items + * + * @return mixed + */ + public function resetMetadata(array $metadata); + + /** + * Extracts metadata from a response object and returns it in the form of an associative array. + * + * @param ResponseInterface $response + * + * @return array + */ + public function parseMetadata(ResponseInterface $response); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php new file mode 100644 index 0000000..519be7e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php @@ -0,0 +1,124 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Error\BadResponseError; + +/** + * Contains reusable functionality for resources that have long operations which require waiting in + * order to reach a particular state. + * + * @codeCoverageIgnore + * + * @package OpenCloud\Common\Resource + */ +trait HasWaiterTrait +{ + /** + * Provides a blocking operation until the resource has reached a particular state. The method + * will enter a loop, requesting feedback from the remote API until it sends back an appropriate + * status. + * + * @param string $status The state to be reached + * @param int $timeout The maximum timeout. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. + * @param int $sleepPeriod The amount of time to pause between each HTTP request. + */ + public function waitUntil($status, $timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + $this->retrieve(); + + if ($this->status == $status || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Provides a blocking operation until the resource has reached a particular state. The method + * will enter a loop, executing the callback until TRUE is returned. This provides great + * flexibility. + * + * @param callable $fn An anonymous function that will be executed on every iteration. You can + * encapsulate your own logic to determine whether the resource has + * successfully transitioned. When TRUE is returned by the callback, + * the loop will end. + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + * @param int $sleepPeriod The amount of time to pause between each HTTP request. + */ + public function waitWithCallback(callable $fn, $timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + $this->retrieve(); + + $response = call_user_func_array($fn, [$this]); + + if ($response === true || $this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } + + /** + * Internal method used to identify whether a timeout has been exceeded. + * + * @param bool|int $timeout + * @param int $startTime + * + * @return bool + */ + private function shouldHalt($timeout, $startTime) + { + if ($timeout === false) { + return false; + } + + return time() - $startTime >= $timeout; + } + + /** + * Convenience method providing a blocking operation until the resource transitions to an + * ``ACTIVE`` status. + * + * @param int|bool $timeout The maximum timeout in seconds. If the total time taken by the waiter has reached + * or exceed this timeout, the blocking operation will immediately cease. If FALSE + * is provided, the timeout will never be considered. + */ + public function waitUntilActive($timeout = false) + { + $this->waitUntil('ACTIVE', $timeout); + } + + public function waitUntilDeleted($timeout = 60, $sleepPeriod = 1) + { + $startTime = time(); + + while (true) { + try { + $this->retrieve(); + } catch (BadResponseError $e) { + if ($e->getResponse()->getStatusCode() === 404) { + break; + } + throw $e; + } + + if ($this->shouldHalt($timeout, $startTime)) { + break; + } + + sleep($sleepPeriod); + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php new file mode 100644 index 0000000..63d4455 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php @@ -0,0 +1,97 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use OpenCloud\Common\Transport\Utils; + +class Iterator +{ + private $requestFn; + private $resourceFn; + private $limit; + private $count; + private $resourcesKey; + private $markerKey; + private $mapFn; + private $currentMarker; + + public function __construct(array $options, callable $requestFn, callable $resourceFn) + { + $this->limit = isset($options['limit']) ? $options['limit'] : false; + $this->count = 0; + + if (isset($options['resourcesKey'])) { + $this->resourcesKey = $options['resourcesKey']; + } + + if (isset($options['markerKey'])) { + $this->markerKey = $options['markerKey']; + } + + if (isset($options['mapFn']) && is_callable($options['mapFn'])) { + $this->mapFn = $options['mapFn']; + } + + $this->requestFn = $requestFn; + $this->resourceFn = $resourceFn; + } + + private function fetchResources() + { + if ($this->shouldNotSendAnotherRequest()) { + return false; + } + + $response = call_user_func($this->requestFn, $this->currentMarker); + + $json = Utils::flattenJson(Utils::jsonDecode($response), $this->resourcesKey); + + if ($response->getStatusCode() === 204 || empty($json)) { + return false; + } + + return $json; + } + + private function assembleResource(array $data) + { + $resource = call_user_func($this->resourceFn, $data); + + // Invoke user-provided fn if provided + if ($this->mapFn) { + call_user_func_array($this->mapFn, [&$resource]); + } + + // Update marker if operation supports it + if ($this->markerKey) { + $this->currentMarker = $resource->{$this->markerKey}; + } + + return $resource; + } + + private function totalReached() + { + return $this->limit && $this->count >= $this->limit; + } + + private function shouldNotSendAnotherRequest() + { + return $this->totalReached() || ($this->count > 0 && !$this->markerKey); + } + + public function __invoke() + { + while ($resources = $this->fetchResources()) { + foreach ($resources as $resourceData) { + if ($this->totalReached()) { + break; + } + + $this->count++; + + yield $this->assembleResource($resourceData); + } + } + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php new file mode 100644 index 0000000..8e255c0 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Listable.php @@ -0,0 +1,28 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be enumerated (listed over). + * + * @package OpenCloud\Common\Resource + */ +interface Listable +{ + /** + * This method iterates over a collection of resources. It sends the operation's request to the API, + * parses the response, converts each element into {@see self} and - if pagination is supported - continues + * to send requests until an empty collection is received back. + * + * For paginated collections, it sends subsequent requests according to a marker URL query. The value + * of the marker will depend on the last element returned in the previous response. If a limit is + * provided, the loop will continue up until that point. + * + * @param array $def The operation definition + * @param array $userVals The user values + * @param callable $mapFn An optional callback that will be executed on every resource iteration. + * + * @returns void + */ + public function enumerate(array $def, array $userVals = [], callable $mapFn = null); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php b/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php new file mode 100644 index 0000000..8cf841b --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php @@ -0,0 +1,29 @@ +<?php + +namespace OpenCloud\Common\Resource; + +use Psr\Http\Message\ResponseInterface; + +/** + * Represents an API resource. + * + * @package OpenCloud\Common\Resource + */ +interface ResourceInterface +{ + /** + * All models which represent an API resource should be able to be populated + * from a {@see ResponseInterface} object. + * + * @param ResponseInterface $response + * + * @return self + */ + public function populateFromResponse(ResponseInterface $response); + + /** + * @param array $data + * @return mixed + */ + public function populateFromArray(array $data); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php new file mode 100644 index 0000000..333b2c2 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * A resource that supports a GET or HEAD operation to retrieve more details. + * + * @package OpenCloud\Common\Resource + */ +interface Retrievable +{ + /** + * Retrieve details of the current resource from the remote API. + * + * @return void + */ + public function retrieve(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php b/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php new file mode 100644 index 0000000..a9a3f0e --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php @@ -0,0 +1,18 @@ +<?php + +namespace OpenCloud\Common\Resource; + +/** + * Represents a resource that can be updated. + * + * @package OpenCloud\Common\Resource + */ +interface Updateable +{ + /** + * Update the current resource with the configuration set out in the user options. + * + * @return void + */ + public function update(); +} diff --git a/server/vendor/php-opencloud/common/src/Common/Service/AbstractService.php b/server/vendor/php-opencloud/common/src/Common/Service/AbstractService.php new file mode 100644 index 0000000..f24e684 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Service/AbstractService.php @@ -0,0 +1,14 @@ +<?php + +namespace OpenCloud\Common\Service; + +use OpenCloud\Common\Api\Operator; + +/** + * Represents the top-level abstraction of a service. + * + * @package OpenCloud\Common\Service + */ +abstract class AbstractService extends Operator implements ServiceInterface +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Service/Builder.php b/server/vendor/php-opencloud/common/src/Common/Service/Builder.php new file mode 100644 index 0000000..0035070 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Service/Builder.php @@ -0,0 +1,170 @@ +<?php + +namespace OpenCloud\Common\Service; + +use GuzzleHttp\Client; +use GuzzleHttp\ClientInterface; +use GuzzleHttp\Middleware as GuzzleMiddleware; +use OpenCloud\Common\Auth\Token; +use OpenCloud\Common\Transport\HandlerStack; +use OpenCloud\Common\Transport\Middleware; +use OpenCloud\Common\Transport\Utils; +use OpenCloud\Identity\v3\Service; + +/** + * A Builder for easily creating OpenCloud services. + * + * @package OpenCloud\Common\Service + */ +class Builder +{ + /** + * Global options that will be applied to every service created by this builder. + * + * @var array + */ + private $globalOptions = []; + + /** @var string */ + private $rootNamespace; + + /** + * Defaults that will be applied to options if no values are provided by the user. + * + * @var array + */ + private $defaults = ['urlType' => 'publicURL']; + + /** + * @param array $globalOptions Options that will be applied to every service created by this builder. + * Eventually they will be merged (and if necessary overridden) by the + * service-specific options passed in. + */ + public function __construct(array $globalOptions = [], $rootNamespace = 'OpenCloud') + { + $this->globalOptions = $globalOptions; + $this->rootNamespace = $rootNamespace; + } + + /** + * Internal method which resolves the API and Service classes for a service. + * + * @param string $serviceName The name of the service, e.g. Compute + * @param int $serviceVersion The major version of the service, e.g. 2 + * + * @return array + */ + private function getClasses($serviceName, $serviceVersion) + { + $rootNamespace = sprintf("%s\\%s\\v%d", $this->rootNamespace, $serviceName, $serviceVersion); + + return [ + sprintf("%s\\Api", $rootNamespace), + sprintf("%s\\Service", $rootNamespace), + ]; + } + + /** + * This method will return an OpenCloud service ready fully built and ready for use. There is + * some initial setup that may prohibit users from directly instantiating the service class + * directly - this setup includes the configuration of the HTTP client's base URL, and the + * attachment of an authentication handler. + * + * @param $serviceName The name of the service as it appears in the OpenCloud\* namespace + * @param $serviceVersion The major version of the service + * @param array $serviceOptions The service-specific options to use + * + * @return \OpenCloud\Common\Service\ServiceInterface + * + * @throws \Exception + */ + public function createService($serviceName, $serviceVersion, array $serviceOptions = []) + { + $options = $this->mergeOptions($serviceOptions); + + $this->stockIdentityService($options); + $this->stockAuthHandler($options); + $this->stockHttpClient($options, $serviceName); + + list($apiClass, $serviceClass) = $this->getClasses($serviceName, $serviceVersion); + + return new $serviceClass($options['httpClient'], new $apiClass()); + } + + private function stockHttpClient(array &$options, $serviceName) + { + if (!isset($options['httpClient']) || !($options['httpClient'] instanceof ClientInterface)) { + if (strcasecmp($serviceName, 'identity') === 0) { + $baseUrl = $options['authUrl']; + $stack = $this->getStack($options['authHandler']); + } else { + list($token, $baseUrl) = $options['identityService']->authenticate($options); + $stack = $this->getStack($options['authHandler'], $token); + } + + $this->addDebugMiddleware($options, $stack); + + $options['httpClient'] = $this->httpClient($baseUrl, $stack); + } + } + + /** + * @codeCoverageIgnore + */ + private function addDebugMiddleware(array $options, HandlerStack &$stack) + { + if (!empty($options['debugLog']) + && !empty($options['logger']) + && !empty($options['messageFormatter']) + ) { + $stack->push(GuzzleMiddleware::log($options['logger'], $options['messageFormatter'])); + } + } + + private function stockIdentityService(array &$options) + { + if (!isset($options['identityService'])) { + $httpClient = $this->httpClient($options['authUrl'], HandlerStack::create()); + $options['identityService'] = Service::factory($httpClient); + } + } + + /** + * @param array $options + * @codeCoverageIgnore + */ + private function stockAuthHandler(array &$options) + { + if (!isset($options['authHandler'])) { + $options['authHandler'] = function () use ($options) { + return $options['identityService']->generateToken($options); + }; + } + } + + private function getStack(callable $authHandler, Token $token = null) + { + $stack = HandlerStack::create(); + $stack->push(Middleware::authHandler($authHandler, $token)); + return $stack; + } + + private function httpClient($baseUrl, HandlerStack $stack) + { + return new Client([ + 'base_uri' => Utils::normalizeUrl($baseUrl), + 'handler' => $stack, + ]); + } + + private function mergeOptions(array $serviceOptions) + { + $options = array_merge($this->defaults, $this->globalOptions, $serviceOptions); + + if (!isset($options['authUrl'])) { + throw new \InvalidArgumentException('"authUrl" is a required option'); + } + + return $options; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php b/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php new file mode 100644 index 0000000..6ad3089 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php @@ -0,0 +1,14 @@ +<?php + +namespace OpenCloud\Common\Service; + +use OpenCloud\Common\Api\OperatorInterface; + +/** + * Service interface. + * + * @package OpenCloud\Common\Service + */ +interface ServiceInterface extends OperatorInterface +{ +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/HandlerStack.php b/server/vendor/php-opencloud/common/src/Common/Transport/HandlerStack.php new file mode 100644 index 0000000..7d83875 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/HandlerStack.php @@ -0,0 +1,19 @@ +<?php + +namespace OpenCloud\Common\Transport; + +use function GuzzleHttp\choose_handler; +use GuzzleHttp\HandlerStack as GuzzleStack; + +class HandlerStack extends GuzzleStack +{ + public static function create(callable $handler = null) + { + $stack = new self($handler ?: choose_handler()); + + $stack->push(Middleware::httpErrors()); + $stack->push(Middleware::prepareBody()); + + return $stack; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php b/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php new file mode 100644 index 0000000..11b31d3 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php @@ -0,0 +1,95 @@ +<?php + +namespace OpenCloud\Common\Transport; + +use OpenCloud\Common\Api\Parameter; +use OpenCloud\Common\JsonPath; + +/** + * Class responsible for populating the JSON body of a {@see GuzzleHttp\Message\Request} object. + * + * @package OpenCloud\Common\Transport + */ +class JsonSerializer +{ + /** + * Populates the actual value into a JSON field, i.e. it has reached the end of the line and no + * further nesting is required. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * @param array $json The existing JSON structure that will be populated + * + * @return array|mixed + */ + private function stockValue(Parameter $param, $userValue, $json) + { + $name = $param->getName(); + if ($path = $param->getPath()) { + $jsonPath = new JsonPath($json); + $jsonPath->set(sprintf("%s.%s", $path, $name), $userValue); + $json = $jsonPath->getStructure(); + } elseif ($name) { + $json[$name] = $userValue; + } else { + $json[] = $userValue; + } + + return $json; + } + + /** + * Populates a value into an array-like structure. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * + * @return array|mixed + */ + private function stockArrayJson(Parameter $param, $userValue) + { + $elems = []; + foreach ($userValue as $item) { + $elems = $this->stockJson($param->getItemSchema(), $item, $elems); + } + return $elems; + } + + /** + * Populates a value into an object-like structure. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * + * @return array + */ + private function stockObjectJson(Parameter $param, $userValue) + { + $object = []; + foreach ($userValue as $key => $val) { + $object = $this->stockJson($param->getProperty($key), $val, $object); + } + return $object; + } + + /** + * A generic method that will populate a JSON structure with a value according to a schema. It + * supports multiple types and will delegate accordingly. + * + * @param Parameter $param The schema that defines how the JSON field is being populated + * @param mixed $userValue The user value that is populating a JSON field + * @param array $json The existing JSON structure that will be populated + * + * @return array + */ + public function stockJson(Parameter $param, $userValue, $json) + { + if ($param->isArray()) { + $userValue = $this->stockArrayJson($param, $userValue); + } elseif ($param->isObject()) { + $userValue = $this->stockObjectJson($param, $userValue); + } + // Populate the final value + return $this->stockValue($param, $userValue, $json); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php b/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php new file mode 100644 index 0000000..916ff22 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php @@ -0,0 +1,94 @@ +<?php + +namespace OpenCloud\Common\Transport; + +use function GuzzleHttp\Psr7\modify_request; +use GuzzleHttp\MessageFormatter; +use GuzzleHttp\Middleware as GuzzleMiddleware; +use OpenCloud\Common\Auth\AuthHandler; +use OpenCloud\Common\Auth\Token; +use OpenCloud\Common\Error\Builder; +use Psr\Http\Message\ResponseInterface; + +final class Middleware +{ + /** + * @return callable + */ + public static function httpErrors() + { + return function (callable $handler) { + return function ($request, array $options) use ($handler) { + return $handler($request, $options)->then( + function (ResponseInterface $response) use ($request, $handler) { + if ($response->getStatusCode() < 400) { + return $response; + } + throw (new Builder())->httpError($request, $response); + } + ); + }; + }; + } + + /** + * @param callable $tokenGenerator + * @param Token $token + * + * @return callable + */ + public static function authHandler(callable $tokenGenerator, Token $token = null) + { + return function (callable $handler) use ($tokenGenerator, $token) { + return new AuthHandler($handler, $tokenGenerator, $token); + }; + } + + /** + * @codeCoverageIgnore + */ + public static function history(array &$container) + { + return GuzzleMiddleware::history($container); + } + + /** + * @codeCoverageIgnore + */ + public static function retry(callable $decider, callable $delay = null) + { + return GuzzleMiddleware::retry($decider, $delay); + } + + /** + * @codeCoverageIgnore + */ + public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO) + { + return GuzzleMiddleware::log($logger, $formatter, $logLevel); + } + + /** + * @codeCoverageIgnore + */ + public static function prepareBody() + { + return GuzzleMiddleware::prepareBody(); + } + + /** + * @codeCoverageIgnore + */ + public static function mapRequest(callable $fn) + { + return GuzzleMiddleware::mapRequest($fn); + } + + /** + * @codeCoverageIgnore + */ + public static function mapResponse(callable $fn) + { + return GuzzleMiddleware::mapResponse($fn); + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php b/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php new file mode 100644 index 0000000..30f04af --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php @@ -0,0 +1,85 @@ +<?php + +namespace OpenCloud\Common\Transport; + +use function GuzzleHttp\uri_template; +use function GuzzleHttp\Psr7\build_query; +use function GuzzleHttp\Psr7\modify_request; +use OpenCloud\Common\Api\Operation; +use OpenCloud\Common\Api\Parameter; + +class RequestSerializer +{ + private $jsonSerializer; + + public function __construct(JsonSerializer $jsonSerializer = null) + { + $this->jsonSerializer = $jsonSerializer ?: new JsonSerializer(); + } + + public function serializeOptions(Operation $operation, array $userValues = []) + { + $options = ['headers' => []]; + + foreach ($userValues as $paramName => $paramValue) { + if (null === ($schema = $operation->getParam($paramName))) { + continue; + } + + $method = sprintf('stock%s', ucfirst($schema->getLocation())); + $this->$method($schema, $paramValue, $options); + } + + if (!empty($options['json'])) { + if ($key = $operation->getJsonKey()) { + $options['json'] = [$key => $options['json']]; + } + if (strpos(json_encode($options['json']), '\/') !== false) { + $options['body'] = json_encode($options['json'], JSON_UNESCAPED_SLASHES); + $options['headers']['Content-Type'] = 'application/json'; + unset($options['json']); + } + } + + return $options; + } + + private function stockUrl() + { + } + + private function stockQuery(Parameter $schema, $paramValue, array &$options) + { + $options['query'][$schema->getName()] = $paramValue; + } + + private function stockHeader(Parameter $schema, $paramValue, array &$options) + { + $paramName = $schema->getName(); + + if (stripos($paramName, 'metadata') !== false) { + return $this->stockMetadataHeader($schema, $paramValue, $options); + } + + $options['headers'] += is_scalar($paramValue) ? [$schema->getPrefixedName() => $paramValue] : []; + } + + private function stockMetadataHeader(Parameter $schema, $paramValue, array &$options) + { + foreach ($paramValue as $key => $keyVal) { + $schema = $schema->getItemSchema() ?: new Parameter(['prefix' => $schema->getPrefix(), 'name' => $key]); + $this->stockHeader($schema, $keyVal, $options); + } + } + + private function stockJson(Parameter $schema, $paramValue, array &$options) + { + $json = isset($options['json']) ? $options['json'] : []; + $options['json'] = $this->jsonSerializer->stockJson($schema, $paramValue, $json); + } + + private function stockRaw(Parameter $schema, $paramValue, array &$options) + { + $options['body'] = $paramValue; + } +} diff --git a/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php b/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php new file mode 100644 index 0000000..c2a2dc1 --- /dev/null +++ b/server/vendor/php-opencloud/common/src/Common/Transport/Utils.php @@ -0,0 +1,88 @@ +<?php + +namespace OpenCloud\Common\Transport; + +use function GuzzleHttp\Psr7\uri_for; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\UriInterface; + +class Utils +{ + public static function jsonDecode(ResponseInterface $response, $assoc = true) + { + $jsonErrors = [ + JSON_ERROR_DEPTH => 'JSON_ERROR_DEPTH - Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'JSON_ERROR_STATE_MISMATCH - Underflow or the modes mismatch', + JSON_ERROR_CTRL_CHAR => 'JSON_ERROR_CTRL_CHAR - Unexpected control character found', + JSON_ERROR_SYNTAX => 'JSON_ERROR_SYNTAX - Syntax error, malformed JSON', + JSON_ERROR_UTF8 => 'JSON_ERROR_UTF8 - Malformed UTF-8 characters, possibly incorrectly encoded' + ]; + + $responseBody = (string) $response->getBody(); + + if (strlen($responseBody) === 0) { + return $responseBody; + } + + $data = json_decode($responseBody, $assoc); + + if (JSON_ERROR_NONE !== json_last_error()) { + $last = json_last_error(); + throw new \InvalidArgumentException( + 'Unable to parse JSON data: ' . (isset($jsonErrors[$last]) ? $jsonErrors[$last] : 'Unknown error') + ); + } + + return $data; + } + + /** + * Method for flattening a nested array. + * + * @param array $data The nested array + * @param null $key The key to extract + * + * @return array + */ + public static function flattenJson($data, $key = null) + { + return (!empty($data) && $key && isset($data[$key])) ? $data[$key] : $data; + } + + /** + * Method for normalize an URL string. + * + * Append the http:// prefix if not present, and add a + * closing url separator when missing. + * + * @param string $url The url representation. + * + * @return string + */ + public static function normalizeUrl($url) + { + if (strpos($url, 'http') === false) { + $url = 'http://' . $url; + } + + return rtrim($url, '/') . '/'; + } + + /** + * Add an unlimited list of paths to a given URI. + * + * @param UriInterface $uri + * @param ...$paths + * + * @return UriInterface + */ + public static function addPaths(UriInterface $uri, ...$paths) + { + return uri_for(rtrim((string) $uri, '/') . '/' . implode('/', $paths)); + } + + public static function appendPath(UriInterface $uri, $path) + { + return uri_for(rtrim((string) $uri, '/') . '/' . $path); + } +} |
