adapter = $adapter; return $this; } /** * Set Namespace. * * Set namespace to divide different scope of data sets * * @param $namespace * * @return $this * * @throws Exception */ public function setNamespace($namespace) { $this->adapter->setNamespace($namespace); return $this; } /** * Get Namespace. * * Get namespace of current set scope * * @return string * * @throws Exception */ public function getNamespace() { return $this->adapter->getNamespace(); } /** * Create Namespace. * * @param string $namespace * * @return bool */ public function createNamespace($namespace) { return $this->adapter->createNamespace($namespace); } /** * Delete Namespace. * * @param string $namespace * * @return bool */ public function deleteNamespace($namespace) { return $this->adapter->deleteNamespace($namespace); } /** * @param array $options * * @return Document[] */ public function getCollection(array $options) { $options = \array_merge([ 'offset' => 0, 'limit' => 15, 'search' => '', 'relations' => true, 'orderField' => '', 'orderType' => 'ASC', 'orderCast' => 'int', 'filters' => [], ], $options); $results = $this->adapter->getCollection($options); foreach ($results as &$node) { $node = $this->decode(new Document($node)); } return $results; } /** * @param array $options * * @return Document */ public function getCollectionFirst(array $options) { $results = $this->getCollection($options); return \reset($results); } /** * @param array $options * * @return Document */ public function getCollectionLast(array $options) { $results = $this->getCollection($options); return \end($results); } /** * @param string $id * @param bool $mock is mocked data allowed? * @param bool $decode enable decoding? * * @return Document */ public function getDocument($id, bool $mock = true, bool $decode = true) { if (\is_null($id)) { return new Document(); } $document = new Document((isset($this->mocks[$id]) && $mock) ? $this->mocks[$id] : $this->adapter->getDocument($id)); $validator = new Authorization($document, 'read'); if (!$validator->isValid($document->getPermissions())) { // Check if user has read access to this document return new Document(); } $document = ($decode) ? $this->decode($document) : $document; return $document; } /** * @param array $data * * @return Document * * @throws AuthorizationException * @throws StructureException */ public function createDocument(array $data, array $unique = []) { $document = new Document($data); $validator = new Authorization($document, 'write'); if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); } $validator = new Structure($this); $document = $this->encode($document); if (!$validator->isValid($document)) { throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $document = new Document($this->adapter->createDocument($document->getArrayCopy(), $unique)); $document = $this->decode($document); return $document; } /** * @param array $data * * @return Document|false * * @throws Exception */ public function updateDocument(array $data) { if (!isset($data['$id'])) { throw new Exception('Must define $id attribute'); } $document = $this->getDocument($data['$id']); // TODO make sure user don\'t need read permission for write operations // Make sure reserved keys stay constant $data['$id'] = $document->getId(); $data['$collection'] = $document->getCollection(); $validator = new Authorization($document, 'write'); if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = new Document($data); if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = $this->encode($new); $validator = new Structure($this); if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = new Document($this->adapter->updateDocument($new->getArrayCopy())); $new = $this->decode($new); return $new; } /** * @param array $data * * @return Document|false * * @throws Exception */ public function overwriteDocument(array $data) { if (!isset($data['$id'])) { throw new Exception('Must define $id attribute'); } $document = $this->getDocument($data['$id']); // TODO make sure user don\'t need read permission for write operations $validator = new Authorization($document, 'write'); if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = new Document($data); if (!$validator->isValid($new->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = $this->encode($new); $validator = new Structure($this); if (!$validator->isValid($new)) { // Make sure updated structure still apply collection rules (if any) throw new StructureException($validator->getDescription()); // var_dump($validator->getDescription()); return false; } $new = new Document($this->adapter->updateDocument($new->getArrayCopy())); $new = $this->decode($new); return $new; } /** * @param string $id * * @return Document|false * * @throws AuthorizationException */ public function deleteDocument(string $id) { $document = $this->getDocument($id); $validator = new Authorization($document, 'write'); if (!$validator->isValid($document->getPermissions())) { // Check if user has write access to this document throw new AuthorizationException($validator->getDescription()); } return new Document($this->adapter->deleteDocument($id)); } /** * @param int $key * * @return Document|false * * @throws AuthorizationException */ public function deleteUniqueKey($key) { return new Document($this->adapter->deleteUniqueKey($key)); } /** * @param int $key * * @return Document|false * * @throws AuthorizationException */ public function addUniqueKey($key) { return new Document($this->adapter->addUniqueKey($key)); } /** * @return array */ public function getDebug() { return $this->adapter->getDebug(); } /** * @return int */ public function getSum() { $debug = $this->getDebug(); return (isset($debug['sum'])) ? $debug['sum'] : 0; } /** * @param array $options * * @return int */ public function getCount(array $options) { $options = \array_merge([ 'filters' => [], ], $options); $results = $this->adapter->getCount($options); return $results; } /** * @param string $key * @param string $value * * @return self */ public function setMock($key, $value): self { $this->mocks[$key] = $value; return $this; } /** * @param array $mocks * * @return self */ public function setMocks(array $mocks): self { $this->mocks = $mocks; return $this; } /** * @return array */ public function getMocks() { return $this->mocks; } /** * Add Attribute Filter * * @param string $name * @param callable $encode * @param callable $decode * * @return void */ static public function addFilter(string $name, callable $encode, callable $decode): void { self::$filters[$name] = [ 'encode' => $encode, 'decode' => $decode, ]; } /** * Disable Attribute decoding * * @return void */ public static function disableFilters(): void { self::$statusFilters = false; } /** * Enable Attribute decoding * * @return void */ public static function enableFilters(): void { self::$statusFilters = true; } public function encode(Document $document):Document { if (!self::$statusFilters) { return $document; } $collection = $this->getDocument($document->getCollection(), true , false); $rules = $collection->getAttribute('rules', []); foreach ($rules as $key => $rule) { $key = $rule->getAttribute('key', null); $type = $rule->getAttribute('type', null); $array = $rule->getAttribute('array', false); $filters = $rule->getAttribute('filter', []); $value = $document->getAttribute($key, null); if (($value !== null)) { if ($type === self::SYSTEM_VAR_TYPE_DOCUMENT) { if($array) { $list = []; foreach ($value as $child) { $list[] = $this->encode($child); } $document->setAttribute($key, $list); } else { $document->setAttribute($key, $this->encode($value)); } } else { foreach ($filters as $filter) { $value = $this->encodeAttribute($filter, $value); $document->setAttribute($key, $value); } } } } return $document; } public function decode(Document $document):Document { if (!self::$statusFilters) { return $document; } $collection = $this->getDocument($document->getCollection(), true , false); $rules = $collection->getAttribute('rules', []); foreach ($rules as $key => $rule) { $key = $rule->getAttribute('key', null); $type = $rule->getAttribute('type', null); $array = $rule->getAttribute('array', false); $filters = $rule->getAttribute('filter', []); $value = $document->getAttribute($key, null); if (($value !== null)) { if ($type === self::SYSTEM_VAR_TYPE_DOCUMENT) { if($array) { $list = []; foreach ($value as $child) { $list[] = $this->decode($child); } $document->setAttribute($key, $list); } else { $document->setAttribute($key, $this->decode($value)); } } else { foreach (array_reverse($filters) as $filter) { $value = $this->decodeAttribute($filter, $value); $document->setAttribute($key, $value); } } } } return $document; } /** * Encode Attribute * * @param string $name * @param mixed $value */ static protected function encodeAttribute(string $name, $value) { if (!isset(self::$filters[$name])) { return $value; throw new Exception('Filter not found'); } try { $value = self::$filters[$name]['encode']($value); } catch (\Throwable $th) { $value = null; } return $value; } /** * Decode Attribute * * @param string $name * @param mixed $value */ static protected function decodeAttribute(string $name, $value) { if (!isset(self::$filters[$name])) { return $value; throw new Exception('Filter not found'); } try { $value = self::$filters[$name]['decode']($value); } catch (\Throwable $th) { $value = null; } return $value; } /** * Get Last Modified. * * Return Unix timestamp of last time a node queried in current session has been changed * * @return int */ public function lastModified() { return $this->adapter->lastModified(); } }