diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a77c5ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +vendor +build +cache +*.phar \ No newline at end of file diff --git a/CloudObjects/SDK/AccountGateway/AAUIDParser.php b/CloudObjects/SDK/AccountGateway/AAUIDParser.php new file mode 100644 index 0000000..f967d43 --- /dev/null +++ b/CloudObjects/SDK/AccountGateway/AAUIDParser.php @@ -0,0 +1,79 @@ +getScheme()!='aauid' || $iri->getPath()=='') + return self::AAUID_INVALID; + + $segments = explode(':', $iri->getPath()); + switch (count($segments)) { + case 1: + return (preg_match(self::REGEX_AAUID, $segments[0]) == 1) + ? self::AAUID_ACCOUNT + : self::AAUID_INVALID; + case 3; + if (preg_match(self::REGEX_AAUID, $segments[0]) != 1 + || preg_match(self::REGEX_QUALIFIER, $segments[2]) != 1) + return self::AAUID_INVALID; + switch ($segments[1]) { + case "connection": + return self::AAUID_CONNECTION; + case "account": + return self::AAUID_CONNECTED_ACCOUNT; + default: + return self::AAUID_INVALID; + } + default: + return self::AAUID_INVALID; + } + } + + public static function getAAUID(IRI $iri) { + if (self::getType($iri)!=self::AAUID_INVALID) { + $segments = explode(':', $iri->getPath()); + return $segments[0]; + } else + return null; + } + + public static function getQualifier(IRI $iri) { + if (self::getType($iri)==self::AAUID_CONNECTION + || self::getType($iri)==self::AAUID_CONNECTED_ACCOUNT) { + $segments = explode(':', $iri->getPath()); + return $segments[2]; + } else + return null; + } + +} diff --git a/CloudObjects/SDK/AccountGateway/AccountContext.php b/CloudObjects/SDK/AccountGateway/AccountContext.php new file mode 100644 index 0000000..51fd19f --- /dev/null +++ b/CloudObjects/SDK/AccountGateway/AccountContext.php @@ -0,0 +1,369 @@ +aauid = $aauid; + $this->accessToken = $accessToken; + if ($dataLoader) { + $this->dataLoader = $dataLoader; + } else { + $this->dataLoader = new DataLoader; + } + } + + private function parseHeaderIntoNode($headerName, Node $node) { + $keyValuePairs = explode(',', $this->request->getHeaderLine($headerName)); + foreach ($keyValuePairs as $pair) { + $keyValue = explode('=', $pair); + $node->addPropertyValue($keyValue[0], urldecode($keyValue[1])); + } + } + + private function parsePsrRequest(RequestInterface $request) { + $this->request = $request; + + if ($request->hasHeader('C-Accessor')) { + // Store COID of Accessor + $this->accessor = new IRI($request->getHeaderLine('C-Accessor')); + } + + if ($request->hasHeader('C-Account-Domain')) { + // Store account domain + $this->accountDomain = $request->getHeaderLine('C-Account-Domain'); + } + + if ($request->hasHeader('C-Accessor-Latest-Version')) { + // A new version of thie accessor is available, store its COID + $this->latestAccessorVersionCOID = new IRI($request + ->getHeaderLine('C-Accessor-Latest-Version')); + } + + if ($request->hasHeader('C-Account-Connection')) { + // For access from connected accounts, store qualifier + $this->connectionQualifier = $request->getHeaderLine('C-Account-Connection'); + } + + if ($request->hasHeader('C-Install-Connection')) { + // For access from applications, store qualifier + $this->installQualifier = $request->getHeaderLine('C-Install-Connection'); + } + + if ($request->hasHeader('C-Connection-Data')) { + // Copy Data into document + if (!$this->document) $this->document = new Document(); + $this->parseHeaderIntoNode('C-Connection-Data', + $this->document->getGraph()->createNode('aauid:'.$this->getAAUID().':connection:'.$this->connectionQualifier)); + } + } + + /** + * Create a new context from the current request. + * + * @param Request $request + */ + public static function fromSymfonyRequest(Request $request) { + if (!$request->headers->has('C-AAUID') || !$request->headers->has('C-Access-Token')) + return null; + + $context = new AccountContext( + new IRI('aauid:'.$request->headers->get('C-AAUID')), + $request->headers->get('C-Access-Token')); + + $psr7Factory = new DiactorosFactory; + $context->parsePsrRequest($psr7Factory->createRequest($request)); + + return $context; + } + + /** + * Create a new context from the current request. + * + * @param RequestInterface $request + */ + public static function fromPsrRequest(RequestInterface $request) { + if (!$request->hasHeader('C-AAUID') || !$request->hasHeader('C-Access-Token')) + return null; + + $context = new AccountContext( + new IRI('aauid:'.$request->getHeaderLine('C-AAUID')), + $request->getHeaderLine('C-Access-Token')); + + $context->parsePsrRequest($request); + + return $context; + } + + public function getAAUID() { + return $this->aauid; + } + + public function getAccessToken() { + return $this->accessToken; + } + + public function getRequest() { + return $this->request; + } + + public function getDataLoader() { + return $this->dataLoader; + } + + private function getDocument() { + if (!$this->document) { + $this->document = $this->dataLoader->fetchAccountGraphDataDocument($this); + } + + return $this->document; + } + + public function getAccount() { + return $this->getDocument()->getGraph()->getNode($this->getAAUID()); + } + + public function getPerson() { + return $this->getDocument()->getGraph()->getNode($this->getAAUID().':person'); + } + + /** + * Checks whether the context uses an account connection, which is the case when an API + * is requested by a connected account on another service. + */ + public function usesAccountConnection() { + return ($this->connectionQualifier !== null); + } + + /** + * Get the qualifier of the account connection used for accessing the API. + */ + public function getConnectionQualifier() { + return $this->connectionQualifier; + } + + /** + * Get the qualifier for the connection to the platform service. + * Only available when the accessor is an application. + */ + public function getInstallQualifier() { + return $this->installQualifier; + } + + /** + * Get the accessor. + */ + public function getAccessorCOID() { + return $this->accessor; + } + + /** + * Get the account's domain. + * Only set from external API requests, null otherwise. + * + * @return string|null + */ + public function getAccountDomain() { + return $this->accountDomain; + } + + /** + * Get a connected account. + * @param $qualifier The qualifier for the account connection. If not specified, uses the connection qualifier. + */ + public function getConnectedAccount($qualifier = null) { + if (!$qualifier) $qualifier = $this->getConnectionQualifier(); + if (!$qualifier) return null; + return $this->getDocument()->getGraph()->getNode($this->getAAUID().':account:'.$qualifier); + } + + /** + * Get an account connection. + * @param $qualifier The qualifier for the account connection. If not specified, uses the connection qualifier. + */ + public function getAccountConnection($qualifier = null) { + if (!$qualifier) $qualifier = $this->getConnectionQualifier(); + if (!$qualifier) return null; + return $this->getDocument()->getGraph()->getNode($this->getAAUID().':connection:'.$qualifier); + } + + /** + * Get the connected account for a service. + * @param $service COID of the service + */ + public function getConnectedAccountForService($service) { + $accounts = $this->getDocument()->getGraph()->getNodesByType('coid://aauid.net/Account'); + foreach ($accounts as $a) { + if ($a->getProperty('coid://aauid.net/isForService') + && $a->getProperty('coid://aauid.net/isForService')->getId()==$service) return $a; + } + return null; + } + + /** + * Get all account connections. + */ + public function getAllAccountConnections() { + $connections = $this->getAccount()->getProperty('coid://aauid.net/hasConnection'); + if (!is_array($connections)) $connections = array($connections); + return $connections; + } + + /** + * Get all connected accounts. + */ + public function getAllConnectedAccounts() { + $accounts = array(); + foreach ($this->getAllAccountConnections() as $ac) { + $accounts[] = $ac->getProperty('coid://aauid.net/connectsTo'); + } + return $accounts; + } + + /** + * Pushes changes on the Account Graph into the Account Graph. + */ + public function pushGraphUpdates() { + $this->getClient()->post('/~/', [ + 'headers' => ['Content-Type' => 'application/ld+json'], + 'body' => JsonLD::toString($this->getDocument()->toJsonLd()) + ]); + } + + /** + * Specifies a template for the Account Gateway Base URL. Must be a valid URL that + * may contain an {aauid} placeholder. Call this if you want to redirect traffic + * through a proxy or a staging or mock instance of an Account Gateway. Most users + * of this SDK should never call this function. + */ + public function setAccountGatewayBaseURLTemplate($baseUrl) { + $this->agwBaseUrl = $baseUrl; + } + + /** + * Get a preconfigured Guzzle client to access the Account Gateway. + * @return Client + */ + public function getClient() { + if (!$this->client) { + // Create custom handler stack with middlewares + $stack = HandlerStack::create(); + + $context = $this; + $stack->push(Middleware::mapResponse(function (ResponseInterface $response) use ($context) { + // If a new version of this accessor is available, store its COID + if ($response->hasHeader('C-Accessor-Latest-Version')) + $context->setLatestAccessorVersionCOID( + new IRI($response->getHeaderLine('C-Accessor-Latest-Version'))); + return $response; + })); + + // Prepare client options + $options = [ + 'base_uri' => str_replace('{aauid}', AAUIDParser::getAAUID($this->getAAUID()), $this->agwBaseUrl), + 'headers' => [ + 'Authorization' => 'Bearer '.$this->getAccessToken() + ], + 'handler' => $stack + ]; + if (isset($this->request) && $this->request->hasHeader('X-Forwarded-For')) { + $options['headers']['X-Forwarded-For'] = $this->request->getHeaderLine('X-Forwarded-For'); + } + + // Create client + $this->client = new Client($options); + } + return $this->client; + } + + /** + * Set a custom code for the current request in the Account Gateway logs. + */ + public function setLogCode($logCode) { + if (!$this->request) { + throw new \Exception('Not in a request context.'); + } + $this->logCode = $logCode; + } + + /** + * Process a response and add headers if applicable. + */ + public function processResponse(Response $response) { + if ($this->logCode) { + $response->headers->set('C-Code-For-Logger', $this->logCode); + } + } + + /** + * Check whether a new version of the accessor is available. This information + * is updated from incoming and outgoing requests. If no request was executed, + * returns false. + * + * @return boolean + */ + public function isNewAccessorVersionAvailable() { + return isset($this->latestAccessorVersionCOID); + } + + /** + * Get the COID of the latest accessor version, if one is available, or + * null otherwise. This information is updated from incoming and outgoing + * requests. If no request was executed, returns null. + * + * @return IRI|null + */ + public function getLatestAccessorVersionCOID() { + return $this->latestAccessorVersionCOID; + } + + /** + * Set the COID of the latest accessor version. This method should only + * called from request processing codes. Most developers should not use it. + * + * @param IRI $latestAccessorVersionCOID + */ + public function setLatestAccessorVersionCOID(IRI $latestAccessorVersionCOID) { + $this->latestAccessorVersionCOID = $latestAccessorVersionCOID; + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/AccountGateway/DataLoader.php b/CloudObjects/SDK/AccountGateway/DataLoader.php new file mode 100644 index 0000000..e0d81a1 --- /dev/null +++ b/CloudObjects/SDK/AccountGateway/DataLoader.php @@ -0,0 +1,81 @@ +cache; + } + + public function setCache(Cache $cache) { + $this->cache = $cache; + return $this; + } + + public function getCachePrefix() { + return $this->cachePrefix; + } + + public function setCachePrefix($cachePrefix) { + $this->cachePrefix = $cachePrefix; + return $this; + } + + public function getMountPointName() { + return $this->mountPointName; + } + + public function setMountPointName($mountPointName) { + $this->mountPointName = $mountPointName; + return $this; + } + + public function fetchAccountGraphDataDocument(AccountContext $accountContext) { + $dataRequest = new Request('GET', '/'.$this->mountPointName.'/', + ['Accept' => 'application/ld+json']); + + if (!$this->cache || !$accountContext->getRequest() + || !$accountContext->getRequest()->hasHeader('C-Data-Updated')) { + // No cache or no timestamp available, so always fetch from Account Gateway + $dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody(); + } else { + $key = $this->cachePrefix.$accountContext->getAAUID(); + $remoteTimestamp = $accountContext->getRequest()->getHeaderLine('C-Data-Updated'); + if ($this->cache->contains($key)) { + // Check timestamp + $cacheEntry = $this->cache->fetch($key); + $timestamp = substr($cacheEntry, 0, strpos($cacheEntry, '|')); + if ($timestamp==$remoteTimestamp) { + // Cache data is up to date, can be returned + $dataString = substr($cacheEntry, strpos($cacheEntry, '|')+1); + } else { + // Fetch from Account Gateway and update cache entry + $dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody(); + $this->cache->save($key, $remoteTimestamp.'|'.$dataString, self::CACHE_TTL); + } + } else { + // Fetch from Account Gateway and store in cache + $dataString = (string)$accountContext->getClient()->send($dataRequest)->getBody(); + $this->cache->save($key, $remoteTimestamp.'|'.$dataString, self::CACHE_TTL); + } + } + + return JsonLD::getDocument($dataString); + } + +} diff --git a/CloudObjects/SDK/COIDParser.php b/CloudObjects/SDK/COIDParser.php new file mode 100644 index 0000000..c270668 --- /dev/null +++ b/CloudObjects/SDK/COIDParser.php @@ -0,0 +1,152 @@ +getHost()).$coidPre->getPath()); + } + + /** + * Get the type of a COID. + * + * @param IRI $coid + * @return int|null + */ + public static function getType(IRI $coid) { + if ($coid->getScheme()!='coid' || $coid->getHost()=='' + || preg_match(self::REGEX_HOSTNAME, $coid->getHost()) != 1) + return self::COID_INVALID; + + if ($coid->getPath()=='' || $coid->getPath()=='/') + return self::COID_ROOT; + + $segments = explode('/', $coid->getPath()); + switch (count($segments)) { + case 2: + return (preg_match(self::REGEX_SEGMENT, $segments[1]) == 1) + ? self::COID_UNVERSIONED + : self::COID_INVALID; + case 3: + if (preg_match(self::REGEX_SEGMENT, $segments[1]) != 1) + return self::COID_INVALID; + + if (preg_match(self::REGEX_SEGMENT, $segments[2]) == 1) + return self::COID_VERSIONED; + else + if (preg_match(self::REGEX_VERSION_WILDCARD, $segments[2]) == 1) + return self::COID_VERSION_WILDCARD; + else + return self::COID_INVALID; + default: + return self::COID_INVALID; + } + } + + /** + * Checks whether the given IRI object is a valid COID. + * + * @param IRI $coid + * @return boolean + */ + public static function isValidCOID(IRI $coid) { + return (self::getType($coid)!=self::COID_INVALID); + } + + /** + * Get the name segment of a valid COID or null if not available. + * + * @param IRI $coid + * @return string|null + */ + public static function getName(IRI $coid) { + if (self::getType($coid)!=self::COID_INVALID + && self::getType($coid)!=self::COID_ROOT) { + $segments = explode('/', $coid->getPath()); + return $segments[1]; + } else + return null; + } + + /** + * Get the version segment of a valid, versioned COID or null if not available. + * + * @param IRI $coid + * @return string|null + */ + public static function getVersion(IRI $coid) { + if (self::getType($coid)==self::COID_VERSIONED) { + $segments = explode('/', $coid->getPath()); + return $segments[2]; + } else + return null; + } + + /** + * Get the version segment of a versioned or version wildcard COID or + * null if not available. + * + * @param IRI $coid + * @return string|null + */ + public static function getVersionWildcard(IRI $coid) { + if (self::getType($coid)==self::COID_VERSION_WILDCARD) { + $segments = explode('/', $coid->getPath()); + return $segments[2]; + } else + return null; + } + + /** + * Returns the COID itself if it is a root COID or a new IRI object + * representing the namespace underlying the given COID. + * + * @param IRI $coid + * @return IRI|null + */ + public static function getNamespaceCOID(IRI $coid) { + switch (self::getType($coid)) { + case self::COID_ROOT: + return $coid; + case self::COID_UNVERSIONED: + case self::COID_VERSIONED: + case self::COID_VERSION_WILDCARD: + return new IRI('coid://'.$coid->getHost()); + default: + return null; + } + } + +} diff --git a/CloudObjects/SDK/Common/CryptoHelper.php b/CloudObjects/SDK/Common/CryptoHelper.php new file mode 100644 index 0000000..b48efa7 --- /dev/null +++ b/CloudObjects/SDK/Common/CryptoHelper.php @@ -0,0 +1,72 @@ +reader->getFirstValueString($this->namespace, 'common:usesSharedEncryptionKey'); + if (!isset($keyValue)) + throw new InvalidObjectConfigurationException("The namespace doesn't have an encryption key."); + + return Key::loadFromAsciiSafeString($keyValue); + } + + /** + * Encrypt data with the default namespace's shared encryption key. + */ + public function encryptWithSharedEncryptionKey($data) { + return Crypto::encrypt($data, $this->getSharedEncryptionKey()); + } + + /** + * Decrypt data with the default namespace's shared encryption key. + */ + public function decryptWithSharedEncryptionKey($data) { + return Crypto::decrypt($data, $this->getSharedEncryptionKey()); + } + + /** + * @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever. + * @param IRI|null $namespaceCoid The namespace used to retrieve keys. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used. + */ + public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) { + if (!class_exists('Defuse\Crypto\Crypto')) + throw new Exception("Run composer require defuse/php-encryption before using CryptoHelper."); + + $this->objectRetriever = $objectRetriever; + $this->namespace = isset($namespaceCoid) + ? $objectRetriever->getObject($namespaceCoid) + : $objectRetriever->getAuthenticatingNamespaceObject(); + + $this->reader = new NodeReader([ + 'prefixes' => [ + 'common' => 'coid://common.cloudobjects.io/' + ] + ]); + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/Exceptions/CoreAPIException.php b/CloudObjects/SDK/Exceptions/CoreAPIException.php new file mode 100644 index 0000000..508a3a6 --- /dev/null +++ b/CloudObjects/SDK/Exceptions/CoreAPIException.php @@ -0,0 +1,14 @@ +objectRetriever = $objectRetriever; + $this->reader = new NodeReader; + } + + /** + * Initialize and return the SDK with the given classname. + * Throws Exception if the SDK is not supported. + * + * @param $classname Classname for the SDK's main class + * @param array $options Additional options for the SDK (if necessary) + */ + public function get($classname, array $options) { + if (!class_exists($classname)) + throw new Exception("<".$classname."> is not a valid classname."); + + $hashkey = md5($classname.serialize($options)); + if (!isset($this->classes[$hashkey])) { + $nsNode = $this->objectRetriever->getAuthenticatingNamespaceObject(); + + // --- Amazon Web Services (https://aws.amazon.com/) --- + // has multiple classnames, so check for common superclass + if (is_a($classname, 'Aws\AwsClient', true)) { + $class = new $classname(array_merge($options, [ + 'credentials' => [ + 'key' => $this->reader->getFirstValueString($nsNode, 'coid://aws.3rd-party.co/accessKeyId'), + 'secret' => $this->reader->getFirstValueString($nsNode, 'coid://aws.3rd-party.co/secretAccessKey') + ] + ])); + } else { + switch ($classname) { + + // --- stream (https://getstream.io/) --- + case "GetStream\Stream\Client": + $class = new $classname( + $this->reader->getFirstValueString($nsNode, 'coid://getstreamio.3rd-party.co/key'), + $this->reader->getFirstValueString($nsNode, 'coid://getstreamio.3rd-party.co/secret') + ); + break; + + // --- Pusher (https://pusher.com/) --- + case "Pusher": + $class = new $classname( + $this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/key'), + $this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/secret'), + $this->reader->getFirstValueString($nsNode, 'coid://pusher.3rd-party.co/appId'), + $options + ); + break; + } + } + } + + if (!isset($class)) + throw new Exception("No rules defined to initialize <".$classname.">."); + + $this->classes[$hashkey] = $class; + return $this->classes[$hashkey]; + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/Helpers/SharedSecretAuthentication.php b/CloudObjects/SDK/Helpers/SharedSecretAuthentication.php new file mode 100644 index 0000000..483d714 --- /dev/null +++ b/CloudObjects/SDK/Helpers/SharedSecretAuthentication.php @@ -0,0 +1,110 @@ +objectRetriever = $objectRetriever; + } + + /** + * Verifies credentials. + * @deprecated + * + * @param ObjectRetriever $retriever Provides access to CloudObjects. + * @param string $username Username; a domain. + * @param string $password Password; a shared secret. + * + * @return integer A result constant, RESULT_OK if successful. + */ + public static function verifyCredentials(ObjectRetriever $retriever, $username, $password) { + // Validate input + $namespaceCoid = new IRI('coid://'.$username); + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + return self::RESULT_INVALID_USERNAME; + if (strlen($password) != 40) + return self::RESULT_INVALID_PASSWORD; + + // Retrieve namespace + $namespace = $retriever->getObject($namespaceCoid); + if (!isset($namespace)) + return self::RESULT_NAMESPACE_NOT_FOUND; + + // Read and validate shared secret + $reader = new NodeReader([ + 'prefixes' => [ + 'co' => 'coid://cloudobjects.io/' + ] + ]); + $sharedSecret = $reader->getAllValuesNode($namespace, 'co:hasSharedSecret'); + if (count($sharedSecret) != 1) + return self::RESULT_SHARED_SECRET_NOT_RETRIEVABLE; + + if ($reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue') == $password) + return self::RESULT_OK; + else + return self::RESULT_SHARED_SECRET_INCORRECT; + } + + /** + * Verifies credentials. + * + * @param string $username Username; a domain. + * @param string $password Password; a shared secret. + * + * @return integer A result constant, RESULT_OK if successful. + */ + public function verify($username, $password) { + // Validate input + $namespaceCoid = new IRI('coid://'.$username); + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + return self::RESULT_INVALID_USERNAME; + if (strlen($password) != 40) + return self::RESULT_INVALID_PASSWORD; + + // Retrieve namespace + $namespace = $this->objectRetriever->getObject($namespaceCoid); + if (!isset($namespace)) + return self::RESULT_NAMESPACE_NOT_FOUND; + + // Read and validate shared secret + $reader = new NodeReader([ + 'prefixes' => [ + 'co' => 'coid://cloudobjects.io/' + ] + ]); + $sharedSecret = $reader->getAllValuesNode($namespace, 'co:hasSharedSecret'); + if (count($sharedSecret) != 1) + return self::RESULT_SHARED_SECRET_NOT_RETRIEVABLE; + + if ($reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue') == $password) + return self::RESULT_OK; + else + return self::RESULT_SHARED_SECRET_INCORRECT; + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/JSON/SchemaValidator.php b/CloudObjects/SDK/JSON/SchemaValidator.php new file mode 100644 index 0000000..7cfc1c5 --- /dev/null +++ b/CloudObjects/SDK/JSON/SchemaValidator.php @@ -0,0 +1,76 @@ +objectRetriever = $objectRetriever; + $this->reader = new NodeReader([ + 'prefixes' => [ + 'json' => 'coid://json.co-n.net/' + ] + ]); + } + + /** + * Validate data against an element specification in an RDF node. + * + * @param mixed $data The data to validate. + * @param Node $node The specification to validate against. + */ + public function validateAgainstNode($data, Node $node) { + if ($this->reader->hasType($node, 'json:String')) + Assert::string($data); + elseif ($this->reader->hasType($node, 'json:Boolean')) + Assert::boolean($data); + elseif ($this->reader->hasType($node, 'json:Number')) + Assert::numeric($data); + elseif ($this->reader->hasType($node, 'json:Integer')) + Assert::integer($data); + elseif ($this->reader->hasType($node, 'json:Array')) + Assert::isArray($data); + elseif ($this->reader->hasType($node, 'json:Object')) { + Assert::isArrayAccessible($data); + foreach ($this->reader->getAllValuesNode($node, 'json:hasProperty') as $prop) { + $key = $this->reader->getFirstValueString($prop, 'json:hasKey'); + if ($this->reader->getFirstValueBool($prop, 'json:isRequired') == true) + Assert::keyExists($data, $key); + if (isset($data[$key])) + $this->validateAgainstNode($data[$key], $prop); + } + } + } + + /** + * Validate data against a specification stored in CloudObjects. + * + * @param mixed $data The data to validate. + * @param Node $node The COID of the specification. + */ + public function validateAgainstCOID($data, IRI $coid) { + $object = $this->objectRetriever->getObject($coid); + Assert::true($this->reader->hasType($object, 'json:Element'), + "You can only validate data against JSON elements!"); + $this->validateAgainstNode($data, $object); + } +} \ No newline at end of file diff --git a/CloudObjects/SDK/NodeReader.php b/CloudObjects/SDK/NodeReader.php new file mode 100644 index 0000000..f4393b9 --- /dev/null +++ b/CloudObjects/SDK/NodeReader.php @@ -0,0 +1,364 @@ +prefixes = $options['prefixes']; + } + + private function expand($uri) { + if (!is_string($uri)) $uri = (string)$uri; + $scheme = parse_url($uri, PHP_URL_SCHEME); + if (isset($scheme) && isset($this->prefixes[$scheme])) + return str_replace($scheme.':', $this->prefixes[$scheme], $uri); + else + return $uri; + } + + /** + * Checks whether a node has a certain type. + * + * @param Node $node The node to work on. + * @param string|object $type The type to check for. + * @return boolean + */ + public function hasType(Node $node = null, $type) { + if (!isset($node)) + return false; + $type = $this->expand($type); + $typesFromNode = $node->getType(); + if (!isset($typesFromNode)) + return false; + if (is_array($typesFromNode)) { + foreach ($typesFromNode as $t) + if (is_a($t, 'ML\JsonLD\Node') + && $t->getId() == $type) + return true; + } else + if (is_a($typesFromNode, 'ML\JsonLD\Node') + && $typesFromNode->getId() == $type) + return true; + else + return false; + + return false; + } + + private function getFirstValue(Node $node = null, $property, $default = null) { + if (!isset($node)) + return $default; + $valueFromNode = $node->getProperty($this->expand($property)); + if (!isset($valueFromNode)) + return $default; + if (is_array($valueFromNode)) + return $valueFromNode[0]; + + return $valueFromNode; + } + + /** + * Reads a property from a node and converts it into a string. + * If the property has multiple values only the first is returned. + * If no value is found or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return string|null + */ + public function getFirstValueString(Node $node = null, $property, $default = null) { + $valueFromNode = $this->getFirstValue($node, $property, $default); + if ($valueFromNode == $default) + return $default; + + if (is_a($valueFromNode, 'ML\JsonLD\Node')) + return $valueFromNode->getId(); + else + return $valueFromNode->getValue(); + } + + /** + * Reads a property from a node and converts it into a boolean. + * If the property has multiple values only the first is returned. + * If no value is found or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return bool|null + */ + public function getFirstValueBool(Node $node = null, $property, $default = null) { + return (in_array( + $this->getFirstValueString($node, $property, $default), + [ '1', 'true' ] + )); + } + + /** + * Reads a property from a node and converts it into an integer. + * If the property has multiple values only the first is returned. + * If no value is found or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return int|null + */ + public function getFirstValueInt(Node $node = null, $property, $default = null) { + $value = $this->getFirstValueString($node, $property); + if (is_numeric($value)) + return (int)($value); + + return $default; + } + + /** + * Reads a property from a node and converts it into an float. + * If the property has multiple values only the first is returned. + * If no value is found or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return float|null + */ + public function getFirstValueFloat(Node $node = null, $property, $default = null) { + $value = $this->getFirstValueString($node, $property); + if (is_numeric($value)) + return (float)($value); + + return $default; + } + + /** + * Reads a property from a node and converts it into a IRI. + * If the property has multiple values only the first is returned. + * If no value is found, value is a literal or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return string|null + */ + public function getFirstValueIRI(Node $node = null, $property, IRI $default = null) { + $valueFromNode = $this->getFirstValue($node, $property, $default); + if ($valueFromNode == $default) + return $default; + + if (is_a($valueFromNode, 'ML\JsonLD\Node')) + return new IRI($valueFromNode->getId()); + else + return $default; + } + + /** + * Reads a property from a node and returns it as a Node. + * If the property has multiple values only the first is returned. + * If no value is found, value is a literal or the node is null, the default is returned. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param $default The default that is returned if no value for the property exists on the node. + * @return string|null + */ + public function getFirstValueNode(Node $node = null, $property, Node $default = null) { + $valueFromNode = $this->getFirstValue($node, $property, $default); + if ($valueFromNode == $default) + return $default; + + if (is_a($valueFromNode, 'ML\JsonLD\Node')) + return $valueFromNode; + else + return $default; + } + + /** + * Checks whether a node has a specific value for a property. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @param string|object $value The expected value. + * @return boolean + */ + public function hasPropertyValue(Node $node = null, $property, $value) { + if (!isset($node)) + return false; + $valuesFromNode = $node->getProperty($this->expand($property)); + if (!isset($valuesFromNode)) + return false; + if (!is_array($valuesFromNode)) + $valuesFromNode = array($valuesFromNode); + + foreach ($valuesFromNode as $v) { + if (is_a($v, 'ML\JsonLD\Node')) { + if ($v->getId() == $this->expand($value)) + return true; + } else { + if ($v->getValue() == $value) + return true; + } + } + + return false; + } + + /** + * Checks whether the node has at least one value for a property. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return boolean + */ + public function hasProperty(Node $node = null, $property) { + if (!isset($node)) + return false; + + return ($node->getProperty($this->expand($property)) != null); + } + + private function getAllValues(Node $node = null, $property) { + if (!isset($node)) + return []; + + $valueFromNode = $node->getProperty($this->expand($property)); + if (!isset($valueFromNode)) + return []; + if (!is_array($valueFromNode)) + $valueFromNode = [$valueFromNode]; + return $valueFromNode; + } + + /** + * Get the language-tagged-string for the property in the specified language. + * If no value is found for the specified language, the default is returned. + */ + public function getLocalizedString(Node $node = null, $property, $language, $default = null) { + $values = $this->getAllValues($node, $property); + foreach ($values as $v) { + if (is_a($v, 'ML\JsonLD\LanguageTaggedString') && $v->getLanguage() == $language) + return $v->getValue(); + } + + return $default; + } + + /** + * Reads all values from a node and returns them as a string array. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesString(Node $node = null, $property) { + $allValues = $this->getAllValues($node, $property); + $output = []; + foreach ($allValues as $a) + if (is_a($a, 'ML\JsonLD\Node')) + $output[] = $a->getId(); + else + $output[] = $a->getValue(); + + return $output; + } + + /** + * Reads all values from a node and returns them as a boolean array. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesBool(Node $node = null, $property) { + $allValues = $this->getAllValuesString($node, $property); + $output = []; + foreach ($allValues as $a) + $output = in_array($a, [ '1', 'true' ]); + + return $output; + } + + /** + * Reads all values from a node and returns them as an integer array. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesInt(Node $node = null, $property) { + $allValues = $this->getAllValuesString($node, $property); + $output = []; + foreach ($allValues as $a) + $output = (int)$a; + + return $output; + } + + /** + * Reads all values from a node and returns them as a float array. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesFloat(Node $node = null, $property) { + $allValues = $this->getAllValuesString($node, $property); + $output = []; + foreach ($allValues as $a) + $output = (float)$a; + + return $output; + } + + /** + * Reads all values from a node and returns them as a IRI array. + * Only converts the Node IDs of nodes into IRI, literal values are skipped. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesIRI(Node $node = null, $property) { + $allValues = $this->getAllValues($node, $property); + $output = []; + foreach ($allValues as $a) + if (is_a($a, 'ML\JsonLD\Node')) + $output[] = new IRI($a->getId()); + + return $output; + } + + /** + * Reads all values from a node and returns them as a Node array. + * Returns only nodes, literal values are skipped. + * + * @param Node $node The node to work on. + * @param string|object $property The property to read. + * @return array + */ + public function getAllValuesNode(Node $node = null, $property) { + $allValues = $this->getAllValues($node, $property); + $output = []; + foreach ($allValues as $a) + if (is_a($a, 'ML\JsonLD\Node')) + $output[] = $a; + + return $output; + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/ObjectRetriever.php b/CloudObjects/SDK/ObjectRetriever.php new file mode 100644 index 0000000..d5a17f1 --- /dev/null +++ b/CloudObjects/SDK/ObjectRetriever.php @@ -0,0 +1,464 @@ +options = array_merge([ + 'cache_provider' => 'none', + 'cache_prefix' => 'clobj:', + 'cache_ttl' => 60, + 'static_config_path' => null, + 'auth_ns' => null, + 'auth_secret' => null, + 'api_base_url' => null, + 'logger' => null, + 'timeout' => 20, + 'connect_timeout' => 5 + ], $options); + + // Set up object cache + switch ($this->options['cache_provider']) { + case 'none': + // no caching + $this->cache = null; + break; + case 'redis': + // caching with Redis + $redis = new \Redis(); + $redis->pconnect( + isset($this->options['cache_provider.redis.host']) ? $this->options['cache_provider.redis.host'] : '127.0.0.1', + isset($this->options['cache_provider.redis.port']) ? $this->options['cache_provider.redis.port'] : 6379); + + $this->cache = new RedisCache(); + $this->cache->setRedis($redis); + break; + case 'file': + // caching on the filesystem + $this->cache = new \Doctrine\Common\Cache\FilesystemCache( + isset($this->options['cache_provider.file.directory']) ? $this->options['cache_provider.file.directory'] : sys_get_temp_dir() + ); + break; + default: + throw new Exception('Valid values for cache_provider are: none, redis, file'); + } + + // Set up logger + if (is_a($this->options['logger'], LoggerInterface::class)) + $this->setLogger($this->options['logger']); + + // Set up handler stack + $stack = HandlerStack::create(); + + // Add HTTP cache if specified + if (isset($this->cache)) { + $stack->push( + new CacheMiddleware( + new PrivateCacheStrategy( + new DoctrineCacheStorage($this->cache) + ) + ) + ); + } + + // Initialize client + $options = [ + 'base_uri' => isset($options['api_base_url']) ? $options['api_base_url'] : self::CO_API_URL, + 'handler' => $stack, + 'connect_timeout' => $this->options['connect_timeout'], + 'timeout' => $this->options['timeout'] + ]; + + if (isset($this->options['auth_ns']) && isset($this->options['auth_secret'])) + $options['auth'] = [$this->options['auth_ns'], $this->options['auth_secret']]; + + $this->client = new Client($options); + } + + private function logInfoWithTime($message, $ts) { + if (isset($this->logger)) + $this->logger->info($message, [ 'elapsed_ms' => round((microtime(true) - $ts) * 1000) ]); + } + + private function getFromCache($id) { + return (isset($this->cache) && $this->cache->contains($this->options['cache_prefix'].$id)) + ? $this->cache->fetch($this->options['cache_prefix'].$id) : null; + } + + private function putIntoCache($id, $data, $ttl) { + if (isset($this->cache)) + $this->cache->save($this->options['cache_prefix'].$id, $data, $ttl); + } + + /** + * Get the HTTP client that is used to access the API. + */ + public function getClient() { + return $this->client; + } + + /** + * Set the HTTP client that is used to access the API. + * + * @param ClientInterface $client The HTTP client. + * @param string $prefix An optional prefix (e.g. an AccountGateway mountpoint) + */ + public function setClient(ClientInterface $client, $prefix = null) { + $this->client = $client; + $this->prefix = $prefix; + } + + /** + * Creates a clone of this object retriever that uses the given account + * context to access the API as a developer API. Cache settings are inherited + * but the prefix is extended to keep cache content specific to account. + * + * @param AccountContext $accountContext + * @param string $mountpointName The name for the API mountpoint. + */ + public function withAccountContext(AccountContext $accountContext, string $mountpointName) { + $newRetriever = new self($this->options); + $newRetriever->options['cache_prefix'] .= (string)$accountContext->getAAUID(); + $newRetriever->client = $accountContext->getClient(); + $newRetriever->prefix = '/'.$mountpointName.'/'; + + return $newRetriever; + } + + /** + * Get an object description from CloudObjects. Attempts to get object + * from in-memory cache first, stored static configurations next, + * configured external cache third, and finally calls the Object API + * on CloudObjects Core. Returns null if the object was not found. + * + * @param IRI $coid COID of the object + * @return Node|null + */ + public function getObject(IRI $coid) { + if (!COIDParser::isValidCOID($coid)) + throw new Exception("Not a valid COID."); + + $uriString = (string)$coid; + + if (isset($this->objects[$uriString])) + // Return from in-memory cache if it exists + return $this->objects[$uriString]; + + $ts = microtime(true); + + if (isset($this->options['static_config_path'])) { + $location = realpath($this->options['static_config_path'].DIRECTORY_SEPARATOR. + $coid->getHost().str_replace('/', DIRECTORY_SEPARATOR, $coid->getPath()) + .DIRECTORY_SEPARATOR.'object.jsonld'); + + if ($location && file_exists($location)) { + $object = $location; + $this->logInfoWithTime('Fetched <'.$uriString.'> from static configuration.', $ts); + } + } + + if (!isset($object)) { + $object = $this->getFromCache($uriString); + if (isset($object)) + $this->logInfoWithTime('Fetched <'.$uriString.'> from object cache.', $ts); + } + + if (!isset($object)) { + try { + $response = $this->client + ->get((isset($this->prefix) ? $this->prefix : '').$coid->getHost().$coid->getPath().'/object', + ['headers' => ['Accept' => 'application/ld+json']]); + + $object = (string)$response->getBody(); + $this->putIntoCache($uriString, $object, $this->options['cache_ttl']); + $this->logInfoWithTime('Fetched <'.$uriString.'> from Core API ['.$response->getStatusCode().'].', $ts); + } catch (RequestException $e) { + if ($e->hasResponse()) + $this->logInfoWithTime('Object <'.$uriString.'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts); + else + $this->logInfoWithTime('Object <'.$uriString.'> could not be retrieved from Core API.', $ts); + return null; + } + } + + $document = JsonLD::getDocument($object); + $this->objects[$uriString] = $document->getGraph()->getNode($uriString); + return $this->objects[$uriString]; + } + + /** + * Fetch all object descriptions for objects in a specific namespace + * and with a certain type from CloudObjects. Adds individual objects + * to cache and returns a list of COIDs (as IRI) for them. The list + * itself is not cached, which means that every call of this function + * goes to the Object API. + * + * @param IRI $namespaceCoid COID of the namespace + * @param $type RDF type that objects should have + * @return array + */ + public function fetchObjectsInNamespaceWithType(IRI $namespaceCoid, $type) { + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + throw new Exception("Not a valid namespace COID."); + + $ts = microtime(true); + $type = (string)$type; + + try { + $response = $this->client + ->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/all', + [ + 'headers' => [ 'Accept' => 'application/ld+json' ], + 'query' => [ 'type' => $type ] + ]); + + $document = JsonLD::getDocument((string)$response->getBody()); + $allObjects = $document->getGraph()->getNodesByType($type); + $allIris = []; + foreach ($allObjects as $object) { + $iri = new IRI($object->getId()); + if (!COIDParser::isValidCOID($iri)) continue; + if ($iri->getHost() != $namespaceCoid->getHost()) continue; + + $this->objects[$object->getId()] = $object; + $this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']); + $allIris[] = $iri; + } + + $this->logInfoWithTime('Fetched all objects with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); + } catch (Exception $e) { + throw new CoreAPIException; + } + + return $allIris; + } + + /** + * Fetch all object descriptions for objects in a specific namespace + * from CloudObjects. Adds individual objects to cache and returns a + * list of COIDs (as IRI) for them. The list itself is not cached, + * which means that every call of this function goes to the Object API. + * + * @param IRI $namespaceCoid COID of the namespace + * @return array + */ + public function fetchAllObjectsInNamespace(IRI $namespaceCoid) { + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + throw new Exception("Not a valid namespace COID."); + + $ts = microtime(true); + + try { + $response = $this->client + ->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/all', + [ 'headers' => [ 'Accept' => 'application/ld+json' ] ]); + + $document = JsonLD::getDocument((string)$response->getBody()); + $allObjects = $document->getGraph()->getNodes(); + $allIris = []; + foreach ($allObjects as $object) { + $iri = new IRI($object->getId()); + if (!COIDParser::isValidCOID($iri)) continue; + if ($iri->getHost() != $namespaceCoid->getHost()) continue; + + $this->objects[$object->getId()] = $object; + $this->putIntoCache($object->getId(), $object, $this->options['cache_ttl']); + $allIris[] = $iri; + } + + $this->logInfoWithTime('Fetched all objects for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); + + } catch (Exception $e) { + throw new CoreAPIException; + } + + return $allIris; + } + + /** + * Fetch a list of COIDs for all objects in a specific namespace + * from CloudObjects, but not the objects itself. The list is not cached, + * which means that every call of this function goes to the Object API. + * + * @param IRI $namespaceCoid COID of the namespace + * @return array + */ + public function getCOIDListForNamespace(IRI $namespaceCoid) { + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + throw new Exception("Not a valid namespace COID."); + + $ts = microtime(true); + + try { + $response = $this->client + ->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/coids', + [ 'headers' => [ 'Accept' => 'application/ld+json' ] ]); + + $document = JsonLD::getDocument((string)$response->getBody()); + $containerNode = $document->getGraph()->getNode('co-namespace-members://'.$namespaceCoid->getHost()); + + $reader = new NodeReader([ 'prefixes' => [ 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' ]]); + $allIris = $reader->getAllValuesIRI($containerNode, 'rdfs:member'); + + $this->logInfoWithTime('Fetched object list for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); + + } catch (Exception $e) { + throw new CoreAPIException; + } + + return $allIris; + } + + /** + * Fetch a list of COIDs for all objects in a specific namespace + * from CloudObjects, but not the objects itself. The list is not cached, + * which means that every call of this function goes to the Object API. + * + * @param IRI $namespaceCoid COID of the namespace + * @param $type RDF type that objects should have + * @return array + */ + public function getCOIDListForNamespaceWithType(IRI $namespaceCoid, $type) { + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + throw new Exception("Not a valid namespace COID."); + + $ts = microtime(true); + $type = (string)$type; + + try { + $response = $this->client + ->get((isset($this->prefix) ? $this->prefix : '').$namespaceCoid->getHost().'/coids', + [ + 'headers' => [ 'Accept' => 'application/ld+json' ], + 'query' => [ 'type' => $type ] + ]); + + $document = JsonLD::getDocument((string)$response->getBody()); + $containerNode = $document->getGraph()->getNode('co-namespace-members://'.$namespaceCoid->getHost()); + + $reader = new NodeReader([ 'prefixes' => [ 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' ]]); + $allIris = $reader->getAllValuesIRI($containerNode, 'rdfs:member'); + + $this->logInfoWithTime('Fetched object list with <'.$type.'> for <'.$namespaceCoid->getHost().'> from Core API ['.$response->getStatusCode().'].', $ts); + + } catch (Exception $e) { + throw new CoreAPIException; + } + + return $allIris; + } + + /** + * Get an object description from CloudObjects. Shorthand method for + * "getObject" which allows passing the COID as string instead of IRI. + * + * @param any $coid + * @return Node|null + */ + public function get($coid) { + if (is_string($coid)) + return $this->getObject(new IRI($coid)); + + if (is_object($coid) && get_class($coid)=='ML\IRI\IRI') + return $this->getObject($coid); + + throw new Exception('COID must be passed as a string or an IRI object.'); + } + + /** + * Get a object's attachment. + * + * @param IRI $coid + * @param string $filename + */ + public function getAttachment(IRI $coid, $filename) { + $object = $this->getObject($coid); + + if (!$object) + // Cannot get attachment for non-existing object + return null; + + $ts = microtime(true); + + $cacheId = $object->getId().'#'.$filename; + $fileData = $this->getFromCache($cacheId); + + // Parse cached data into revision and content + if (isset($fileData)) { + $this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from object cache.', $ts); + list($fileRevision, $fileContent) = explode('#', $fileData, 2); + } + + if (!isset($fileData) + || $fileRevision!=$object->getProperty(self::REVISION_PROPERTY)->getValue()) { + + // Does not exist in cache or is outdated, fetch from CloudObjects + try { + $response = $this->client->get((isset($this->prefix) ? $this->prefix : '').$coid->getHost().$coid->getPath() + .'/'.basename($filename)); + + $fileContent = $response->getBody()->getContents(); + $fileData = $object->getProperty(self::REVISION_PROPERTY)->getValue().'#'.$fileContent; + $this->putIntoCache($cacheId, $fileData, 0); + + $this->logInfoWithTime('Fetched attachment <'.$filename.'> for <'.$object->getId().'> from Core API ['.$response->getStatusCode().'].', $ts); + } catch (Exception $e) { + $this->logInfoWithTime('Attachment <'.$filename.'> for <'.$object->getId().'> not found in Core API ['.$e->getResponse()->getStatusCode().'].', $ts); + // ignore exception - treat as non-existing file + } + + } + + return $fileContent; + } + + /** + * Retrieve the object that describes the namespace provided with the "auth_ns" + * configuration option. + * + * @return Node + */ + public function getAuthenticatingNamespaceObject() { + if (!isset($this->options['auth_ns'])) + throw new Exception("Missing 'auth_ns' configuration option."); + + $namespaceCoid = COIDParser::fromString($this->options['auth_ns']); + if (COIDParser::getType($namespaceCoid) != COIDParser::COID_ROOT) + throw new Exception("The 'auth_ns' configuration option is not a valid namespace/root COID."); + + return $this->getObject($namespaceCoid); + } + +} \ No newline at end of file diff --git a/CloudObjects/SDK/WebAPI/APIClientFactory.php b/CloudObjects/SDK/WebAPI/APIClientFactory.php new file mode 100644 index 0000000..859b8f1 --- /dev/null +++ b/CloudObjects/SDK/WebAPI/APIClientFactory.php @@ -0,0 +1,224 @@ +reader->getFirstValueString($api, 'wa:hasFixedAPIKey'); + + if (!isset($apiKey)) { + $apiKeyProperty = $this->reader->getFirstValueString($api, 'wa:usesAPIKeyFrom'); + if (!isset($apiKeyProperty)) + throw new InvalidObjectConfigurationException("An API must have either a fixed API key or a defined API key property."); + $apiKey = $this->reader->getFirstValueString($this->namespace, $apiKeyProperty); + if (!isset($apiKey)) + throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$apiKeyProperty.">."); + } + + $parameter = $this->reader->getFirstValueNode($api, 'wa:usesAuthenticationParameter'); + + if (!isset($parameter) || !$this->reader->hasProperty($parameter, 'wa:hasKey')) + throw new InvalidObjectConfigurationException("The API does not declare a parameter for inserting the API key."); + + $parameterName = $this->reader->getFirstValueString($parameter, 'wa:hasKey'); + + if ($this->reader->hasType($parameter, 'wa:HeaderParameter')) + $clientConfig['headers'][$parameterName] = $apiKey; + + elseif ($this->reader->hasType($parameter, 'wa:QueryParameter')) { + // Guzzle currently doesn't merge query strings from default options and the request itself, + // therefore we're implementing this behavior with a custom middleware + $handler = HandlerStack::create(); + $handler->push(Middleware::mapRequest(function (RequestInterface $request) use ($parameterName, $apiKey) { + $uri = $request->getUri(); + $uri = $uri->withQuery( + (!empty($uri->getQuery()) ? $uri->getQuery().'&' : '') + . urlencode($parameterName).'='.urlencode($apiKey) + ); + return $request->withUri($uri); + })); + $clientConfig['handler'] = $handler; + } + + else + throw new InvalidObjectConfigurationException("The authentication parameter must be either or ."); + + return $clientConfig; + } + + private function configureBearerTokenAuthentication(Node $api, array $clientConfig) { + // see also: https://coid.link/webapis.co-n.net/HTTPBasicAuthentication + + $accessToken = $this->reader->getFirstValueString($api, 'oauth2:hasFixedBearerToken'); + + if (!isset($accessToken)) { + $tokenProperty = $this->reader->getFirstValueString($api, 'oauth2:usesFixedBearerTokenFrom'); + if (!isset($tokenProperty)) + throw new InvalidObjectConfigurationException("An API must have either a fixed access token or a defined token property."); + $accessToken = $this->reader->getFirstValueString($this->namespace, $tokenProperty); + if (!isset($accessToken)) + throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$tokenProperty.">."); + } + + $clientConfig['headers']['Authorization'] = 'Bearer ' . $accessToken; + + return $clientConfig; + } + + private function configureBasicAuthentication(Node $api, array $clientConfig) { + // see also: https://coid.link/webapis.co-n.net/HTTPBasicAuthentication + + $username = $this->reader->getFirstValueString($api, 'wa:hasFixedUsername'); + $password = $this->reader->getFirstValueString($api, 'wa:hasFixedPassword'); + + if (!isset($username)) { + $usernameProperty = $this->reader->getFirstValueString($api, 'wa:usesUsernameFrom'); + if (!isset($usernameProperty)) + throw new InvalidObjectConfigurationException("An API must have either a fixed username or a defined username property."); + $username = $this->reader->getFirstValueString($this->namespace, $usernameProperty); + if (!isset($username)) + throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$usernameProperty.">."); + } + + if (!isset($password)) { + $passwordProperty = $this->reader->getFirstValueString($api, 'wa:usesPasswordFrom'); + if (!isset($passwordProperty)) + throw new InvalidObjectConfigurationException("An API must have either a fixed password or a defined password property."); + $password = $this->reader->getFirstValueString($this->namespace, $passwordProperty); + if (!isset($password)) + throw new InvalidObjectConfigurationException("The namespace does not have a value for <".$passwordProperty.">."); + } + + $clientConfig['auth'] = [$username, $password]; + return $clientConfig; + } + + private function configureSharedSecretBasicAuthentication(Node $api, array $clientConfig) { + // see also: https://coid.link/webapis.co-n.net/SharedSecretAuthenticationViaHTTPBasic + + $username = COIDParser::fromString($this->namespace->getId())->getHost(); + + $apiCoid = COIDParser::fromString($api->getId()); + $providerNamespaceCoid = COIDParser::getNamespaceCOID($apiCoid); + $providerNamespace = $this->objectRetriever->get($providerNamespaceCoid); + $sharedSecret = $this->reader->getAllValuesNode($providerNamespace, 'co:hasSharedSecret'); + if (count($sharedSecret) != 1) + throw new CoreAPIException("Could not retrieve the shared secret."); + + $password = $this->reader->getFirstValueString($sharedSecret[0], 'co:hasTokenValue'); + + $clientConfig['auth'] = [$username, $password]; + return $clientConfig; + } + + private function createClient(Node $api, bool $specificClient = false) { + if (!$this->reader->hasType($api, 'wa:HTTPEndpoint')) + throw new InvalidObjectConfigurationException("The API node must have the type ."); + + $baseUrl = $this->reader->getFirstValueString($api, 'wa:hasBaseURL'); + if (!isset($baseUrl)) + throw new InvalidObjectConfigurationException("The API must have a base URL."); + + $clientConfig = [ + 'base_uri' => $baseUrl, + 'connect_timeout' => self::DEFAULT_CONNECT_TIMEOUT, + 'timeout' => self::DEFAULT_TIMEOUT + ]; + + if ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism', + 'wa:APIKeyAuthentication')) + $clientConfig = $this->configureAPIKeyAuthentication($api, $clientConfig); + + elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism', + 'oauth2:FixedBearerTokenAuthentication')) + $clientConfig = $this->configureBearerTokenAuthentication($api, $clientConfig); + + elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism', + 'wa:HTTPBasicAuthentication')) + $clientConfig = $this->configureBasicAuthentication($api, $clientConfig); + + elseif ($this->reader->hasPropertyValue($api, 'wa:supportsAuthenticationMechanism', + 'wa:SharedSecretAuthenticationViaHTTPBasic')) + $clientConfig = $this->configureSharedSecretBasicAuthentication($api, $clientConfig); + + if ($specificClient == false) + return new Client($clientConfig); + + if ($this->reader->hasType($api, 'wa:GraphQLEndpoint')) { + if (!class_exists('GraphQL\Client')) + throw new Exception("Install the gmostafa/php-graphql-client package to retrieve a specific client for wa:GraphQLEndpoint objects."); + + return new \GraphQL\Client($clientConfig['base_uri'], + isset($clientConfig['headers']) ? $clientConfig['headers'] : []); + } else + return new Client($clientConfig); + } + + /** + * @param ObjectRetriever $objectRetriever An initialized and authenticated object retriever. + * @param IRI|null $namespaceCoid The namespace of the API client. Used to retrieve credentials. If this parameter is not provided, the namespace provided with the "auth_ns" configuration option from the object retriever is used. + */ + public function __construct(ObjectRetriever $objectRetriever, IRI $namespaceCoid = null) { + $this->objectRetriever = $objectRetriever; + $this->namespace = isset($namespaceCoid) + ? $objectRetriever->getObject($namespaceCoid) + : $objectRetriever->getAuthenticatingNamespaceObject(); + + $this->reader = new NodeReader([ + 'prefixes' => [ + 'co' => 'coid://cloudobjects.io/', + 'wa' => 'coid://webapis.co-n.net/', + 'oauth2' => 'coid://oauth2.co-n.net/' + ] + ]); + } + + /** + * Get an API client for the WebAPI with the specified COID. + * + * @param IRI $apiCoid WebAPI COID + * @param boolean $specificClient If TRUE, returns a specific client class based on the API type. If FALSE, always returns a Guzzle client. Defaults to FALSE. + * @return Client + */ + public function getClientWithCOID(IRI $apiCoid, bool $specificClient = false) { + $idString = (string)$apiCoid.(string)$specificClient; + if (!isset($this->apiClients[$idString])) { + $object = $this->objectRetriever->getObject($apiCoid); + if (!isset($object)) + throw new CoreAPIException("Could not retrieve API <".(string)$apiCoid.">."); + $this->apiClients[$idString] = $this->createClient($object, $specificClient); + } + + return $this->apiClients[$idString]; + } + +} \ No newline at end of file diff --git a/LICENSE b/LICENSE index ee6256c..e87a115 100644 --- a/LICENSE +++ b/LICENSE @@ -1,373 +1,363 @@ -Mozilla Public License Version 2.0 -================================== +Mozilla Public License, version 2.0 1. Definitions --------------- 1.1. "Contributor" - means each individual or legal entity that creates, contributes to - the creation of, or owns Covered Software. + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. 1.2. "Contributor Version" - means the combination of the Contributions of others (if any) used - by a Contributor and that particular Contributor's Contribution. + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor's Contribution. 1.3. "Contribution" - means Covered Software of a particular Contributor. + + means Covered Software of a particular Contributor. 1.4. "Covered Software" - means Source Code Form to which the initial Contributor has attached - the notice in Exhibit A, the Executable Form of such Source Code - Form, and Modifications of such Source Code Form, in each case - including portions thereof. + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. 1.5. "Incompatible With Secondary Licenses" - means + means - (a) that the initial Contributor has attached the notice described - in Exhibit B to the Covered Software; or + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or - (b) that the Covered Software was made available under the terms of - version 1.1 or earlier of the License, but not also under the - terms of a Secondary License. + b. that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the terms of + a Secondary License. 1.6. "Executable Form" - means any form of the work other than Source Code Form. + + means any form of the work other than Source Code Form. 1.7. "Larger Work" - means a work that combines Covered Software with other material, in - a separate file or files, that is not Covered Software. + + means a work that combines Covered Software with other material, in a + separate file or files, that is not Covered Software. 1.8. "License" - means this document. + + means this document. 1.9. "Licensable" - means having the right to grant, to the maximum extent possible, - whether at the time of the initial grant or subsequently, any and - all of the rights conveyed by this License. + + means having the right to grant, to the maximum extent possible, whether + at the time of the initial grant or subsequently, any and all of the + rights conveyed by this License. 1.10. "Modifications" - means any of the following: - (a) any file in Source Code Form that results from an addition to, - deletion from, or modification of the contents of Covered - Software; or + means any of the following: - (b) any new file in Source Code Form that contains any Covered - Software. + a. any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. 1.11. "Patent Claims" of a Contributor - means any patent claim(s), including without limitation, method, - process, and apparatus claims, in any patent Licensable by such - Contributor that would be infringed, but for the grant of the - License, by the making, using, selling, offering for sale, having - made, import, or transfer of either its Contributions or its - Contributor Version. + + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the License, + by the making, using, selling, offering for sale, having made, import, + or transfer of either its Contributions or its Contributor Version. 1.12. "Secondary License" - means either the GNU General Public License, Version 2.0, the GNU - Lesser General Public License, Version 2.1, the GNU Affero General - Public License, Version 3.0, or any later versions of those - licenses. + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. 1.13. "Source Code Form" - means the form of the work preferred for making modifications. + + means the form of the work preferred for making modifications. 1.14. "You" (or "Your") - means an individual or a legal entity exercising rights under this - License. For legal entities, "You" includes any entity that - controls, is controlled by, or is under common control with You. For - purposes of this definition, "control" means (a) the power, direct - or indirect, to cause the direction or management of such entity, - whether by contract or otherwise, or (b) ownership of more than - fifty percent (50%) of the outstanding shares or beneficial - ownership of such entity. + + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, "control" means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + 2. License Grants and Conditions --------------------------------- 2.1. Grants -Each Contributor hereby grants You a world-wide, royalty-free, -non-exclusive license: + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: -(a) under intellectual property rights (other than patent or trademark) - Licensable by such Contributor to use, reproduce, make available, - modify, display, perform, distribute, and otherwise exploit its - Contributions, either on an unmodified basis, with Modifications, or - as part of a Larger Work; and + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and -(b) under Patent Claims of such Contributor to make, use, sell, offer - for sale, have made, import, and otherwise transfer either its - Contributions or its Contributor Version. + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. 2.2. Effective Date -The licenses granted in Section 2.1 with respect to any Contribution -become effective for each Contribution on the date the Contributor first -distributes such Contribution. + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. 2.3. Limitations on Grant Scope -The licenses granted in this Section 2 are the only rights granted under -this License. No additional rights or licenses will be implied from the -distribution or licensing of Covered Software under this License. -Notwithstanding Section 2.1(b) above, no patent license is granted by a -Contributor: + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: -(a) for any code that a Contributor has removed from Covered Software; - or + a. for any code that a Contributor has removed from Covered Software; or -(b) for infringements caused by: (i) Your and any other third party's - modifications of Covered Software, or (ii) the combination of its - Contributions with other software (except as part of its Contributor - Version); or + b. for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or -(c) under Patent Claims infringed by Covered Software in the absence of - its Contributions. + c. under Patent Claims infringed by Covered Software in the absence of + its Contributions. -This License does not grant any rights in the trademarks, service marks, -or logos of any Contributor (except as may be necessary to comply with -the notice requirements in Section 3.4). + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). 2.4. Subsequent Licenses -No Contributor makes additional grants as a result of Your choice to -distribute the Covered Software under a subsequent version of this -License (see Section 10.2) or under the terms of a Secondary License (if -permitted under the terms of Section 3.3). + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). 2.5. Representation -Each Contributor represents that the Contributor believes its -Contributions are its original creation(s) or it has sufficient rights -to grant the rights to its Contributions conveyed by this License. + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights to + grant the rights to its Contributions conveyed by this License. 2.6. Fair Use -This License is not intended to limit any rights You have under -applicable copyright doctrines of fair use, fair dealing, or other -equivalents. + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. 2.7. Conditions -Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted -in Section 2.1. + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + 3. Responsibilities -------------------- 3.1. Distribution of Source Form -All distribution of Covered Software in Source Code Form, including any -Modifications that You create or to which You contribute, must be under -the terms of this License. You must inform recipients that the Source -Code Form of the Covered Software is governed by the terms of this -License, and how they can obtain a copy of this License. You may not -attempt to alter or restrict the recipients' rights in the Source Code -Form. + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. 3.2. Distribution of Executable Form -If You distribute Covered Software in Executable Form then: + If You distribute Covered Software in Executable Form then: -(a) such Covered Software must also be made available in Source Code - Form, as described in Section 3.1, and You must inform recipients of - the Executable Form how they can obtain a copy of such Source Code - Form by reasonable means in a timely manner, at a charge no more - than the cost of distribution to the recipient; and + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and -(b) You may distribute such Executable Form under the terms of this - License, or sublicense it under different terms, provided that the - license for the Executable Form does not attempt to limit or alter - the recipients' rights in the Source Code Form under this License. + b. You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter the + recipients' rights in the Source Code Form under this License. 3.3. Distribution of a Larger Work -You may create and distribute a Larger Work under terms of Your choice, -provided that You also comply with the requirements of this License for -the Covered Software. If the Larger Work is a combination of Covered -Software with a work governed by one or more Secondary Licenses, and the -Covered Software is not Incompatible With Secondary Licenses, this -License permits You to additionally distribute such Covered Software -under the terms of such Secondary License(s), so that the recipient of -the Larger Work may, at their option, further distribute the Covered -Software under the terms of either this License or such Secondary -License(s). + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). 3.4. Notices -You may not remove or alter the substance of any license notices -(including copyright notices, patent notices, disclaimers of warranty, -or limitations of liability) contained within the Source Code Form of -the Covered Software, except that You may alter any license notices to -the extent required to remedy known factual inaccuracies. + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, or + limitations of liability) contained within the Source Code Form of the + Covered Software, except that You may alter any license notices to the + extent required to remedy known factual inaccuracies. 3.5. Application of Additional Terms -You may choose to offer, and to charge a fee for, warranty, support, -indemnity or liability obligations to one or more recipients of Covered -Software. However, You may do so only on Your own behalf, and not on -behalf of any Contributor. You must make it absolutely clear that any -such warranty, support, indemnity, or liability obligation is offered by -You alone, and You hereby agree to indemnify every Contributor for any -liability incurred by such Contributor as a result of warranty, support, -indemnity or liability terms You offer. You may include additional -disclaimers of warranty and limitations of liability specific to any -jurisdiction. + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. 4. Inability to Comply Due to Statute or Regulation ---------------------------------------------------- -If it is impossible for You to comply with any of the terms of this -License with respect to some or all of the Covered Software due to -statute, judicial order, or regulation then You must: (a) comply with -the terms of this License to the maximum extent possible; and (b) -describe the limitations and the code they affect. Such description must -be placed in a text file included with all distributions of the Covered -Software under this License. Except to the extent prohibited by statute -or regulation, such description must be sufficiently detailed for a -recipient of ordinary skill to be able to understand it. + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, + judicial order, or regulation then You must: (a) comply with the terms of + this License to the maximum extent possible; and (b) describe the + limitations and the code they affect. Such description must be placed in a + text file included with all distributions of the Covered Software under + this License. Except to the extent prohibited by statute or regulation, + such description must be sufficiently detailed for a recipient of ordinary + skill to be able to understand it. 5. Termination --------------- -5.1. The rights granted under this License will terminate automatically -if You fail to comply with any of its terms. However, if You become -compliant, then the rights granted under this License from a particular -Contributor are reinstated (a) provisionally, unless and until such -Contributor explicitly and finally terminates Your grants, and (b) on an -ongoing basis, if such Contributor fails to notify You of the -non-compliance by some reasonable means prior to 60 days after You have -come back into compliance. Moreover, Your grants from a particular -Contributor are reinstated on an ongoing basis if such Contributor -notifies You of the non-compliance by some reasonable means, this is the -first time You have received notice of non-compliance with this License -from such Contributor, and You become compliant prior to 30 days after -Your receipt of the notice. +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing + basis, if such Contributor fails to notify You of the non-compliance by + some reasonable means prior to 60 days after You have come back into + compliance. Moreover, Your grants from a particular Contributor are + reinstated on an ongoing basis if such Contributor notifies You of the + non-compliance by some reasonable means, this is the first time You have + received notice of non-compliance with this License from such + Contributor, and You become compliant prior to 30 days after Your receipt + of the notice. 5.2. If You initiate litigation against any entity by asserting a patent -infringement claim (excluding declaratory judgment actions, -counter-claims, and cross-claims) alleging that a Contributor Version -directly or indirectly infringes any patent, then the rights granted to -You by any and all Contributors for the Covered Software under Section -2.1 of this License shall terminate. + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. -5.3. In the event of termination under Sections 5.1 or 5.2 above, all -end user license agreements (excluding distributors and resellers) which -have been validly granted by You or Your distributors under this License -prior to termination shall survive termination. +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. -************************************************************************ -* * -* 6. Disclaimer of Warranty * -* ------------------------- * -* * -* Covered Software is provided under this License on an "as is" * -* basis, without warranty of any kind, either expressed, implied, or * -* statutory, including, without limitation, warranties that the * -* Covered Software is free of defects, merchantable, fit for a * -* particular purpose or non-infringing. The entire risk as to the * -* quality and performance of the Covered Software is with You. * -* Should any Covered Software prove defective in any respect, You * -* (not any Contributor) assume the cost of any necessary servicing, * -* repair, or correction. This disclaimer of warranty constitutes an * -* essential part of this License. No use of any Covered Software is * -* authorized under this License except under this disclaimer. * -* * -************************************************************************ +6. Disclaimer of Warranty -************************************************************************ -* * -* 7. Limitation of Liability * -* -------------------------- * -* * -* Under no circumstances and under no legal theory, whether tort * -* (including negligence), contract, or otherwise, shall any * -* Contributor, or anyone who distributes Covered Software as * -* permitted above, be liable to You for any direct, indirect, * -* special, incidental, or consequential damages of any character * -* including, without limitation, damages for lost profits, loss of * -* goodwill, work stoppage, computer failure or malfunction, or any * -* and all other commercial damages or losses, even if such party * -* shall have been informed of the possibility of such damages. This * -* limitation of liability shall not apply to liability for death or * -* personal injury resulting from such party's negligence to the * -* extent applicable law prohibits such limitation. Some * -* jurisdictions do not allow the exclusion or limitation of * -* incidental or consequential damages, so this exclusion and * -* limitation may not apply to You. * -* * -************************************************************************ + Covered Software is provided under this License on an "as is" basis, + without warranty of any kind, either expressed, implied, or statutory, + including, without limitation, warranties that the Covered Software is free + of defects, merchantable, fit for a particular purpose or non-infringing. + The entire risk as to the quality and performance of the Covered Software + is with You. Should any Covered Software prove defective in any respect, + You (not any Contributor) assume the cost of any necessary servicing, + repair, or correction. This disclaimer of warranty constitutes an essential + part of this License. No use of any Covered Software is authorized under + this License except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from + such party's negligence to the extent applicable law prohibits such + limitation. Some jurisdictions do not allow the exclusion or limitation of + incidental or consequential damages, so this exclusion and limitation may + not apply to You. 8. Litigation -------------- -Any litigation relating to this License may be brought only in the -courts of a jurisdiction where the defendant maintains its principal -place of business and such litigation shall be governed by laws of that -jurisdiction, without reference to its conflict-of-law provisions. -Nothing in this Section shall prevent a party's ability to bring -cross-claims or counter-claims. + Any litigation relating to this License may be brought only in the courts + of a jurisdiction where the defendant maintains its principal place of + business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. Nothing + in this Section shall prevent a party's ability to bring cross-claims or + counter-claims. 9. Miscellaneous ----------------- -This License represents the complete agreement concerning the subject -matter hereof. If any provision of this License is held to be -unenforceable, such provision shall be reformed only to the extent -necessary to make it enforceable. Any law or regulation which provides -that the language of a contract shall be construed against the drafter -shall not be used to construe this License against a Contributor. + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides that + the language of a contract shall be construed against the drafter shall not + be used to construe this License against a Contributor. + 10. Versions of the License ---------------------------- 10.1. New Versions -Mozilla Foundation is the license steward. Except as provided in Section -10.3, no one other than the license steward has the right to modify or -publish new versions of this License. Each version will be given a -distinguishing version number. + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. 10.2. Effect of New Versions -You may distribute the Covered Software under the terms of the version -of the License under which You originally received the Covered Software, -or under the terms of any subsequent version published by the license -steward. + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. 10.3. Modified Versions -If you create software not governed by this License, and you want to -create a new license for such software, you may create and use a -modified version of this License if you rename the license and remove -any references to the name of the license steward (except to note that -such modified license differs from this License). + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). 10.4. Distributing Source Code Form that is Incompatible With Secondary -Licenses - -If You choose to distribute Source Code Form that is Incompatible With -Secondary Licenses under the terms of this version of the License, the -notice described in Exhibit B of this License must be attached. + Licenses If You choose to distribute Source Code Form that is + Incompatible With Secondary Licenses under the terms of this version of + the License, the notice described in Exhibit B of this License must be + attached. Exhibit A - Source Code Form License Notice -------------------------------------------- - This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. -If it is not possible or desirable to put the notice in a particular -file, then You may include the notice in a location (such as a LICENSE -file in a relevant directory) where a recipient would be likely to look -for such a notice. +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a +notice. You may add additional accurate notices of copyright ownership. Exhibit B - "Incompatible With Secondary Licenses" Notice ---------------------------------------------------------- - This Source Code Form is "Incompatible With Secondary Licenses", as - defined by the Mozilla Public License, v. 2.0. + This Source Code Form is "Incompatible + With Secondary Licenses", as defined by + the Mozilla Public License, v. 2.0. + diff --git a/README.md b/README.md index e389ead..ff0d961 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,66 @@ -# CloudObjects-PHP-SDK +# CloudObjects PHP SDK +[![Latest Stable Version](https://poser.pugx.org/cloudobjects/sdk/v/stable)](https://packagist.org/packages/cloudobjects/sdk) [![Total Downloads](https://poser.pugx.org/cloudobjects/sdk/downloads)](https://packagist.org/packages/cloudobjects/sdk) + +The CloudObjects PHP SDK provides simple access to [CloudObjects](https://cloudobjects.io/) from PHP-based applications. It wraps the [Object API](https://coid.link/cloudobjects.io/ObjectAPI/1.0) to fetch objects from the CloudObjects Core database and provides object-based access to their RDF description. A two-tiered caching mechanism (in-memory and Doctrine cache drivers) is included. The SDK also contains a helper class to validate COIDs. + +## Installation + +The SDK is [distributed through packagist](https://packagist.org/packages/cloudobjects/sdk). Add `cloudobjects/sdk` to the `require` section of your `composer.json`, like this: + +````json +{ + "require": { + "cloudobjects/sdk" : ">=0.7" + } +} +```` + +## Retrieving Objects + +In order to retrieve objects from the CloudObjects Core database you need to create an instance of `CloudObjects\SDK\ObjectRetriever`. Then you can call `getObject()`. This method returns an `ML\JsonLD\Node` instance or `null` if the object is not found. You can use the object interface of the [JsonLD library](https://github.com/lanthaler/JsonLD/) to read the information from the object. + +Here's a simple example: + +````php +use ML\IRI\IRI; +use CloudObjects\SDK\ObjectRetriever; + +/* ... */ + +$retriever = new ObjectRetriever(); +$object = $this->retriever->getObject(new IRI('coid://cloudobjects.io')); +if (isset($object)) + echo $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue(); +else + echo "Object not found."; +```` + +### Configuration + +You can pass an array of configuration options to the ObjectRetriever's constructor: + +| Option | Description | Default | +|---|---|---| +| `cache_provider` | The type of cache used. Currently supports `redis`, `file` and `none`. | `none` | +| `cache_prefix` | A prefix used for cache IDs. Normally this should not be set but might be necessary on shared caches. | `clobj:` | +| `cache_ttl` | Determines how long objects can remain cached. | `60` | +| `auth_ns` | The namespace of the service that this retriever acts for. If not set the API is accessed anonymously. | `null` | +| `auth_secret` | The shared secret between the namespace in `auth_ns` and `cloudobjects.io` for authenticated. If not set the API is accessed anonymously. | `null` | + +#### For `redis` cache: + +| Option | Description | Default | +|---|---|---| +| `cache_provider.redis.host` | The hostname or IP of the Redis instance. | `127.0.0.1` | +| `cache_provider.redis.port` | The port number of the Redis instance. | `6379` | + +#### For `file` cache: + +| Option | Description | Default | +|---|---|---| +| `cache_provider.file.directory` | The directory to store cache data in. | The system's temporary directory. | + +## License + +The PHP SDK is licensed under Mozilla Public License (see LICENSE file). \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..27e3cbd --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "cloudobjects/sdk", + "description": "CloudObjects SDK for PHP for working with COIDs and object descriptions from CloudObjects.", + "keywords": ["cloudobjects", "sdk"], + "homepage": "https://github.com/CloudObjects/CloudObjects-PHP-SDK", + "license": "MPL-2.0", + "require" : { + "ml/json-ld": ">=1.0.7", + "doctrine/common" : ">=2.6.1", + "doctrine/cache" : "1.*", + "guzzlehttp/guzzle" : ">=6.0", + "psr/log": "^1.1", + "kevinrob/guzzle-cache-middleware": "^3.2", + "webmozart/assert": "^1.6" + }, + "authors": [ + { + "name": "Lukas Rosenstock" + } + ], + "autoload": { + "psr-0": { + "CloudObjects\\SDK" : "" + } + }, + "require-dev" : { + "phpunit/phpunit": ">=4.8.0,<5.0", + "symfony/http-foundation" : ">=4.0", + "symfony/psr-http-message-bridge" : ">=1.1.0", + "zendframework/zend-diactoros" : "~1.8.6", + "defuse/php-encryption" : "^2.2" + }, + "suggest" : { + "symfony/http-foundation" : "Required to use parseSymfonyRequest() in AccountContext.", + "symfony/psr-http-message-bridge" : "Required to use parseSymfonyRequest() in AccountContext.", + "zendframework/zend-diactoros" : "Required to use parseSymfonyRequest() in AccountContext.", + "defuse/php-encryption": "Required to use CryptoHelper" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..ffb4c76 --- /dev/null +++ b/composer.lock @@ -0,0 +1,3206 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "8beed6f1075dabd5329a022f3e9a5d83", + "packages": [ + { + "name": "doctrine/annotations", + "version": "1.13.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", + "reference": "e6e7b7d5b45a2f2abc5460cc6396480b2b1d321f", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "ext-tokenizer": "*", + "php": "^7.1 || ^8.0", + "psr/cache": "^1 || ^2 || ^3" + }, + "require-dev": { + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^6.0 || ^8.1", + "phpstan/phpstan": "^0.12.20", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", + "symfony/cache": "^4.4 || ^5.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "https://www.doctrine-project.org/projects/annotations.html", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.13.1" + }, + "time": "2021-05-16T18:07:53+00:00" + }, + { + "name": "doctrine/cache", + "version": "1.11.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "163074496dc7c3c7b8ccbf3d4376c0187424ed81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/163074496dc7c3c7b8ccbf3d4376c0187424ed81", + "reference": "163074496dc7c3c7b8ccbf3d4376c0187424ed81", + "shasum": "" + }, + "require": { + "php": "~7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4", + "psr/cache": ">=3" + }, + "require-dev": { + "alcaeus/mongo-php-adapter": "^1.1", + "cache/integration-tests": "dev-master", + "doctrine/coding-standard": "^8.0", + "mongodb/mongodb": "^1.1", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0", + "predis/predis": "~1.0", + "psr/cache": "^1.0 || ^2.0", + "symfony/cache": "^4.4 || ^5.2" + }, + "suggest": { + "alcaeus/mongo-php-adapter": "Required to use legacy MongoDB driver" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", + "homepage": "https://www.doctrine-project.org/projects/cache.html", + "keywords": [ + "abstraction", + "apcu", + "cache", + "caching", + "couchdb", + "memcached", + "php", + "redis", + "xcache" + ], + "support": { + "issues": "https://github.com/doctrine/cache/issues", + "source": "https://github.com/doctrine/cache/tree/1.11.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", + "type": "tidelift" + } + ], + "time": "2021-05-18T16:45:32+00:00" + }, + { + "name": "doctrine/collections", + "version": "1.6.7", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "reference": "55f8b799269a1a472457bd1a41b4f379d4cfba4a", + "shasum": "" + }, + "require": { + "php": "^7.1.3 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", + "keywords": [ + "array", + "collections", + "iterators", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/collections/issues", + "source": "https://github.com/doctrine/collections/tree/1.6.7" + }, + "time": "2020-07-27T17:53:49+00:00" + }, + { + "name": "doctrine/common", + "version": "3.1.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "a036d90c303f3163b5be8b8fde9b6755b2be4a3a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/a036d90c303f3163b5be8b8fde9b6755b2be4a3a", + "reference": "a036d90c303f3163b5be8b8fde9b6755b2be4a3a", + "shasum": "" + }, + "require": { + "doctrine/persistence": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0 || ^8.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.5.20 || ^8.5 || ^9.0", + "squizlabs/php_codesniffer": "^3.0", + "symfony/phpunit-bridge": "^4.0.5", + "vimeo/psalm": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "PHP Doctrine Common project is a library that provides additional functionality that other Doctrine projects depend on such as better reflection support, proxies and much more.", + "homepage": "https://www.doctrine-project.org/projects/common.html", + "keywords": [ + "common", + "doctrine", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/common/issues", + "source": "https://github.com/doctrine/common/tree/3.1.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcommon", + "type": "tidelift" + } + ], + "time": "2021-02-10T20:18:51+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v0.5.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/9504165960a1f83cc1480e2be1dd0a0478561314", + "reference": "9504165960a1f83cc1480e2be1dd0a0478561314", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0|^7.0|^8.0", + "phpunit/phpunit": "^7.0|^8.0|^9.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v0.5.3" + }, + "time": "2021-03-21T12:59:47+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/41370af6a30faa9dc0368c4a6814d596e81aba7f", + "reference": "41370af6a30faa9dc0368c4a6814d596e81aba7f", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/common": "<2.9@dev" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/1.1.x" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2020-05-29T18:28:51+00:00" + }, + { + "name": "doctrine/lexer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e864bbf5904cb8f5bb334f99209b48018522f042", + "reference": "e864bbf5904cb8f5bb334f99209b48018522f042", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", + "keywords": [ + "annotations", + "docblock", + "lexer", + "parser", + "php" + ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Flexer", + "type": "tidelift" + } + ], + "time": "2020-05-25T17:44:05+00:00" + }, + { + "name": "doctrine/persistence", + "version": "2.2.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "d138f3ab5f761055cab1054070377cfd3222e368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/d138f3ab5f761055cab1054070377cfd3222e368", + "reference": "d138f3ab5f761055cab1054070377cfd3222e368", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/collections": "^1.0", + "doctrine/deprecations": "^0.5.3", + "doctrine/event-manager": "^1.0", + "php": "^7.1 || ^8.0", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "conflict": { + "doctrine/common": "<2.10@dev" + }, + "require-dev": { + "composer/package-versions-deprecated": "^1.11", + "doctrine/coding-standard": "^6.0 || ^9.0", + "doctrine/common": "^3.0", + "phpstan/phpstan": "0.12.84", + "phpunit/phpunit": "^7.5.20 || ^8.0 || ^9.0", + "symfony/cache": "^4.4|^5.0", + "vimeo/psalm": "4.7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common", + "Doctrine\\Persistence\\": "lib/Doctrine/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/2.2.1" + }, + "time": "2021-05-19T07:07:01+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.3.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7008573787b430c1c1f650e3722d9bba59967628" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628", + "reference": "7008573787b430c1c1f650e3722d9bba59967628", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.4", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "ext-curl": "*", + "php-http/client-integration-tests": "^3.0", + "phpunit/phpunit": "^8.5.5 || ^9.3.5", + "psr/log": "^1.1" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "7.3-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.3.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://github.com/alexeyshockov", + "type": "github" + }, + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2021-03-23T11:33:13+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4 || ^5.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/1.4.1" + }, + "time": "2021-03-07T09:25:29+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.8.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91", + "reference": "dc960a912984efb74d0a90222870c72c87f10c91", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.7-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/1.8.2" + }, + "time": "2021-04-26T09:17:50+00:00" + }, + { + "name": "kevinrob/guzzle-cache-middleware", + "version": "v3.3.1", + "source": { + "type": "git", + "url": "https://github.com/Kevinrob/guzzle-cache-middleware.git", + "reference": "f978b8da7484a16e26589a5518d6bacc6ccdee99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Kevinrob/guzzle-cache-middleware/zipball/f978b8da7484a16e26589a5518d6bacc6ccdee99", + "reference": "f978b8da7484a16e26589a5518d6bacc6ccdee99", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "cache/array-adapter": "^0.4 || ^0.5 || ^1.0", + "cache/simple-cache-bridge": "^0.1 || ^1.0", + "doctrine/cache": "^1.0", + "guzzlehttp/guzzle": "^6.0", + "illuminate/cache": "^5.0", + "league/flysystem": "^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.0", + "psr/cache": "^1.0", + "symfony/phpunit-bridge": "^4.4 || ^5.0" + }, + "suggest": { + "doctrine/cache": "This library has a lot of ready-to-use cache storage (to be used with Kevinrob\\GuzzleCache\\Storage\\DoctrineCacheStorage).", + "guzzlehttp/guzzle": "For using this library. It was created for Guzzle6 (but you can use it with any PSR-7 HTTP client).", + "laravel/framework": "To be used with Kevinrob\\GuzzleCache\\Storage\\LaravelCacheStorage", + "league/flysystem": "To be used with Kevinrob\\GuzzleCache\\Storage\\FlysystemStorage", + "psr/cache": "To be used with Kevinrob\\GuzzleCache\\Storage\\Psr6CacheStorage", + "psr/simple-cache": "To be used with Kevinrob\\GuzzleCache\\Storage\\Psr16CacheStorage" + }, + "type": "library", + "autoload": { + "psr-4": { + "Kevinrob\\GuzzleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kevin Robatel", + "email": "kevinrob2@gmail.com", + "homepage": "https://github.com/Kevinrob" + } + ], + "description": "A HTTP/1.1 Cache for Guzzle 6. It's a simple Middleware to be added in the HandlerStack. (RFC 7234)", + "homepage": "https://github.com/Kevinrob/guzzle-cache-middleware", + "keywords": [ + "Etag", + "Flysystem", + "Guzzle", + "cache", + "cache-control", + "doctrine", + "expiration", + "guzzle6", + "handler", + "http", + "http 1.1", + "middleware", + "performance", + "php", + "promise", + "psr6", + "psr7", + "rfc7234", + "validation" + ], + "support": { + "issues": "https://github.com/Kevinrob/guzzle-cache-middleware/issues", + "source": "https://github.com/Kevinrob/guzzle-cache-middleware/tree/v3.3.1" + }, + "time": "2020-02-14T11:17:02+00:00" + }, + { + "name": "ml/iri", + "version": "1.1.4", + "target-dir": "ML/IRI", + "source": { + "type": "git", + "url": "https://github.com/lanthaler/IRI.git", + "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lanthaler/IRI/zipball/cbd44fa913e00ea624241b38cefaa99da8d71341", + "reference": "cbd44fa913e00ea624241b38cefaa99da8d71341", + "shasum": "" + }, + "require": { + "lib-pcre": ">=4.0", + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "ML\\IRI": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Markus Lanthaler", + "email": "mail@markus-lanthaler.com", + "homepage": "http://www.markus-lanthaler.com", + "role": "Developer" + } + ], + "description": "IRI handling for PHP", + "homepage": "http://www.markus-lanthaler.com", + "keywords": [ + "URN", + "iri", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/lanthaler/IRI/issues", + "source": "https://github.com/lanthaler/IRI/tree/master" + }, + "time": "2014-01-21T13:43:39+00:00" + }, + { + "name": "ml/json-ld", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/lanthaler/JsonLD.git", + "reference": "c74a1aed5979ed1cfb1be35a55a305fd30e30b93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/lanthaler/JsonLD/zipball/c74a1aed5979ed1cfb1be35a55a305fd30e30b93", + "reference": "c74a1aed5979ed1cfb1be35a55a305fd30e30b93", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ml/iri": "^1.1.1", + "php": ">=5.3.0" + }, + "require-dev": { + "json-ld/tests": "1.0", + "phpunit/phpunit": "^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ML\\JsonLD\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Markus Lanthaler", + "email": "mail@markus-lanthaler.com", + "homepage": "http://www.markus-lanthaler.com", + "role": "Developer" + } + ], + "description": "JSON-LD Processor for PHP", + "homepage": "http://www.markus-lanthaler.com", + "keywords": [ + "JSON-LD", + "jsonld" + ], + "support": { + "issues": "https://github.com/lanthaler/JsonLD/issues", + "source": "https://github.com/lanthaler/JsonLD/tree/1.2.0" + }, + "time": "2020-06-16T17:45:06+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, + "time": "2020-06-29T06:28:15+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/c6c942b1ac76c82448322025e084cadc56048b4e", + "reference": "c6c942b1ac76c82448322025e084cadc56048b4e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25", + "reference": "6964c76c7804814a842473e0c8fd15bab0f18e25", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.10.0" + }, + "time": "2021-03-09T10:59:23+00:00" + } + ], + "packages-dev": [ + { + "name": "defuse/php-encryption", + "version": "v2.3.1", + "source": { + "type": "git", + "url": "https://github.com/defuse/php-encryption.git", + "reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/defuse/php-encryption/zipball/77880488b9954b7884c25555c2a0ea9e7053f9d2", + "reference": "77880488b9954b7884c25555c2a0ea9e7053f9d2", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "paragonie/random_compat": ">= 2", + "php": ">=5.6.0" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5|^6|^7|^8|^9" + }, + "bin": [ + "bin/generate-defuse-key" + ], + "type": "library", + "autoload": { + "psr-4": { + "Defuse\\Crypto\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Hornby", + "email": "taylor@defuse.ca", + "homepage": "https://defuse.ca/" + }, + { + "name": "Scott Arciszewski", + "email": "info@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "Secure PHP Encryption Library", + "keywords": [ + "aes", + "authenticated encryption", + "cipher", + "crypto", + "cryptography", + "encrypt", + "encryption", + "openssl", + "security", + "symmetric key cryptography" + ], + "support": { + "issues": "https://github.com/defuse/php-encryption/issues", + "source": "https://github.com/defuse/php-encryption/tree/v2.3.1" + }, + "time": "2021-04-09T23:57:26+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b", + "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^8.0", + "ext-pdo": "*", + "ext-phar": "*", + "phpbench/phpbench": "^0.13 || 1.0.0-alpha2", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-phpunit": "^0.12", + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "https://ocramius.github.io/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "keywords": [ + "constructor", + "instantiate" + ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.4.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", + "type": "tidelift" + } + ], + "time": "2020-11-10T18:47:58+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, + "time": "2020-06-27T09:03:43+00:00" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "5.2.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/069a785b2141f5bcf49f3e353548dc1cce6df556", + "reference": "069a785b2141f5bcf49f3e353548dc1cce6df556", + "shasum": "" + }, + "require": { + "ext-filter": "*", + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.3", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/master" + }, + "time": "2020-09-03T19:13:55+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "reference": "6a467b8989322d92aa1c8bf2bebcc6e5c2ba55c0", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0", + "phpdocumentor/reflection-common": "^2.0" + }, + "require-dev": { + "ext-tokenizer": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.4.0" + }, + "time": "2020-09-17T18:55:26+00:00" + }, + { + "name": "phpspec/prophecy", + "version": "v1.10.3", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "451c3cd1418cf640de218914901e51b064abb093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.5 || ^3.2", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Prophecy\\": "src/Prophecy" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, + "time": "2020-03-05T15:02:03+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "~1.3", + "sebastian/environment": "^1.3.2", + "sebastian/version": "~1.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "~4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.2.1", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/2.2" + }, + "time": "2015-10-06T15:47:00+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", + "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + }, + "time": "2017-11-27T13:52:08+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, + "time": "2015-06-21T13:50:34+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + }, + "time": "2017-02-26T11:10:40+00:00" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.12", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1ce90ba27c42e4e44e6d8458241466380b51fa16", + "reference": "1ce90ba27c42e4e44e6d8458241466380b51fa16", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/1.4" + }, + "abandoned": true, + "time": "2017-12-04T08:55:13+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "4.8.36", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/46023de9a91eec7dfb06cc56cb4e260017298517", + "reference": "46023de9a91eec7dfb06cc56cb4e260017298517", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "php": ">=5.3.3", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "~2.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "~2.3", + "sebastian/comparator": "~1.2.2", + "sebastian/diff": "~1.2", + "sebastian/environment": "~1.3", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/version": "~1.0", + "symfony/yaml": "~2.1|~3.0" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.8.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/4.8.36" + }, + "time": "2017-06-21T08:07:12+00:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "2.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": ">=5.3.3", + "phpunit/php-text-template": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", + "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/2.3" + }, + "abandoned": true, + "time": "2015-10-02T06:51:40+00:00" + }, + { + "name": "sebastian/comparator", + "version": "1.2.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2 || ~2.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + }, + "time": "2017-01-29T09:50:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "1.4.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "reference": "7f066a26a962dbe58ddea9f72a4e82874a3975a4", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + }, + "time": "2017-05-22T07:24:03+00:00" + }, + { + "name": "sebastian/environment", + "version": "1.3.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/1.3" + }, + "time": "2016-08-18T05:49:44+00:00" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/master" + }, + "time": "2016-06-17T09:04:28+00:00" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + }, + "time": "2015-10-12T03:26:01+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + }, + "time": "2016-10-03T07:41:43+00:00" + }, + { + "name": "sebastian/version", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", + "shasum": "" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/1.0.6" + }, + "time": "2015-06-21T13:59:46+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v2.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "reference": "5f38c8804a9e97d23e0c8d63341088cd8a22d627", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-03-23T23:28:01+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v5.2.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e8fbbab7c4a71592985019477532629cb2e142dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8fbbab7c4a71592985019477532629cb2e142dc", + "reference": "e8fbbab7c4a71592985019477532629cb2e142dc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/cache": "^4.4|^5.0", + "symfony/expression-language": "^4.4|^5.0", + "symfony/mime": "^4.4|^5.0" + }, + "suggest": { + "symfony/mime": "To use the file extension guesser" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Defines an object-oriented layer for the HTTP specification", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/http-foundation/tree/v5.2.8" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-07T13:41:16+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/5232de97ee3b75b0360528dae24e73db49566ab1", + "reference": "5232de97ee3b75b0360528dae24e73db49566ab1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-22T09:19:47+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.22.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dc3063ba22c2a1fd2f45ed856374d79114998f91", + "reference": "dc3063ba22c2a1fd2f45ed856374d79114998f91", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.22-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.22.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-01-07T16:49:33+00:00" + }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "81db2d4ae86e9f0049828d9343a72b9523884e5d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/81db2d4ae86e9f0049828d9343a72b9523884e5d", + "reference": "81db2d4ae86e9f0049828d9343a72b9523884e5d", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0", + "symfony/http-foundation": "^4.4 || ^5.0" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "psr/log": "^1.1", + "symfony/browser-kit": "^4.4 || ^5.0", + "symfony/config": "^4.4 || ^5.0", + "symfony/event-dispatcher": "^4.4 || ^5.0", + "symfony/framework-bundle": "^4.4 || ^5.0", + "symfony/http-kernel": "^4.4 || ^5.0", + "symfony/phpunit-bridge": "^4.4.19 || ^5.2" + }, + "suggest": { + "nyholm/psr7": "For a super lightweight PSR-7/17 implementation" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-main": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "http://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/symfony/psr-http-message-bridge/issues", + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v2.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-02-17T10:35:25+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.4.47", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "88289caa3c166321883f67fe5130188ebbb47094" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/88289caa3c166321883f67fe5130188ebbb47094", + "reference": "88289caa3c166321883f67fe5130188ebbb47094", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/console": "<3.4" + }, + "require-dev": { + "symfony/console": "~3.4|~4.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v3.4.47" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2020-10-24T10:57:07+00:00" + }, + { + "name": "zendframework/zend-diactoros", + "version": "1.8.7", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-diactoros.git", + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "reference": "a85e67b86e9b8520d07e6415fcbcb8391b44a75b", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-dom": "*", + "ext-libxml": "*", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", + "zendframework/zend-coding-standard": "~1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-release-1.8": "1.8.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], + "psr-4": { + "Zend\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://github.com/zendframework/zend-diactoros", + "keywords": [ + "http", + "psr", + "psr-7" + ], + "support": { + "issues": "https://github.com/zendframework/zend-diactoros/issues", + "source": "https://github.com/zendframework/zend-diactoros" + }, + "abandoned": "laminas/laminas-diactoros", + "time": "2019-08-06T17:53:53+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/makefile b/makefile new file mode 100644 index 0000000..18108b7 --- /dev/null +++ b/makefile @@ -0,0 +1,23 @@ +.PHONY: all + +all: vendor build + +composer.lock: composer.json + # Updating Dependencies with Composer + composer update -o + +vendor: composer.lock + # Installing Dependencies with Composer + composer install -o + +sami.phar: + # Get a copy of sami (only on PHP 7) + @if [ `php -v | awk '{ if ($$1 == "PHP") { print substr($$2,0,1) }}'` = "7" ]; then\ + wget http://get.sensiolabs.org/sami.phar;\ + fi + +build: sami.phar + # Building documentation with sami.phar (only on PHP 7) + @if [ `php -v | awk '{ if ($$1 == "PHP") { print substr($$2,0,1) }}'` = "7" ]; then\ + php sami.phar update sami-config.php --force;\ + fi \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..4d56644 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,20 @@ + + + + + ./tests/OfflineTests/ + + + ./tests/OnlineTests/ + + + diff --git a/tests/OfflineTests/AccountGateway/AAUIDParserTest.php b/tests/OfflineTests/AccountGateway/AAUIDParserTest.php new file mode 100644 index 0000000..e82eef1 --- /dev/null +++ b/tests/OfflineTests/AccountGateway/AAUIDParserTest.php @@ -0,0 +1,63 @@ +assertEquals(AAUIDParser::AAUID_ACCOUNT, AAUIDParser::getType($aauid)); + $this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid)); + } + + public function testInvalidAccountAAUID() { + $aauid = new IRI('aauid:abcd1234abcd123'); + $this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid)); + $this->assertNull(AAUIDParser::getAAUID($aauid)); + } + + public function testValidAccountConnectionAAUID() { + $aauid = new IRI('aauid:abcd1234abcd1234:connection:AA'); + $this->assertEquals(AAUIDParser::AAUID_CONNECTION, AAUIDParser::getType($aauid)); + $this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid)); + $this->assertEquals('AA', AAUIDParser::getQualifier($aauid)); + } + + public function testInvalidAccountConnectionAAUID() { + $aauid = new IRI('aauid:abcd1234abcd1234:connection:AAA'); + $this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid)); + $this->assertNull(AAUIDParser::getAAUID($aauid)); + $this->assertNull(AAUIDParser::getQualifier($aauid)); + } + + public function testValidConnectedAccountAAUID() { + $aauid = new IRI('aauid:abcd1234abcd1234:account:AA'); + $this->assertEquals(AAUIDParser::AAUID_CONNECTED_ACCOUNT, AAUIDParser::getType($aauid)); + $this->assertEquals('abcd1234abcd1234', AAUIDParser::getAAUID($aauid)); + $this->assertEquals('AA', AAUIDParser::getQualifier($aauid)); + } + + public function testInvalidConnectedAccountAAUID() { + $aauid = new IRI('aauid:abcd1234abcd1234:account:X9'); + $this->assertEquals(AAUIDParser::AAUID_INVALID, AAUIDParser::getType($aauid)); + $this->assertNull(AAUIDParser::getAAUID($aauid)); + $this->assertNull(AAUIDParser::getQualifier($aauid)); + } + + public function testFromStringValid() { + $aauid1 = new IRI('aauid:5678defg8765gfed'); + $aauid2 = AAUIDParser::fromString('aauid:5678defg8765gfed'); + $this->assertEquals($aauid1, $aauid2); + + $aauid1 = new IRI('aauid:5678defg8765gfed'); + $aauid2 = AAUIDParser::fromString('5678defg8765gfed'); + $this->assertEquals($aauid1, $aauid2); + } + +} diff --git a/tests/OfflineTests/AccountGateway/AccountContextParseTest.php b/tests/OfflineTests/AccountGateway/AccountContextParseTest.php new file mode 100644 index 0000000..c40111b --- /dev/null +++ b/tests/OfflineTests/AccountGateway/AccountContextParseTest.php @@ -0,0 +1,34 @@ + '1234123412341234', 'C-Access-Token' => 'test' + ]); + + $context = AccountContext::fromPsrRequest($request); + $this->assertNotNull($context); + $this->assertEquals('1234123412341234', AAUIDParser::getAAUID($context->getAAUID())); + } + + public function testParseSymfonyRequest() { + $request = SymfonyRequest::create('/', 'GET', [], [], [], [ + 'HTTP_C_AAUID' => '1234123412341234', 'HTTP_C_ACCESS_TOKEN' => 'test' + ]); + + $context = AccountContext::fromSymfonyRequest($request); + $this->assertNotNull($context); + $this->assertEquals('1234123412341234', AAUIDParser::getAAUID($context->getAAUID())); + } + +} diff --git a/tests/OfflineTests/AccountGateway/AccountContextTest.php b/tests/OfflineTests/AccountGateway/AccountContextTest.php new file mode 100644 index 0000000..d7d08c7 --- /dev/null +++ b/tests/OfflineTests/AccountGateway/AccountContextTest.php @@ -0,0 +1,33 @@ +context = new AccountContext(new IRI('aauid:aaaabbbbccccdddd'), 'DUMMY'); + } + + public function testDefaultGatewayBaseURL() { + $this->assertEquals('https://aaaabbbbccccdddd.aauid.net', $this->context->getClient()->getConfig('base_uri')); + } + + public function testSetAccountGatewayBaseURLTemplateWithPlaceholder() { + $this->context->setAccountGatewayBaseURLTemplate('http://{aauid}.localhost'); + $this->assertEquals('http://aaaabbbbccccdddd.localhost', $this->context->getClient()->getConfig('base_uri')); + } + + public function testSetAccountGatewayBaseURLTemplateWithoutPlaceholder() { + $this->context->setAccountGatewayBaseURLTemplate('http://localhost'); + $this->assertEquals('http://localhost', $this->context->getClient()->getConfig('base_uri')); + } + +} diff --git a/tests/OfflineTests/COIDParserTest.php b/tests/OfflineTests/COIDParserTest.php new file mode 100644 index 0000000..97a00eb --- /dev/null +++ b/tests/OfflineTests/COIDParserTest.php @@ -0,0 +1,130 @@ +assertEquals(COIDParser::COID_ROOT, COIDParser::getType($coid)); + } + + public function testInvalidRootCOID() { + $coid = new IRI('coid://example'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://exämple.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://ex&mple.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + } + + public function testInvalidCOID() { + $coid = new IRI('http://example.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('example.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('COID://example.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('Coid://example.com'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://EXAMPLE.COM'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://exAMPle.CoM'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + } + + public function testUnversionedCOID() { + $coid = new IRI('coid://example.com/Example'); + $this->assertEquals(COIDParser::COID_UNVERSIONED, COIDParser::getType($coid)); + } + + public function testInvalidUnversionedCOID() { + $coid = new IRI('coid://example.com/Exümple'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://example.com/Examp%e'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + } + + public function testVersionedCOID() { + $coid = new IRI('coid://example.com/Example/1.0'); + $this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid)); + $coid = new IRI('coid://example.com/Example/alpha'); + $this->assertEquals(COIDParser::COID_VERSIONED, COIDParser::getType($coid)); + } + + public function testInvalidVersionedCOID() { + $coid = new IRI('coid://example.com/Example/1.$'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + } + + public function testVersionWildcardCOID() { + $coid = new IRI('coid://example.com/Example/^1.0'); + $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); + $coid = new IRI('coid://example.com/Example/~1.0'); + $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); + $coid = new IRI('coid://example.com/Example/1.*'); + $this->assertEquals(COIDParser::COID_VERSION_WILDCARD, COIDParser::getType($coid)); + } + + public function testInvalidVersionWildcardCOID() { + $coid = new IRI('coid://example.com/Example/^1.*'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + $coid = new IRI('coid://example.com/Example/1.a.*'); + $this->assertEquals(COIDParser::COID_INVALID, COIDParser::getType($coid)); + } + + public function testIRICaseSensitivity() { + $coid1 = new IRI('coid://example.com/example/1.0'); + $coid2 = new IRI('coid://example.com/Example/1.0'); + $this->assertFalse($coid1->equals($coid2)); + } + + public function testRootFromString() { + $coid1 = new IRI('coid://example.com'); + $coid2 = COIDParser::fromString('coid://example.com'); + $coid3 = COIDParser::fromString('example.com'); + $this->assertTrue($coid1->equals($coid2)); + $this->assertTrue($coid1->equals($coid3)); + } + + public function testUnversionedFromString() { + $coid1 = new IRI('coid://example.com/Example'); + $coid2 = COIDParser::fromString('coid://example.com/Example'); + $coid3 = COIDParser::fromString('example.com/Example'); + $this->assertTrue($coid1->equals($coid2)); + $this->assertTrue($coid1->equals($coid3)); + } + + public function testVersionedFromString() { + $coid1 = new IRI('coid://example.com/Example/1.0'); + $coid2 = COIDParser::fromString('coid://example.com/Example/1.0'); + $coid3 = COIDParser::fromString('example.com/Example/1.0'); + $this->assertTrue($coid1->equals($coid2)); + $this->assertTrue($coid1->equals($coid3)); + } + + public function testNormalizeRootFromString() { + $coid1 = new IRI('coid://example.com'); + $coid2 = COIDParser::fromString('COID://example.com'); + $coid3 = COIDParser::fromString('ExAmple.COM'); + $this->assertTrue($coid1->equals($coid2)); + $this->assertTrue($coid1->equals($coid3)); + } + + public function testNormalizeNonRootFromString() { + $coid1 = new IRI('coid://example.com/Example'); + $coid2 = COIDParser::fromString('COID://example.com/Example'); + $coid3 = COIDParser::fromString('ExAmple.COM/Example'); + $coid4 = COIDParser::fromString('ExAmple.COM/EXample'); + $this->assertTrue($coid1->equals($coid2)); + $this->assertTrue($coid1->equals($coid3)); + $this->assertFalse($coid1->equals($coid4)); + } + +} diff --git a/tests/OfflineTests/Common/CryptoHelperTest.php b/tests/OfflineTests/Common/CryptoHelperTest.php new file mode 100644 index 0000000..2ef9e07 --- /dev/null +++ b/tests/OfflineTests/Common/CryptoHelperTest.php @@ -0,0 +1,46 @@ +retriever->setClient(new Client(['handler' => $handler])); + } + + public function setUp() { + $this->retriever = new ObjectRetriever([ + 'auth_ns' => 'test.cloudobjects.io', + 'auth_secret' => 'TEST' + ]); + } + + public function testEncryptDecrypt() { + $this->setMockResponse(new Response(200, + [ 'Content-Type' => 'application/ld+json' ], + '{"@context":{"common":"coid:\/\/common.cloudobjects.io\/"},"@id":"coid:\/\/test.cloudobjects.io","common:usesSharedEncryptionKey": "def0000092c63296feb07f6b44f323351ab2e570fb04c2dff73c3119fd1103234ea03f5af094d33e8fb5122c5cf73f745957a5f8f47b4fc3c43bc86fb631969f4c591831"}')); + + $cryptoHelper = new CryptoHelper($this->retriever); + + $cleartext = "CLEARTEXT"; + $ciphertext = $cryptoHelper->encryptWithSharedEncryptionKey($cleartext); + $encryptedDecryptedText = $cryptoHelper->decryptWithSharedEncryptionKey($ciphertext); + + $this->assertEquals($cleartext, $encryptedDecryptedText); + } + +} diff --git a/tests/OfflineTests/JSON/SchemaValidatorTest.php b/tests/OfflineTests/JSON/SchemaValidatorTest.php new file mode 100644 index 0000000..e8df70c --- /dev/null +++ b/tests/OfflineTests/JSON/SchemaValidatorTest.php @@ -0,0 +1,175 @@ +schemaValidator = new SchemaValidator(new ObjectRetriever); + $this->graph = JsonLD::getDocument('{}')->getGraph(); + } + + public function testString() { + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/String')); + $this->schemaValidator->validateAgainstNode("Test", $node); + } + + public function testNotString() { + $this->setExpectedException(InvalidArgumentException::class); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/String')); + $this->schemaValidator->validateAgainstNode(9, $node); + } + + public function testNumber() { + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Number')); + $this->schemaValidator->validateAgainstNode(3.5, $node); + } + + public function testNotNumber() { + $this->setExpectedException(InvalidArgumentException::class); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Number')); + $this->schemaValidator->validateAgainstNode("ABC", $node); + } + + public function testInteger() { + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Integer')); + $this->schemaValidator->validateAgainstNode(12, $node); + } + + public function testNotInteger() { + $this->setExpectedException(InvalidArgumentException::class); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Integer')); + $this->schemaValidator->validateAgainstNode(1.4, $node); + } + + public function testArray() { + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Array')); + $this->schemaValidator->validateAgainstNode([ 1, 2, "foo" ], $node); + } + + public function testNotArray() { + $this->setExpectedException(InvalidArgumentException::class); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Array')); + $this->schemaValidator->validateAgainstNode("NANANA", $node); + } + + public function testObject() { + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $this->schemaValidator->validateAgainstNode([ + 'a' => 'A', + 'b' => 'B' + ], $node); + } + + public function testNotObject() { + $this->setExpectedException(InvalidArgumentException::class); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $this->schemaValidator->validateAgainstNode(5, $node); + } + + public function testObjectWithProperty() { + $stringNode = $this->graph->createNode(); + $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); + $stringNode->setType($this->graph->createNode('coid://json.co-n.net/String')); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $node->setProperty('coid://json.co-n.net/hasProperty', $stringNode); + $this->schemaValidator->validateAgainstNode([ + 'a' => 'A', + 'b' => 'B' + ], $node); + } + + public function testObjectWithPropertyTypeError() { + $this->setExpectedException(InvalidArgumentException::class); + + $stringNode = $this->graph->createNode(); + $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); + $stringNode->setType($this->graph->createNode('coid://json.co-n.net/String')); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $node->setProperty('coid://json.co-n.net/hasProperty', $stringNode); + $this->schemaValidator->validateAgainstNode([ + 'a' => 0, + 'b' => 'B' + ], $node); + } + + public function testObjectWithRequiredProperty() { + $stringNode = $this->graph->createNode(); + $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); + $stringNode->setProperty('coid://json.co-n.net/isRequired', 'true'); + $stringNode->setType($this->graph->createNode('coid://json.co-n.net/String')); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $node->setProperty('coid://json.co-n.net/hasProperty', $stringNode); + $this->schemaValidator->validateAgainstNode([ + 'a' => 'A', + 'b' => 'B' + ], $node); + } + + public function testObjectWithRequiredPropertyTypeError() { + $this->setExpectedException(InvalidArgumentException::class); + + $stringNode = $this->graph->createNode(); + $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); + $stringNode->setProperty('coid://json.co-n.net/isRequired', 'true'); + $stringNode->setType($this->graph->createNode('coid://json.co-n.net/String')); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $node->setProperty('coid://json.co-n.net/hasProperty', $stringNode); + $this->schemaValidator->validateAgainstNode([ + 'a' => 0, + 'b' => 'B' + ], $node); + } + + public function testObjectWithRequiredPropertyMissing() { + $this->setExpectedException(InvalidArgumentException::class); + + $stringNode = $this->graph->createNode(); + $stringNode->setProperty('coid://json.co-n.net/hasKey', 'a'); + $stringNode->setProperty('coid://json.co-n.net/isRequired', 'true'); + $stringNode->setType($this->graph->createNode('coid://json.co-n.net/String')); + + $node = $this->graph->createNode(); + $node->setType($this->graph->createNode('coid://json.co-n.net/Object')); + $node->setProperty('coid://json.co-n.net/hasProperty', $stringNode); + $this->schemaValidator->validateAgainstNode([ + 'b' => 'B', + 'c' => 'C' + ], $node); + } + +} diff --git a/tests/OfflineTests/NodeReaderMockTest.php b/tests/OfflineTests/NodeReaderMockTest.php new file mode 100644 index 0000000..5f595ff --- /dev/null +++ b/tests/OfflineTests/NodeReaderMockTest.php @@ -0,0 +1,156 @@ +retriever->setClient(new Client(['handler' => $handler])); + } + + private function useRootResourceMock() { + $this->setMockResponse(new Response(200, + ['Content-Type' => 'application/ld+json'], + '{"@context":{"co":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","agws":"coid:\/\/aauid.net\/","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":["agws:Service","co:Namespace"],"co:isAtRevision":"6-fbea0c90b2c5e5300e4039ed99be9b2d","co:isVisibleTo":{"@id":"co:Public"},"co:recommendsPrefix":"co","co:wasUpdatedAt":{"@type":"http:\/\/www.w3.org\/2001\/XMLSchema#dateTime","@value":"2017-01-16T17:29:22+00:00"},"rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}')); + } + + protected function setUp() { + $this->retriever = new ObjectRetriever; + $this->reader = new NodeReader([ + 'prefixes' => [ + 'co' => 'coid://cloudobjects.io/', + 'rdfs' => 'http://www.w3.org/2000/01/rdf-schema#' + ] + ]); + } + + public function testHasType1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertTrue($this->reader->hasType($object, 'coid://cloudobjects.io/Namespace')); + $this->assertTrue($this->reader->hasType($object, 'co:Namespace')); + $this->assertFalse($this->reader->hasType($object, 'coid://cloudobjects.io/MemberRole')); + $this->assertFalse($this->reader->hasType($object, 'co:MemberRole')); + } + + public function testHasPropertyValue1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertTrue($this->reader->hasPropertyValue($object, 'http://www.w3.org/2000/01/rdf-schema#label', 'CloudObjects')); + $this->assertTrue($this->reader->hasPropertyValue($object, 'rdfs:label', 'CloudObjects')); + } + + public function testGetFirstValueString1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertEquals('CloudObjects', $this->reader->getFirstValueString($object, 'rdfs:label')); + + $this->assertNull($this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo')); + $this->assertNull($this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo')); + + $this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo', 'theDefaultValue')); + $this->assertEquals('theDefaultValue', $this->reader->getFirstValueString($object, 'co:makesTriplesVisibleTo', 'theDefaultValue')); + } + + public function testGetFirstValueIRI1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo')); + $this->assertInstanceOf('ML\IRI\IRI', $this->reader->getFirstValueIRI($object, 'co:isVisibleTo')); + + $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'coid://cloudobjects.io/isVisibleTo')); + $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getFirstValueIRI($object, 'co:isVisibleTo')); + } + + public function testGetFirstValueNode1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')); + $this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')); + + $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'coid://cloudobjects.io/isVisibleTo')->getId()); + $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getFirstValueNode($object, 'co:isVisibleTo')->getId()); + } + + public function testGetAllValuesString1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertCount(1, $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertCount(1, $this->reader->getAllValuesString($object, 'rdfs:label')); + + $this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'http://www.w3.org/2000/01/rdf-schema#label')[0]); + $this->assertEquals('CloudObjects', $this->reader->getAllValuesString($object, 'rdfs:label')[0]); + + $this->assertCount(0, $this->reader->getAllValuesString($object, 'coid://cloudobjects.io/makesTriplesVisibleTo')); + $this->assertCount(0, $this->reader->getAllValuesString($object, 'co:makesTriplesVisibleTo')); + + $this->assertCount(2, $this->reader->getAllValuesString($object, '@type')); + } + + public function testGetAllValuesIRI1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertCount(0, $this->reader->getAllValuesIRI($object, 'http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertCount(0, $this->reader->getAllValuesIRI($object, 'rdfs:label')); + + $this->assertCount(1, $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')); + $this->assertCount(1, $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')); + + $this->assertCount(2, $this->reader->getAllValuesIRI($object, '@type')); + + $this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]); + $this->assertInstanceOf('ML\IRI\IRI', $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]); + + $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'coid://cloudobjects.io/isVisibleTo')[0]); + $this->assertEquals(new IRI('coid://cloudobjects.io/Public'), $this->reader->getAllValuesIRI($object, 'co:isVisibleTo')[0]); + } + + public function testGetAllValuesNode1() { + $coid = new IRI('coid://cloudobjects.io'); + $this->useRootResourceMock(); + $object = $this->retriever->getObject($coid); + + $this->assertCount(0, $this->reader->getAllValuesNode($object, 'http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertCount(0, $this->reader->getAllValuesNode($object, 'rdfs:label')); + + $this->assertCount(1, $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')); + $this->assertCount(1, $this->reader->getAllValuesNode($object, 'co:isVisibleTo')); + + $this->assertCount(2, $this->reader->getAllValuesNode($object, '@type')); + + $this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]); + $this->assertInstanceOf('ML\JsonLD\Node', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]); + + $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'coid://cloudobjects.io/isVisibleTo')[0]->getId()); + $this->assertEquals('coid://cloudobjects.io/Public', $this->reader->getAllValuesNode($object, 'co:isVisibleTo')[0]->getId()); + } + +} diff --git a/tests/OfflineTests/ObjectRetrieverMockTest.php b/tests/OfflineTests/ObjectRetrieverMockTest.php new file mode 100644 index 0000000..ff482d2 --- /dev/null +++ b/tests/OfflineTests/ObjectRetrieverMockTest.php @@ -0,0 +1,40 @@ +retriever->setClient(new Client(['handler' => $handler])); + } + + protected function setUp() { + $this->retriever = new ObjectRetriever; + } + + public function testGetRootResource() { + $this->setMockResponse(new Response(200, + ['Content-Type' => 'application/ld+json'], + '{"@context":{"cloudobjects":"coid:\/\/cloudobjects.io\/","rdf":"http:\/\/www.w3.org\/1999\/02\/22-rdf-syntax-ns#","rdfs":"http:\/\/www.w3.org\/2000\/01\/rdf-schema#"},"@id":"coid:\/\/cloudobjects.io","@type":"cloudobjects:Namespace","cloudobjects:hasPublicListing":"true","cloudobjects:revision":"1-325baa62b76105f56dc09386f5a2ec91","rdfs:comment":"The CloudObjects namespace defines the essential objects.","rdfs:label":"CloudObjects"}')); + + $coid = new IRI('coid://cloudobjects.io'); + $object = $this->retriever->getObject($coid); + $this->assertNotNull($object); + $this->assertEquals((string)$coid, $object->getID()); + $this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); + } + +} diff --git a/tests/OnlineTests/JSON/SchemaValidatorPublicTest.php b/tests/OnlineTests/JSON/SchemaValidatorPublicTest.php new file mode 100644 index 0000000..e91f467 --- /dev/null +++ b/tests/OnlineTests/JSON/SchemaValidatorPublicTest.php @@ -0,0 +1,38 @@ +schemaValidator = new SchemaValidator(new ObjectRetriever); + } + + public function testAddress() { + $this->schemaValidator->validateAgainstCOID([ + 'locality' => 'Frankfurt', + 'region' => 'Hessen', + 'country-name' => 'Germany' + ], new IRI('coid://json.co-n.net/Address')); + } + + public function testNotAddress() { + $this->setExpectedException(InvalidArgumentException::class); + + $this->schemaValidator->validateAgainstCOID([ + 'region' => 'Hessen', + 'country-name' => 'Germany' + ], new IRI('coid://json.co-n.net/Address')); + } + +} diff --git a/tests/OnlineTests/ObjectRetrieverPublicTest.php b/tests/OnlineTests/ObjectRetrieverPublicTest.php new file mode 100644 index 0000000..d2dc2b7 --- /dev/null +++ b/tests/OnlineTests/ObjectRetrieverPublicTest.php @@ -0,0 +1,60 @@ +retriever = new ObjectRetriever; + } + + private function stringifyItems(array $input) { + $output = []; + foreach ($input as $i) + $output[] = (string)$i; + + return $output; + } + + public function testGetRootObject() { + $coid = new IRI('coid://cloudobjects.io'); + $object = $this->retriever->getObject($coid); + $this->assertNotNull($object); + $this->assertEquals((string)$coid, $object->getID()); + $this->assertNotNull($object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')); + $this->assertEquals('CloudObjects', $object->getProperty('http://www.w3.org/2000/01/rdf-schema#label')->getValue()); + } + + public function testGetCOIDList() { + $coid = new IRI('coid://cloudobjects.io'); + $list = $this->stringifyItems( + $this->retriever->getCOIDListForNamespace($coid) + ); + $this->assertNotEmpty($list); + + $this->assertContains('coid://cloudobjects.io/isVisibleTo', $list); + $this->assertContains('coid://cloudobjects.io/Public', $list); + $this->assertNotContains('coid://json.co-n.net/Element', $list); + } + + public function testGetFilteredCOIDList() { + $coid = new IRI('coid://cloudobjects.io'); + $list = $this->stringifyItems( + $this->retriever->getCOIDListForNamespaceWithType($coid, 'coid://cloudobjects.io/Audience') + ); + $this->assertNotEmpty($list); + + $this->assertNotContains('coid://cloudobjects.io/isVisibleTo', $list); + $this->assertContains('coid://cloudobjects.io/Public', $list); + $this->assertContains('coid://cloudobjects.io/Private', $list); + } + +} diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..559e6a0 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,7 @@ +