* @link https://brentspine.de/php/requirements-check Homepage */ class RequirementsCheck { private ?RequestStack $requestStack; private ?EntityManagerInterface $entityManager; private ?Users $user; private bool $login; private array $permissions; private bool $no_login; private array $required_fields; private array $data_fields; private array $required_types; private bool $allowed; private Response $response; private bool $ran = false; /** * An easy-to-use class to declare if a request is allowed or not * * @param RequestStack|null $requestStack Accessible via Autowire * @param EntityManagerInterface|null $entityManager Accessible via Autowire * @param bool $login Is a Login required? * @param Permission[] $permissions Required Permissions, if set $login will automatically be true * @param bool $no_login Does the user need to be signed out? * @param array $required_fields The fields that need to be filled in $data_fields. * Fields required in $required_types are automatically checked as well * @param array $required_types The required types for each field. For example ['user_id' => 'int', 'keys' => 'array_string']. * Supports checking arrays for types, e.g. ['ids' => 'array_int'] checks if 'ids' is an array and * if it consists of integers. If not the request will not be allowed * @param array $data_fields The data fields passed with the request, e.g. $_GET or $_POST * * @return static The created instance of RequirementsCheck * @author Brentspine * @link https://brentspine.de/php/requirements-check Homepage */ public static function create(RequestStack $requestStack = null, EntityManagerInterface $entityManager = null, bool $login = true, array $permissions = [], bool $no_login = false, array $required_fields = [], array $required_types = [], array $data_fields = []): RequirementsCheck { return new RequirementsCheck($requestStack, $entityManager, $login, $permissions, $no_login, $required_fields, $required_types, $data_fields); } /** * An easy-to-use class to declare if a request is allowed or not * * @param RequestStack|null $requestStack Accessible via Autowire * @param EntityManagerInterface|null $entityManager Accessible via Autowire * @param bool $login Is a Login required? * @param Permission[] $permissions Required Permissions, if set $login will automatically be true * @param bool $no_login Does the user need to be signed out? * @param array $required_fields The fields that need to be filled in $data_fields. * Fields required in $required_types are automatically checked as well * @param array $required_types The required types for each field. For example ['user_id' => 'int', 'keys' => 'array_string']. * Supports checking arrays for types, e.g. ['ids' => 'array_int'] checks if 'ids' is an array and * if it consists of integers. If not the request will not be allowed * @param array $data_fields The data fields passed with the request, e.g. $_GET or $_POST * @author Brentspine * @link https://brentspine.de/php/requirements-check Homepage */ private function __construct(RequestStack $requestStack = null, EntityManagerInterface $entityManager = null, bool $login = true, array $permissions = [Permission::NONE], bool $no_login = false, array $required_fields = [], array $required_types = [], array $data_fields = []) { $this->requestStack = $requestStack; $this->entityManager = $entityManager; $this->login = $login; $this->permissions = $permissions; $this->no_login = $no_login; $this->required_fields = $required_fields; $this->data_fields = $data_fields; $this->required_types = $required_types; if($requestStack) $this->user = Users::getUserBySession($requestStack, $entityManager); else $this->user = null; return $this; } /** * Runs all the checks and declares $allowed and $response * * @throws NonUniqueResultException */ private function run() : RequirementsCheck { if($this->isRan()) return $this; $this->setRan(true); if(($this->isLogin() && !$this->getUser()) || (count($this->permissions) > 0 && !$this->getUser())) { return $this->disallow("login_required"); } if($this->isNoLogin() && $this->getUser()) { return $this->disallow("already_logged_in"); } if(count($this->permissions) > 0 && !$this->getUser()->hasOneOfPermissions($this->getEntityManager(), $this->getPermissions())) { return $this->disallow("missing_permissions"); } if(!empty($this->getRequiredFields())) { for($i = 0; $i < count($this->getRequiredFields()); $i++) { if(empty($this->getDataFields()[$this->getRequiredFields()[$i]])) { return $this->disallow("missing_fields", additional_info: ["field" => $this->getRequiredFields()[$i]]); } } } if(!empty($this->getRequiredTypes())) { foreach ($this->getRequiredTypes() as $k => $type) { if(empty($this->getDataFields()[$k])) { return $this->disallow("missing_fields", additional_info: ["field" => $k]); } $value = $this->getDataFields()[$k]; $check = $this->check_variable_type($type, $value); if($check) return $check; if(str_starts_with($type, "array")) { if(!is_array($value)) return $this->disallow("expected_data_type", replacements: ["type", "'".$type."'"]); if(str_contains($type, "_")) { $type = explode("_", $type)[1]; foreach ($value as $c) { $check = $this->check_variable_type($type, $c, true); if($check) return $check; } } } } } $this->allowed = true; return $this; } /** * Checks if a variable is the required type. If not disallows the check and returns. * Compatible types are: (int, string, bool, null, resource). Does not work with arrays. * * @param string $type The type the variable to check should be * @param ?mixed $value The variable/value to check * @param bool $in_array If set to true a '[]' will be appended at the end of the required type, when a check fails. (e.g. 'int' -> 'int[]') * @return ?static Null if allowed, static if not * * @throws NonUniqueResultException */ private function check_variable_type(string $type, mixed $value, bool $in_array = false): ?static { // Add float To-do switch (strtolower($type)) { //case "float": //if(!str_contains($value, ".")) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); //if(!is_numeric($value)) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); //break; case "int": if(str_contains($value, ".")) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); if(!is_numeric($value)) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); break; case "string": if(!is_string($value)) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); break; case "bool": case "boolean": if($value != "true" && $value != "false") return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); break; case "null": if(!is_null($value)) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); break; case "resource": if(!is_resource($value)) return $this->disallow("expected_data_type", replacements: ["type", $type.($in_array ? "[]" : "")]); break; } return null; } /** * @throws NonUniqueResultException */ private function disallow(string $response_id, array $replacements = [], array $additional_info = null): RequirementsCheck { $this->allowed = false; $this->response = Responses::getResponse($response_id, $this->getEntityManager(), replacements: $replacements, additional_info: $additional_info); return $this; } /** * @return array */ public function getRequiredFields(): array { return $this->required_fields; } /** * @param array $required_fields */ public function setRequiredFields(array $required_fields): void { $this->required_fields = $required_fields; } /** * @return array */ public function getDataFields(): array { return $this->data_fields; } /** * @param array $data_fields */ public function setDataFields(array $data_fields): void { $this->data_fields = $data_fields; } /** * Automatically runs all checks and returns if the request is allowed
* ->getResponse() will contain the error response on fail * * @return bool * @throws NonUniqueResultException */ public function isAllowed(): bool { $this->run(); return $this->allowed; } /** * Returns the error response if any check fails. Will try to run() the checks if not done already.
* ->isAllowed() runs the checks. * * @return Response * @throws NonUniqueResultException */ public function getResponse(): Response { $this->run(); return $this->response; } /** * @return bool */ public function isRan(): bool { return $this->ran; } /** * @param bool $ran */ private function setRan(bool $ran): void { $this->ran = $ran; } /** * @return RequestStack */ public function getRequestStack(): RequestStack { return $this->requestStack; } /** * @param RequestStack $requestStack */ public function setRequestStack(RequestStack $requestStack): void { $this->requestStack = $requestStack; } /** * @return EntityManagerInterface */ public function getEntityManager(): EntityManagerInterface { return $this->entityManager; } /** * @param EntityManagerInterface $entityManager */ public function setEntityManager(EntityManagerInterface $entityManager): void { $this->entityManager = $entityManager; } /** * @return ?Users */ public function getUser(): ?Users { return $this->user; } /** * @param Users $user */ public function setUser(Users $user): void { $this->user = $user; } /** * @return array */ public function getPermissions(): array { return $this->permissions; } /** * @param array $permissions */ public function setPermissions(array $permissions): void { $this->permissions = $permissions; } /** * @return bool */ public function isLogin(): bool { return $this->login; } /** * @param bool $login */ public function setLogin(bool $login): void { $this->login = $login; } /** * @return bool */ public function isNoLogin(): bool { return $this->no_login; } /** * @param bool $no_login */ public function setNoLogin(bool $no_login): void { $this->no_login = $no_login; } /** * @return array */ public function getRequiredTypes(): array { return $this->required_types; } /** * @param array $required_types */ public function setRequiredTypes(array $required_types): void { $this->required_types = $required_types; } }