RequirementsCheck

unlisted ⁨1⁩ ⁨file⁩ 2024-04-14 12:53:54 UTC

RequirementsCheck.php

Raw
<?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;
    }



}