'$id', '$collection' => Database::SYSTEM_COLLECTION_RULES, 'key' => '$id', 'type' => 'id', 'default' => null, 'required' => false, 'array' => false, ], [ 'label' => '$collection', '$collection' => Database::SYSTEM_COLLECTION_RULES, 'key' => '$collection', 'type' => 'id', 'default' => null, 'required' => true, 'array' => false, ], [ 'label' => '$permissions', '$collection' => Database::SYSTEM_COLLECTION_RULES, 'key' => '$permissions', 'type' => 'permissions', 'default' => null, 'required' => true, 'array' => false, ], [ 'label' => '$createdAt', '$collection' => Database::SYSTEM_COLLECTION_RULES, 'key' => '$createdAt', 'type' => 'numeric', 'default' => null, 'required' => false, 'array' => false, ], [ 'label' => '$updatedAt', '$collection' => Database::SYSTEM_COLLECTION_RULES, 'key' => '$updatedAt', 'type' => 'numeric', 'default' => null, 'required' => false, 'array' => false, ], ]; /** * @var string */ protected $message = 'General Error'; /** * Structure constructor. * * @param Database $database */ public function __construct(Database $database) { $this->database = $database; } /** * Get Description. * * Returns validator description * * @return string */ public function getDescription() { return 'Invalid document structure: '.$this->message; } /** * Is valid. * * Returns true if valid or false if not. * * @param mixed $document * * @return bool */ public function isValid($document) { $document = (\is_array($document)) ? new Document($document) : $document; $this->id = $document->getId(); if (\is_null($document->getCollection())) { $this->message = 'Missing collection attribute $collection'; return false; } $collection = $this->getCollection($document->getCollection()); if (\is_null($collection->getId()) || Database::SYSTEM_COLLECTION_COLLECTIONS != $collection->getCollection()) { $this->message = 'Collection not found'; return false; } $array = $document->getArrayCopy(); $rules = \array_merge($this->rules, $collection->getAttribute('rules', [])); foreach ($rules as $rule) { // Check all required keys are set if (isset($rule['key']) && !isset($array[$rule['key']]) && isset($rule['required']) && true == $rule['required']) { $this->message = 'Missing required key "'.$rule['key'].'"'; return false; } } foreach ($array as $key => $value) { $rule = $collection->search('key', $key, $rules); if(!$rule) { continue; } $ruleType = $rule['type'] ?? ''; $ruleRequired = $rule['required'] ?? true; $ruleArray = $rule['array'] ?? false; $validator = null; switch ($ruleType) { case self::RULE_TYPE_ID: $validator = new UID(); break; case self::RULE_TYPE_PERMISSIONS: $validator = new Permissions($document); //$validator = ($this->forcePermissions) ? new Authorization($original, 'write') : new Validator\Mock(); break; case self::RULE_TYPE_KEY: $validator = new Key(); break; case self::RULE_TYPE_TEXT: case self::RULE_TYPE_MARKDOWN: $validator = new Validator\Text(0); break; case self::RULE_TYPE_NUMERIC: $validator = new Validator\Numeric(); break; case self::RULE_TYPE_BOOLEAN: $validator = new Validator\Boolean(); break; case self::RULE_TYPE_EMAIL: $validator = new Validator\Email(); break; case self::RULE_TYPE_URL: $validator = new Validator\URL(); break; case self::RULE_TYPE_IP: $validator = new Validator\IP(); break; case self::RULE_TYPE_WILDCARD: $validator = new Validator\Mock(); break; case self::RULE_TYPE_DOCUMENT: $validator = new Collection($this->database, (isset($rule['list'])) ? $rule['list'] : []); $value = $document->getAttribute($key); break; case self::RULE_TYPE_DOCUMENTID: $validator = new DocumentId($this->database, (isset($rule['list']) && isset($rule['list'][0])) ? $rule['list'][0] : ''); $value = $document->getAttribute($key); break; case self::RULE_TYPE_FILEID: $validator = new DocumentId($this->database, Database::SYSTEM_COLLECTION_FILES); $value = $document->getAttribute($key); break; } if (empty($validator)) { // Error creating validator for property $this->message = 'Unknown rule type "'.$ruleType.'" for property "'.\htmlspecialchars($key, ENT_QUOTES, 'UTF-8').'"'; if (empty($ruleType)) { $this->message = 'Unknown property "'.$key.'" type'. '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; } return false; } if ($ruleRequired && ('' === $value || null === $value)) { $this->message = 'Required property "'.$key.'" has no value'; return false; } if (!$ruleRequired && empty($value)) { unset($array[$key]); unset($rule); continue; } if ($ruleArray) { // Array of values validation if (!\is_array($value)) { $this->message = 'Property "'.$key.'" must be an array'; return false; } // TODO add is required check here foreach ($value as $node) { if (!$validator->isValid($node)) { // Check if property is valid, if not required can also be empty $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); return false; } } } else { // Single value validation if ((!$validator->isValid($value)) && !('' === $value && !$ruleRequired)) { // Error when value is not valid, and is not optional and empty $this->message = 'Property "'.$key.'" has invalid input. '.$validator->getDescription(); return false; } } unset($array[$key]); unset($rule); } if (!empty($array)) { // No fields should be left unvalidated $this->message = 'Unknown properties are not allowed ('.\implode(', ', \array_keys($array)).') for this collection'. '. Make sure to follow '.\strtolower($collection->getAttribute('name', 'unknown')).' collection structure'; return false; } return true; } /** * Get Collection * * Get Collection by unique ID * * @return Document */ protected function getCollection($id): Document { return $this->database->getDocument($id); } }