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