summaryrefslogtreecommitdiff
path: root/server/vendor/php-opencloud/common/src/Common
diff options
context:
space:
mode:
authorEoleDev <root@serverpc.home>2016-03-09 16:17:33 +0100
committerEoleDev <root@serverpc.home>2016-03-09 16:17:33 +0100
commit2b8decb81faeb7928bcbfda84c6f33a003f707fd (patch)
tree0e491d7ae2bf91347b1cf50d2c475625d8f659b4 /server/vendor/php-opencloud/common/src/Common
parentb7ebe1272c1127df290535af2430622b28160bb0 (diff)
parent03ef74d0cfe675a6e18a91f039182ca1b248d8f5 (diff)
Maj Library
Diffstat (limited to 'server/vendor/php-opencloud/common/src/Common')
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/AbstractApi.php33
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/AbstractParams.php100
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/ApiInterface.php20
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/Operation.php136
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/Operator.php173
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/OperatorInterface.php51
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Api/Parameter.php389
-rw-r--r--server/vendor/php-opencloud/common/src/Common/ArrayAccessTrait.php67
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Auth/AuthHandler.php73
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Auth/Catalog.php20
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Auth/IdentityService.php13
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Auth/Token.php15
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Error/BadResponseError.php40
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Error/BaseError.php12
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Error/Builder.php179
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Error/NotImplementedError.php12
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Error/UserInputError.php12
-rw-r--r--server/vendor/php-opencloud/common/src/Common/HydratorStrategyTrait.php35
-rw-r--r--server/vendor/php-opencloud/common/src/Common/JsonPath.php119
-rw-r--r--server/vendor/php-opencloud/common/src/Common/JsonSchema/JsonPatch.php115
-rw-r--r--server/vendor/php-opencloud/common/src/Common/JsonSchema/Schema.php72
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/AbstractResource.php243
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Creatable.php19
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Deletable.php18
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/HasMetadata.php67
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/HasWaiterTrait.php124
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Iterator.php97
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Listable.php28
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/ResourceInterface.php29
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Retrievable.php18
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Resource/Updateable.php18
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Service/AbstractService.php14
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Service/Builder.php170
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Service/ServiceInterface.php14
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Transport/HandlerStack.php19
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Transport/JsonSerializer.php95
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Transport/Middleware.php94
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Transport/RequestSerializer.php85
-rw-r--r--server/vendor/php-opencloud/common/src/Common/Transport/Utils.php88
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);
+ }
+}