RequirementsCheck.php
@@ -0,0 +1,394 @@
+<?php
+
+namespace App;
+
+use App\Entity\Responses;
+use App\Entity\Users;
+use App\Enums\Permission;
+use Doctrine\ORM\EntityManagerInterface;
+use Doctrine\ORM\NonUniqueResultException;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * @author Brentspine <[email protected]>
+ * @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 <[email protected]>
+ * @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 <[email protected]>
+ * @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 <br>
+ * ->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. <br>
+ * ->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;
+ }
+
+
+
+}
\ No newline at end of file