Add admin form to view and edit users

This commit is contained in:
Alex Cabal 2024-11-23 13:13:31 -06:00
parent 32607fb220
commit 8ad3291a35
35 changed files with 902 additions and 72 deletions

View file

@ -2,18 +2,26 @@
use Ramsey\Uuid\Uuid;
use Safe\DateTimeImmutable;
use function Safe\preg_match;
/**
* @property array<Payment> $Payments
* @property bool $IsRegistered
* @property bool $IsRegistered A user is "registered" if they have an entry in the `Benefits` table; a password is required to log in.
* @property Benefits $Benefits
* @property string $Url
* @property bool $IsPatron
* @property ?Patron $Patron
* @property ?NewsletterSubscription $NewsletterSubscription
*/
class User{
use Traits\Accessor;
use Traits\PropertyFromHttp;
public int $UserId;
public ?string $Name = null;
public ?string $Email = null;
public DateTimeImmutable $Created;
public DateTimeImmutable $Updated;
public string $Uuid;
public ?string $PasswordHash = null;
@ -21,12 +29,60 @@ class User{
/** @var array<Payment> $_Payments */
protected array $_Payments;
protected Benefits $_Benefits;
protected string $_Url;
protected bool $_IsPatron;
protected ?Patron $_Patron;
protected ?NewsletterSubscription $_NewsletterSubscription;
// *******
// GETTERS
// *******
protected function GetIsPatron(): bool{
if(!isset($this->_IsPatron)){
$this->GetPatron();
}
return $this->_IsPatron;
}
protected function GetNewsletterSubscription(): ?NewsletterSubscription{
if(!isset($this->_NewsletterSubscription)){
try{
$this->_NewsletterSubscription = NewsletterSubscription::GetByUserId($this->UserId);
}
catch(Exceptions\NewsletterSubscriptionNotFoundException){
$this->_NewsletterSubscription = null;
}
}
return $this->_NewsletterSubscription;
}
protected function GetPatron(): ?Patron{
if(!isset($this->_Patron)){
try{
$this->_Patron = Patron::Get($this->UserId);
$this->IsPatron = true;
}
catch(Exceptions\PatronNotFoundException){
$this->_Patron = null;
$this->IsPatron = false;
}
}
return $this->_Patron;
}
protected function GetUrl(): string{
if(!isset($this->_Url)){
$this->_Url = '/users/' . $this->UserId;
}
return $this->_Url;
}
/**
* @return array<Payment>
*/
@ -66,7 +122,7 @@ class User{
protected function GetIsRegistered(): ?bool{
if(!isset($this->_IsRegistered)){
// A user is "registered" if they have a benefits entry in the table.
// A user is "registered" if they have an entry in the `Benefits` table.
// This function will fill it out for us.
$this->GetBenefits();
}
@ -80,11 +136,61 @@ class User{
// *******
/**
* @throws Exceptions\InvalidUserException
*/
public function Validate(): void{
$error = new Exceptions\InvalidUserException();
if(!isset($this->Email)){
$error->Add(new Exceptions\EmailRequiredException());
}
else{
if(filter_var($this->Email, FILTER_VALIDATE_EMAIL) === false){
$error->Add(new Exceptions\InvalidEmailException('Email is invalid.'));
}
}
if(!isset($this->Uuid)){
$error->Add(new Exceptions\UuidRequiredException());
}
else{
if(!preg_match('/^[0-9a-f]{8}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{4}\-[0-9a-f]{12}$/', $this->Uuid)){
$error->Add(new Exceptions\InvalidUuidException());
}
}
if(trim($this->Name ?? '') == ''){
$this->Name = null;
}
if(trim($this->PasswordHash ?? '') == ''){
$this->PasswordHash = null;
}
// Some benefits require this `User` to have a password set.
if($this->Benefits->RequiresPassword && $this->PasswordHash === null){
$error->Add(new Exceptions\BenefitsRequirePasswordException());
}
if($error->HasExceptions){
throw $error;
}
}
public function GenerateUuid(): void{
$uuid = Uuid::uuid4();
$this->Uuid = $uuid->toString();
}
/**
* @throws Exceptions\InvalidUserException
* @throws Exceptions\UserExistsException
*/
public function Create(?string $password = null): void{
$uuid = Uuid::uuid4();
$this->Uuid = $uuid->toString();
$this->GenerateUuid();
$this->Validate();
$this->Created = NOW;
@ -110,6 +216,37 @@ class User{
$this->UserId = Db::GetLastInsertedId();
}
/**
* @throws Exceptions\InvalidUserException
* @throws Exceptions\UserExistsException
*/
public function Save(): void{
$this->Validate();
$this->Updated = NOW;
try{
Db::Query('
UPDATE Users
set Email = ?, Name = ?, Uuid = ?, Updated = ?, PasswordHash = ?
where
UserId = ?
', [$this->Email, $this->Name, $this->Uuid, $this->Updated, $this->PasswordHash, $this->UserId]);
if($this->IsRegistered){
$this->Benefits->Save();
}
elseif($this->Benefits->HasBenefits){
$this->Benefits->UserId = $this->UserId;
$this->Benefits->Create();
$this->IsRegistered = true;
}
}
catch(Exceptions\DuplicateDatabaseKeyException){
throw new Exceptions\UserExistsException();
}
}
// ***********
// ORM METHODS
@ -130,6 +267,30 @@ class User{
', [$userId], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
}
/**
* Get a `User` based on either a `UserId`, `Email`, or `Uuid`.
*
* @throws Exceptions\UserNotFoundException
*/
public static function GetByIdentifier(?string $identifier): User{
if($identifier === null){
throw new Exceptions\UserNotFoundException();
}
if(ctype_digit($identifier)){
return User::Get(intval($identifier));
}
elseif(mb_stripos($identifier, '@') !== false){
return User::GetByEmail($identifier);
}
elseif(mb_stripos($identifier, '-') !== false){
return User::GetByUuid($identifier);
}
else{
throw new Exceptions\UserNotFoundException();
}
}
/**
* @throws Exceptions\UserNotFoundException
*/
@ -145,6 +306,21 @@ class User{
', [$email], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
}
/**
* @throws Exceptions\UserNotFoundException
*/
public static function GetByUuid(?string $uuid): User{
if($uuid === null){
throw new Exceptions\UserNotFoundException();
}
return Db::Query('
SELECT *
from Users
where Uuid = ?
', [$uuid], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
}
/**
* Get a `User` if they are considered "registered".
*
@ -169,7 +345,7 @@ class User{
', [$identifier, $identifier], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
if($user->PasswordHash !== null && $password === null){
// Indicate that a password is required before we log in
// Indicate that a password is required before we log in.
throw new Exceptions\PasswordRequiredException();
}
@ -179,4 +355,12 @@ class User{
return $user;
}
public function FillFromHttpPost(): void{
$this->PropertyFromHttp('Name');
$this->PropertyFromHttp('Email');
$this->PropertyFromHttp('Uuid');
$this->Benefits->FillFromHttpPost();
}
}