mirror of
https://github.com/standardebooks/web.git
synced 2025-07-08 07:40:39 -04:00
Add admin form to view and edit users
This commit is contained in:
parent
32607fb220
commit
8ad3291a35
35 changed files with 902 additions and 72 deletions
6
config/apache/rewrites/users.conf
Normal file
6
config/apache/rewrites/users.conf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||||
|
RewriteRule ^/users/([\d]+)$ /users/post.php?user-id=$1 [L]
|
||||||
|
|
||||||
|
RewriteRule ^/users/([^/]+)$ /users/get.php?user-identifier=$1 [L]
|
||||||
|
|
||||||
|
RewriteRule ^/users/([\d]+)/edit$ /users/edit.php?user-id=$1 [L]
|
|
@ -216,6 +216,7 @@ Define conf_rewrite_root ${web_root}/config/apache/rewrites
|
||||||
Include ${conf_rewrite_root}/newsletters.conf
|
Include ${conf_rewrite_root}/newsletters.conf
|
||||||
Include ${conf_rewrite_root}/artworks.conf
|
Include ${conf_rewrite_root}/artworks.conf
|
||||||
Include ${conf_rewrite_root}/polls.conf
|
Include ${conf_rewrite_root}/polls.conf
|
||||||
|
Include ${conf_rewrite_root}/users.conf
|
||||||
|
|
||||||
# Specific config for /ebooks/<author>/<ebook>/downloads
|
# Specific config for /ebooks/<author>/<ebook>/downloads
|
||||||
<DirectoryMatch "^${web_root}/www/ebooks/.+">
|
<DirectoryMatch "^${web_root}/www/ebooks/.+">
|
||||||
|
|
|
@ -198,6 +198,7 @@ Define conf_rewrite_root ${web_root}/config/apache/rewrites
|
||||||
Include ${conf_rewrite_root}/newsletters.conf
|
Include ${conf_rewrite_root}/newsletters.conf
|
||||||
Include ${conf_rewrite_root}/artworks.conf
|
Include ${conf_rewrite_root}/artworks.conf
|
||||||
Include ${conf_rewrite_root}/polls.conf
|
Include ${conf_rewrite_root}/polls.conf
|
||||||
|
Include ${conf_rewrite_root}/users.conf
|
||||||
|
|
||||||
# Specific config for /ebooks/<author>/<ebook>/downloads
|
# Specific config for /ebooks/<author>/<ebook>/downloads
|
||||||
<DirectoryMatch "^${web_root}/www/ebooks/.+">
|
<DirectoryMatch "^${web_root}/www/ebooks/.+">
|
||||||
|
|
|
@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS `Benefits` (
|
||||||
`CanUploadArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanUploadArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanReviewArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanReviewArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanReviewOwnArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanReviewOwnArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
|
`CanEditUsers` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
PRIMARY KEY (`UserId`),
|
PRIMARY KEY (`UserId`),
|
||||||
KEY `idxBenefits` (`CanAccessFeeds`,`CanVote`,`CanBulkDownload`)
|
KEY `idxBenefits` (`CanAccessFeeds`,`CanVote`,`CanBulkDownload`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
|
@ -3,8 +3,13 @@ CREATE TABLE IF NOT EXISTS `Users` (
|
||||||
`Email` varchar(80) DEFAULT NULL,
|
`Email` varchar(80) DEFAULT NULL,
|
||||||
`Name` varchar(255) DEFAULT NULL,
|
`Name` varchar(255) DEFAULT NULL,
|
||||||
`Created` timestamp NOT NULL DEFAULT current_timestamp(),
|
`Created` timestamp NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`Updated` timestamp NOT NULL DEFAULT current_timestamp() on update current_timestamp(),
|
||||||
`Uuid` char(36) NOT NULL DEFAULT (uuid()),
|
`Uuid` char(36) NOT NULL DEFAULT (uuid()),
|
||||||
`PasswordHash` varchar(255) NULL,
|
`PasswordHash` varchar(255) NULL,
|
||||||
PRIMARY KEY (`UserId`),
|
PRIMARY KEY (`UserId`),
|
||||||
UNIQUE KEY `idxEmail` (`Email`)
|
UNIQUE KEY `idxEmail` (`Email`,`Uuid`,`UserId`),
|
||||||
|
UNIQUE KEY `idxUniqueEmail` (`Email`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
ALTER TABLE `se`.`Users`
|
||||||
|
ADD INDEX `idxUniqueEmail` (`Email` ASC) VISIBLE;
|
||||||
|
;
|
||||||
|
|
|
@ -1,9 +1,84 @@
|
||||||
<?
|
<?
|
||||||
|
/**
|
||||||
|
* @property bool $HasBenefits Are any of the benefits in this object **`TRUE`**?
|
||||||
|
* @property bool $RequiresPassword Do any of the benefits in this object require the `User` to have a password set?
|
||||||
|
*/
|
||||||
class Benefits{
|
class Benefits{
|
||||||
|
use Traits\Accessor;
|
||||||
|
use Traits\PropertyFromHttp;
|
||||||
|
|
||||||
|
public int $UserId;
|
||||||
public bool $CanAccessFeeds = false;
|
public bool $CanAccessFeeds = false;
|
||||||
public bool $CanVote = false;
|
public bool $CanVote = false;
|
||||||
public bool $CanBulkDownload = false;
|
public bool $CanBulkDownload = false;
|
||||||
public bool $CanUploadArtwork = false;
|
public bool $CanUploadArtwork = false;
|
||||||
public bool $CanReviewArtwork = false;
|
public bool $CanReviewArtwork = false;
|
||||||
public bool $CanReviewOwnArtwork = false;
|
public bool $CanReviewOwnArtwork = false;
|
||||||
|
public bool $CanEditUsers = false;
|
||||||
|
|
||||||
|
protected bool $_HasBenefits;
|
||||||
|
|
||||||
|
protected function GetRequiresPassword(): bool{
|
||||||
|
if(
|
||||||
|
$this->CanUploadArtwork
|
||||||
|
||
|
||||||
|
$this->CanReviewArtwork
|
||||||
|
||
|
||||||
|
$this->CanReviewOwnArtwork
|
||||||
|
||
|
||||||
|
$this->CanEditUsers
|
||||||
|
){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function GetHasBenefits(): bool{
|
||||||
|
if(!isset($this->_HasBenefits)){
|
||||||
|
$this->_HasBenefits = false;
|
||||||
|
|
||||||
|
/** @phpstan-ignore-next-line */
|
||||||
|
foreach($this as $property => $value){
|
||||||
|
$rp = new ReflectionProperty(self::class, $property);
|
||||||
|
$type = $rp->getType();
|
||||||
|
|
||||||
|
if($type !== null && ($type instanceof \ReflectionNamedType)){
|
||||||
|
$typeName = $type->getName();
|
||||||
|
if($typeName == 'bool' && $value == true){
|
||||||
|
$this->_HasBenefits = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_HasBenefits;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Create(): void{
|
||||||
|
Db::Query('
|
||||||
|
INSERT into Benefits (UserId, CanAccessFeeds, CanVote, CanBulkDownload, CanUploadArtwork, CanReviewArtwork, CanReviewOwnArtwork, CanEditUsers)
|
||||||
|
values (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
', [$this->UserId, $this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Save(): void{
|
||||||
|
Db::Query('
|
||||||
|
UPDATE Benefits
|
||||||
|
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?
|
||||||
|
where
|
||||||
|
UserId = ?
|
||||||
|
', [$this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->UserId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function FillFromHttpPost(): void{
|
||||||
|
$this->PropertyFromHttp('CanAccessFeeds');
|
||||||
|
$this->PropertyFromHttp('CanVote');
|
||||||
|
$this->PropertyFromHttp('CanBulkDownload');
|
||||||
|
$this->PropertyFromHttp('CanUploadArtwork');
|
||||||
|
$this->PropertyFromHttp('CanReviewArtwork');
|
||||||
|
$this->PropertyFromHttp('CanReviewOwnArtwork');
|
||||||
|
$this->PropertyFromHttp('CanEditUsers');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
8
lib/Enums/PasswordActionType.php
Normal file
8
lib/Enums/PasswordActionType.php
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?
|
||||||
|
namespace Enums;
|
||||||
|
|
||||||
|
enum PasswordActionType: string{
|
||||||
|
case Edit = 'edit';
|
||||||
|
case None = 'none';
|
||||||
|
case Delete = 'delete';
|
||||||
|
}
|
7
lib/Exceptions/BenefitsRequirePasswordException.php
Normal file
7
lib/Exceptions/BenefitsRequirePasswordException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class BenefitsRequirePasswordException extends AppException{
|
||||||
|
/** @var string $message */
|
||||||
|
protected $message = 'One or more of the selected benefits require that the user have a password set.';
|
||||||
|
}
|
7
lib/Exceptions/EmailRequiredException.php
Normal file
7
lib/Exceptions/EmailRequiredException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class EmailRequiredException extends AppException{
|
||||||
|
/** @var string $message */
|
||||||
|
protected $message = 'An email is required.';
|
||||||
|
}
|
7
lib/Exceptions/InvalidUserException.php
Normal file
7
lib/Exceptions/InvalidUserException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidUserException extends ValidationException{
|
||||||
|
/** @var string $message */
|
||||||
|
protected $message = 'User is invalid.';
|
||||||
|
}
|
7
lib/Exceptions/InvalidUuidException.php
Normal file
7
lib/Exceptions/InvalidUuidException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class InvalidUuidException extends AppException{
|
||||||
|
/** @var string $message */
|
||||||
|
protected $message = 'Invalid UUID.';
|
||||||
|
}
|
12
lib/Exceptions/SeeOtherException.php
Normal file
12
lib/Exceptions/SeeOtherException.php
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class SeeOtherException extends AppException{
|
||||||
|
public string $Url;
|
||||||
|
|
||||||
|
public function __construct(string $url){
|
||||||
|
$this->Url = $url;
|
||||||
|
|
||||||
|
parent::__construct();
|
||||||
|
}
|
||||||
|
}
|
7
lib/Exceptions/UuidRequiredException.php
Normal file
7
lib/Exceptions/UuidRequiredException.php
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?
|
||||||
|
namespace Exceptions;
|
||||||
|
|
||||||
|
class UuidRequiredException extends AppException{
|
||||||
|
/** @var string $message */
|
||||||
|
protected $message = 'A UUID is required.';
|
||||||
|
}
|
|
@ -42,18 +42,18 @@ class NewsletterSubscription{
|
||||||
public function Create(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
public function Create(?string $expectedCaptcha = null, ?string $receivedCaptcha = null): void{
|
||||||
$this->Validate($expectedCaptcha, $receivedCaptcha);
|
$this->Validate($expectedCaptcha, $receivedCaptcha);
|
||||||
|
|
||||||
// Do we need to create a user?
|
// Do we need to create a `User`?
|
||||||
try{
|
try{
|
||||||
$this->User = User::GetByEmail($this->User->Email);
|
$this->User = User::GetByEmail($this->User->Email);
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserNotFoundException){
|
catch(Exceptions\UserNotFoundException){
|
||||||
// User doesn't exist, create the user
|
// User doesn't exist, create the `User`.
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$this->User->Create();
|
$this->User->Create();
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserExistsException){
|
catch(Exceptions\UserExistsException | Exceptions\InvalidUserException){
|
||||||
// User exists, pass
|
// `User` exists, pass.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class NewsletterSubscription{
|
||||||
throw new Exceptions\NewsletterSubscriptionExistsException();
|
throw new Exceptions\NewsletterSubscriptionExistsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the double opt-in confirmation email
|
// Send the double opt-in confirmation email.
|
||||||
$this->SendConfirmationEmail();
|
$this->SendConfirmationEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,13 +162,27 @@ class NewsletterSubscription{
|
||||||
throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = Db::Query('
|
return Db::Query('
|
||||||
SELECT ns.*
|
SELECT ns.*
|
||||||
from NewsletterSubscriptions ns
|
from NewsletterSubscriptions ns
|
||||||
inner join Users u using(UserId)
|
inner join Users u using(UserId)
|
||||||
where u.Uuid = ?
|
where u.Uuid = ?
|
||||||
', [$uuid], NewsletterSubscription::class);
|
', [$uuid], NewsletterSubscription::class)[0] ?? throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
return $result[0] ?? throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
/**
|
||||||
|
* @throws Exceptions\NewsletterSubscriptionNotFoundException
|
||||||
|
*/
|
||||||
|
public static function GetByUserId(?int $userId): NewsletterSubscription{
|
||||||
|
if($userId === null){
|
||||||
|
throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Db::Query('
|
||||||
|
SELECT ns.*
|
||||||
|
from NewsletterSubscriptions ns
|
||||||
|
inner join Users u using(UserId)
|
||||||
|
where u.UserId = ?
|
||||||
|
', [$userId], NewsletterSubscription::class)[0] ?? throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,18 +45,18 @@ class Payment{
|
||||||
*/
|
*/
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
if($this->UserId === null){
|
if($this->UserId === null){
|
||||||
// Check if we have to create a new user in the database
|
// Check if we have to create a new `User` in the database.
|
||||||
|
|
||||||
// If the User object isn't null, then check if we already have this user in our system
|
// If the `User` isn't **null**, then check if we already have this user in our system.
|
||||||
if($this->User !== null && $this->User->Email !== null){
|
if($this->User !== null && $this->User->Email !== null){
|
||||||
try{
|
try{
|
||||||
$user = User::GetByEmail($this->User->Email);
|
$user = User::GetByEmail($this->User->Email);
|
||||||
|
|
||||||
// User exists, use their data
|
// `User` exists, use their data
|
||||||
$user->Name = $this->User->Name;
|
$user->Name = $this->User->Name;
|
||||||
$this->User = $user;
|
$this->User = $user;
|
||||||
|
|
||||||
// Update their name in case we have their email (but not name) recorded from a newsletter subscription
|
// Update their name in case we have their email (but not name) recorded from a newsletter subscription.
|
||||||
Db::Query('
|
Db::Query('
|
||||||
UPDATE Users
|
UPDATE Users
|
||||||
set Name = ?
|
set Name = ?
|
||||||
|
@ -64,13 +64,13 @@ class Payment{
|
||||||
', [$this->User->Name, $this->User->UserId]);
|
', [$this->User->Name, $this->User->UserId]);
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserNotFoundException){
|
catch(Exceptions\UserNotFoundException){
|
||||||
// User doesn't exist, create it now
|
// User doesn't exist, create it now.
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$this->User->Create();
|
$this->User->Create();
|
||||||
}
|
}
|
||||||
catch(Exceptions\UserExistsException){
|
catch(Exceptions\UserExistsException | Exceptions\InvalidUserException){
|
||||||
// User already exists, pass
|
// `User` already exists, pass.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
194
lib/User.php
194
lib/User.php
|
@ -2,18 +2,26 @@
|
||||||
use Ramsey\Uuid\Uuid;
|
use Ramsey\Uuid\Uuid;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
|
use function Safe\preg_match;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property array<Payment> $Payments
|
* @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 Benefits $Benefits
|
||||||
|
* @property string $Url
|
||||||
|
* @property bool $IsPatron
|
||||||
|
* @property ?Patron $Patron
|
||||||
|
* @property ?NewsletterSubscription $NewsletterSubscription
|
||||||
*/
|
*/
|
||||||
class User{
|
class User{
|
||||||
use Traits\Accessor;
|
use Traits\Accessor;
|
||||||
|
use Traits\PropertyFromHttp;
|
||||||
|
|
||||||
public int $UserId;
|
public int $UserId;
|
||||||
public ?string $Name = null;
|
public ?string $Name = null;
|
||||||
public ?string $Email = null;
|
public ?string $Email = null;
|
||||||
public DateTimeImmutable $Created;
|
public DateTimeImmutable $Created;
|
||||||
|
public DateTimeImmutable $Updated;
|
||||||
public string $Uuid;
|
public string $Uuid;
|
||||||
public ?string $PasswordHash = null;
|
public ?string $PasswordHash = null;
|
||||||
|
|
||||||
|
@ -21,12 +29,60 @@ class User{
|
||||||
/** @var array<Payment> $_Payments */
|
/** @var array<Payment> $_Payments */
|
||||||
protected array $_Payments;
|
protected array $_Payments;
|
||||||
protected Benefits $_Benefits;
|
protected Benefits $_Benefits;
|
||||||
|
protected string $_Url;
|
||||||
|
protected bool $_IsPatron;
|
||||||
|
protected ?Patron $_Patron;
|
||||||
|
protected ?NewsletterSubscription $_NewsletterSubscription;
|
||||||
|
|
||||||
|
|
||||||
// *******
|
// *******
|
||||||
// GETTERS
|
// 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>
|
* @return array<Payment>
|
||||||
*/
|
*/
|
||||||
|
@ -66,7 +122,7 @@ class User{
|
||||||
|
|
||||||
protected function GetIsRegistered(): ?bool{
|
protected function GetIsRegistered(): ?bool{
|
||||||
if(!isset($this->_IsRegistered)){
|
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 function will fill it out for us.
|
||||||
$this->GetBenefits();
|
$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
|
* @throws Exceptions\UserExistsException
|
||||||
*/
|
*/
|
||||||
public function Create(?string $password = null): void{
|
public function Create(?string $password = null): void{
|
||||||
$uuid = Uuid::uuid4();
|
$this->GenerateUuid();
|
||||||
$this->Uuid = $uuid->toString();
|
|
||||||
|
$this->Validate();
|
||||||
|
|
||||||
$this->Created = NOW;
|
$this->Created = NOW;
|
||||||
|
|
||||||
|
@ -110,6 +216,37 @@ class User{
|
||||||
$this->UserId = Db::GetLastInsertedId();
|
$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
|
// ORM METHODS
|
||||||
|
@ -130,6 +267,30 @@ class User{
|
||||||
', [$userId], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
|
', [$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
|
* @throws Exceptions\UserNotFoundException
|
||||||
*/
|
*/
|
||||||
|
@ -145,6 +306,21 @@ class User{
|
||||||
', [$email], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
|
', [$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".
|
* Get a `User` if they are considered "registered".
|
||||||
*
|
*
|
||||||
|
@ -169,7 +345,7 @@ class User{
|
||||||
', [$identifier, $identifier], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
|
', [$identifier, $identifier], User::class)[0] ?? throw new Exceptions\UserNotFoundException();
|
||||||
|
|
||||||
if($user->PasswordHash !== null && $password === null){
|
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();
|
throw new Exceptions\PasswordRequiredException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,4 +355,12 @@ class User{
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function FillFromHttpPost(): void{
|
||||||
|
$this->PropertyFromHttp('Name');
|
||||||
|
$this->PropertyFromHttp('Email');
|
||||||
|
$this->PropertyFromHttp('Uuid');
|
||||||
|
|
||||||
|
$this->Benefits->FillFromHttpPost();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,5 +196,11 @@ $isEditForm = $isEditForm ?? false;
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<div class="footer">
|
<div class="footer">
|
||||||
<button><? if($isEditForm){ ?>Save changes<? }else{ ?>Submit<? } ?></button>
|
<button>
|
||||||
|
<? if($isEditForm){ ?>
|
||||||
|
Save changes
|
||||||
|
<? }else{ ?>
|
||||||
|
Submit
|
||||||
|
<? } ?>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -41,7 +41,7 @@ $digits = str_split(str_pad((string)$current, 3, "0", STR_PAD_LEFT))
|
||||||
?>
|
?>
|
||||||
<aside class="donation counter closable">
|
<aside class="donation counter closable">
|
||||||
<? if($autoHide){ ?>
|
<? if($autoHide){ ?>
|
||||||
<form action="/settings" method="post">
|
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||||
<input type="hidden" name="hide-donation-alert" value="true" />
|
<input type="hidden" name="hide-donation-alert" value="true" />
|
||||||
<button class="close" title="Close this box">Close this box</button>
|
<button class="close" title="Close this box">Close this box</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -43,7 +43,7 @@ else{
|
||||||
?>
|
?>
|
||||||
<aside class="donation closable">
|
<aside class="donation closable">
|
||||||
<? if($autoHide){ ?>
|
<? if($autoHide){ ?>
|
||||||
<form action="/settings" method="post">
|
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||||
<input type="hidden" name="hide-donation-alert" value="true" />
|
<input type="hidden" name="hide-donation-alert" value="true" />
|
||||||
<button class="close" title="Close this box">Close this box</button>
|
<button class="close" title="Close this box">Close this box</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -5,7 +5,6 @@ $title = $title ?? '';
|
||||||
$highlight = $highlight ?? '';
|
$highlight = $highlight ?? '';
|
||||||
$description = $description ?? '';
|
$description = $description ?? '';
|
||||||
$manual = $manual ?? false;
|
$manual = $manual ?? false;
|
||||||
$artwork = $artwork ?? false;
|
|
||||||
$colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
|
$colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
|
||||||
$isXslt = $isXslt ?? false;
|
$isXslt = $isXslt ?? false;
|
||||||
$feedUrl = $feedUrl ?? null;
|
$feedUrl = $feedUrl ?? null;
|
||||||
|
@ -13,11 +12,11 @@ $feedTitle = $feedTitle ?? '';
|
||||||
$isErrorPage = $isErrorPage ?? false;
|
$isErrorPage = $isErrorPage ?? false;
|
||||||
$downloadUrl = $downloadUrl ?? null;
|
$downloadUrl = $downloadUrl ?? null;
|
||||||
$canonicalUrl = $canonicalUrl ?? null;
|
$canonicalUrl = $canonicalUrl ?? null;
|
||||||
|
$css = $css ?? [];
|
||||||
|
|
||||||
// As of Sep 2022, all versions of Safari have a bug where if the page is served as XHTML,
|
// As of Sep. 2022, all versions of Safari have a bug where if the page is served as XHTML, then `<picture>` elements download all `<source>`s instead of the first supported match.
|
||||||
// then <picture> elements download all <source>s instead of the first supported match.
|
// So, we try to detect Safari here, and don't use multiple `<source>` if we find Safari.
|
||||||
// So, we try to detect Safari here, and don't use multiple <source> if we find Safari.
|
// See <https://bugs.webkit.org/show_bug.cgi?id=245411>.
|
||||||
// See https://bugs.webkit.org/show_bug.cgi?id=245411
|
|
||||||
$isSafari = stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'safari') !== false;
|
$isSafari = stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'safari') !== false;
|
||||||
|
|
||||||
if(!$isXslt){
|
if(!$isXslt){
|
||||||
|
@ -56,8 +55,8 @@ if(!$isXslt){
|
||||||
<link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == 'auto'){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
|
<link href="/css/manual-dark.css?version=<?= filemtime(WEB_ROOT . '/css/manual-dark.css') ?>" media="screen<? if($colorScheme == 'auto'){ ?> and (prefers-color-scheme: dark)<? } ?>" rel="stylesheet" type="text/css"/>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<? if($artwork){ ?>
|
<? foreach($css as $url){ ?>
|
||||||
<link href="/css/artwork.css?version=<?= filemtime(WEB_ROOT . '/css/artwork.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
|
<link href="<?= Formatter::EscapeHtml($url) ?>?version=<?= filemtime(WEB_ROOT . $url) ?>" media="screen" rel="stylesheet" type="text/css"/>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<? if($canonicalUrl){ ?>
|
<? if($canonicalUrl){ ?>
|
||||||
<link rel="canonical" href="<?= Formatter::EscapeHtml($canonicalUrl) ?>" />
|
<link rel="canonical" href="<?= Formatter::EscapeHtml($canonicalUrl) ?>" />
|
||||||
|
|
149
templates/UserForm.php
Normal file
149
templates/UserForm.php
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
<?
|
||||||
|
$user = $user ?? new User();
|
||||||
|
$isEditForm = $isEditForm ?? false;
|
||||||
|
$generateNewUuid = $generateNewUuid ?? false;
|
||||||
|
$passwordAction = $passwordAction ?? Enums\PasswordActionType::None;
|
||||||
|
?>
|
||||||
|
|
||||||
|
<label class="email">
|
||||||
|
Email
|
||||||
|
<input
|
||||||
|
type="email"
|
||||||
|
name="user-email"
|
||||||
|
required="required"
|
||||||
|
value="<?= Formatter::EscapeHtml($user->Email) ?>"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="user">
|
||||||
|
Name
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="user-name"
|
||||||
|
value="<?= Formatter::EscapeHtml($user->Name) ?>"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
UUID
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="user-uuid"
|
||||||
|
value="<?= Formatter::EscapeHtml($user->Uuid) ?>"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="generate-new-uuid" value="false" />
|
||||||
|
<input type="checkbox" name="generate-new-uuid" value="true"<? if($generateNewUuid){ ?> checked="checked"<? } ?> />
|
||||||
|
Generate a new UUID
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="password-action" value="<?= Enums\PasswordActionType::None->value ?>"<? if($passwordAction == Enums\PasswordActionType::None){ ?> checked="checked"<? } ?> />Don’t change password
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<? if($user->PasswordHash === null){ ?>
|
||||||
|
<li>
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="password-action" value="<?= Enums\PasswordActionType::Edit->value ?>"<? if($passwordAction == Enums\PasswordActionType::Edit){ ?> checked="checked"<? } ?> />Create a new password
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="user-password"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</li>
|
||||||
|
<? }else{ ?>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="password-action" value="<?= Enums\PasswordActionType::Delete->value ?>"<? if($passwordAction == Enums\PasswordActionType::Delete){ ?> checked="checked"<? } ?> />Remove password
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<fieldset>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="password-action" value="<?= Enums\PasswordActionType::Edit->value ?>"<? if($passwordAction == Enums\PasswordActionType::Edit){ ?> checked="checked"<? } ?> />Change password
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="user-password"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</fieldset>
|
||||||
|
</li>
|
||||||
|
<? } ?>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
<fieldset>
|
||||||
|
<legend>Benefits</legend>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-access-feeds" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-access-feeds" value="true"<? if($user->Benefits->CanAccessFeeds){ ?> checked="checked"<? } ?> />
|
||||||
|
Can access feeds
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-vote" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-vote" value="true"<? if($user->Benefits->CanVote){ ?> checked="checked"<? } ?> />
|
||||||
|
Can vote in polls
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-bulk-download" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-bulk-download" value="true"<? if($user->Benefits->CanBulkDownload){ ?> checked="checked"<? } ?> />
|
||||||
|
Can access bulk downloads
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-upload-artwork" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-upload-artwork" value="true"<? if($user->Benefits->CanUploadArtwork){ ?> checked="checked"<? } ?> />
|
||||||
|
Can upload artwork
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-review-artwork" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-review-artwork" value="true"<? if($user->Benefits->CanReviewArtwork){ ?> checked="checked"<? } ?> />
|
||||||
|
Can review artwork
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-review-own-artwork" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-review-own-artwork" value="true"<? if($user->Benefits->CanReviewOwnArtwork){ ?> checked="checked"<? } ?> />
|
||||||
|
Can review own artwork
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-edit-users" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-edit-users" value="true"<? if($user->Benefits->CanEditUsers){ ?> checked="checked"<? } ?> />
|
||||||
|
Can edit users
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<button>
|
||||||
|
<? if($isEditForm){ ?>
|
||||||
|
Save changes
|
||||||
|
<? }else{ ?>
|
||||||
|
Submit
|
||||||
|
<? } ?>
|
||||||
|
</button>
|
||||||
|
</div>
|
|
@ -23,7 +23,7 @@ try{
|
||||||
catch(Exceptions\ArtistNotFoundException){
|
catch(Exceptions\ArtistNotFoundException){
|
||||||
Template::Emit404();
|
Template::Emit404();
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => 'Artwork by ' . $artworks[0]->Artist->Name, 'artwork' => true]) ?>
|
?><?= Template::Header(['title' => 'Artwork by ' . $artworks[0]->Artist->Name, 'css' => ['/css/artwork.css']]) ?>
|
||||||
<main class="artworks">
|
<main class="artworks">
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>Artwork by <?= Formatter::EscapeHtml($artworks[0]->Artist->Name) ?></h1>
|
<h1>Artwork by <?= Formatter::EscapeHtml($artworks[0]->Artist->Name) ?></h1>
|
||||||
|
|
|
@ -3,10 +3,8 @@ use function Safe\session_unset;
|
||||||
|
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
/** @var ?\Exception $exception */
|
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
|
||||||
/** @var ?Artwork $artwork */
|
|
||||||
$artwork = $_SESSION['artwork'] ?? null;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if(Session::$User === null){
|
if(Session::$User === null){
|
||||||
|
@ -41,7 +39,7 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<?= Template::Header(
|
<?= Template::Header(
|
||||||
[
|
[
|
||||||
'title' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name,
|
'title' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name,
|
||||||
'artwork' => true,
|
'css' => ['/css/artwork.css'],
|
||||||
'highlight' => '',
|
'highlight' => '',
|
||||||
'description' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name . ' in the Standard Ebooks cover art database.'
|
'description' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name . ' in the Standard Ebooks cover art database.'
|
||||||
]
|
]
|
||||||
|
@ -57,8 +55,8 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<img src="<?= $artwork->ThumbUrl ?>" alt="" property="schema:image"/>
|
<img src="<?= $artwork->ThumbUrl ?>" alt="" property="schema:image"/>
|
||||||
</picture>
|
</picture>
|
||||||
|
|
||||||
<form class="create-update-artwork" method="post" action="<?= $artwork->Url ?>" enctype="multipart/form-data" autocomplete="off">
|
<form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $artwork->Url ?>" enctype="multipart/form-data" autocomplete="off">
|
||||||
<input type="hidden" name="_method" value="PUT" />
|
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" />
|
||||||
<?= Template::ArtworkForm(['artwork' => $artwork, 'isEditForm' => true]) ?>
|
<?= Template::ArtworkForm(['artwork' => $artwork, 'isEditForm' => true]) ?>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -4,8 +4,7 @@ use function Safe\session_unset;
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$isSaved = HttpInput::Bool(SESSION, 'is-artwork-saved') ?? false;
|
$isSaved = HttpInput::Bool(SESSION, 'is-artwork-saved') ?? false;
|
||||||
/** @var ?\Exception $exception */
|
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
try{
|
try{
|
||||||
|
@ -65,7 +64,7 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
Template::Emit403();
|
Template::Emit403();
|
||||||
}
|
}
|
||||||
|
|
||||||
?><?= Template::Header(['title' => $artwork->Name, 'artwork' => true]) ?>
|
?><?= Template::Header(['title' => $artwork->Name, 'css' => ['/css/artwork.css']]) ?>
|
||||||
<main class="artworks">
|
<main class="artworks">
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1><?= Formatter::EscapeHtml($artwork->Name) ?></h1>
|
<h1><?= Formatter::EscapeHtml($artwork->Name) ?></h1>
|
||||||
|
@ -173,8 +172,8 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<? if($artwork->CanStatusBeChangedBy(Session::$User)){ ?>
|
<? if($artwork->CanStatusBeChangedBy(Session::$User)){ ?>
|
||||||
<p>Review the metadata and PD proof for this artwork submission. Approve to make it available for future producers. Once an artwork is approved, it can no longer be edited.</p>
|
<p>Review the metadata and PD proof for this artwork submission. Approve to make it available for future producers. Once an artwork is approved, it can no longer be edited.</p>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<form method="post" action="<?= $artwork->Url ?>" autocomplete="off">
|
<form method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $artwork->Url ?>" autocomplete="off">
|
||||||
<input type="hidden" name="_method" value="PATCH" />
|
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
|
||||||
<? if($artwork->CanStatusBeChangedBy(Session::$User)){ ?>
|
<? if($artwork->CanStatusBeChangedBy(Session::$User)){ ?>
|
||||||
<label>
|
<label>
|
||||||
<span>Artwork approval status</span>
|
<span>Artwork approval status</span>
|
||||||
|
|
|
@ -135,7 +135,7 @@ catch(Exceptions\PageOutOfBoundsException){
|
||||||
header('Location: ' . $url);
|
header('Location: ' . $url);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => $pageTitle, 'artwork' => true, 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?>
|
?><?= Template::Header(['title' => $pageTitle, 'css' => ['/css/artwork.css'], 'description' => $pageDescription, 'canonicalUrl' => $canonicalUrl]) ?>
|
||||||
<main class="artworks">
|
<main class="artworks">
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>Browse U.S. Public Domain Artwork</h1>
|
<h1>Browse U.S. Public Domain Artwork</h1>
|
||||||
|
|
|
@ -4,10 +4,8 @@ use function Safe\session_unset;
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$isCreated = HttpInput::Bool(SESSION, 'is-artwork-created') ?? false;
|
$isCreated = HttpInput::Bool(SESSION, 'is-artwork-created') ?? false;
|
||||||
/** @var ?\Exception $exception */
|
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
|
||||||
/** @var ?Artwork $artwork */
|
|
||||||
$artwork = $_SESSION['artwork'] ?? null;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if(Session::$User === null){
|
if(Session::$User === null){
|
||||||
|
@ -51,7 +49,7 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<?= Template::Header(
|
<?= Template::Header(
|
||||||
[
|
[
|
||||||
'title' => 'Submit an Artwork',
|
'title' => 'Submit an Artwork',
|
||||||
'artwork' => true,
|
'css' => ['/css/artwork.css'],
|
||||||
'highlight' => '',
|
'highlight' => '',
|
||||||
'description' => 'Submit public domain artwork to the database for use as cover art.'
|
'description' => 'Submit public domain artwork to the database for use as cover art.'
|
||||||
]
|
]
|
||||||
|
@ -66,7 +64,7 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<p class="message success">Artwork submitted!</p>
|
<p class="message success">Artwork submitted!</p>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
||||||
<form class="create-update-artwork" method="post" action="/artworks" enctype="multipart/form-data" autocomplete="off">
|
<form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="/artworks" enctype="multipart/form-data" autocomplete="off">
|
||||||
<?= Template::ArtworkForm(['artwork' => $artwork]) ?>
|
<?= Template::ArtworkForm(['artwork' => $artwork]) ?>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -835,7 +835,7 @@ input[type="number"]:focus,
|
||||||
input[type="url"]:focus,
|
input[type="url"]:focus,
|
||||||
input[type="search"]:focus,
|
input[type="search"]:focus,
|
||||||
input[type="file"]:focus,
|
input[type="file"]:focus,
|
||||||
label.checkbox:focus-within,
|
label:has(input[type="radio"]):focus-within,
|
||||||
label:has(input[type="checkbox"]):focus-within,
|
label:has(input[type="checkbox"]):focus-within,
|
||||||
select:focus,
|
select:focus,
|
||||||
button:focus,
|
button:focus,
|
||||||
|
@ -1829,20 +1829,24 @@ label span + span i{
|
||||||
|
|
||||||
input[type="file"],
|
input[type="file"],
|
||||||
label:has(input[type="file"]),
|
label:has(input[type="file"]),
|
||||||
|
label:has(input[type="radio"]),
|
||||||
label:has(input[type="checkbox"]){
|
label:has(input[type="checkbox"]){
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label + label:has(input[type="radio"]),
|
||||||
label + label:has(input[type="checkbox"]){
|
label + label:has(input[type="checkbox"]){
|
||||||
margin-top: 1px; /* So we can see the top outline on focus */
|
margin-top: 1px; /* So we can see the top outline on focus */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label:has(input[type="radio"]):has(> span),
|
||||||
label:has(input[type="checkbox"]):has(> span){
|
label:has(input[type="checkbox"]):has(> span){
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr;
|
grid-template-columns: auto 1fr;
|
||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label:has(input[type="radio"]):has(> span) input,
|
||||||
label:has(input[type="checkbox"]):has(> span) input{
|
label:has(input[type="checkbox"]):has(> span) input{
|
||||||
grid-row: 1 / span 2;
|
grid-row: 1 / span 2;
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
|
@ -1897,6 +1901,7 @@ label.tags,
|
||||||
label.picture,
|
label.picture,
|
||||||
label.user,
|
label.user,
|
||||||
label:has(input[type="password"]){
|
label:has(input[type="password"]){
|
||||||
|
display: block;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -2483,10 +2488,12 @@ fieldset{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset p{
|
fieldset p,
|
||||||
|
fieldset legend{
|
||||||
border-bottom: 1px dashed var(--input-border);
|
border-bottom: 1px dashed var(--input-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldset legend,
|
||||||
fieldset p:has(+ ul){
|
fieldset p:has(+ ul){
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
@ -2495,7 +2502,8 @@ input[type="checkbox"] + span:has(+ span){
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox{
|
label:has(input[type="radio"]),
|
||||||
|
label:has(input[type="checkbox"]){
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
@ -2503,20 +2511,24 @@ label.checkbox{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox input{
|
label:has(input[type="radio"]) input,
|
||||||
|
label:has(input[type="checkbox"]) input{
|
||||||
margin-right: .25rem;
|
margin-right: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox span{
|
label:has(input[type="radio"]) span,
|
||||||
|
label:has(input[type="checkbox"]) span{
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox span > span{
|
label:has(input[type="radio"]) span > span,
|
||||||
|
label:has(input[type="checkbox"]) span > span{
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin-top: .25rem;
|
margin-top: .25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
label.checkbox span{
|
label:has(input[type="radio"]) span,
|
||||||
|
label:has(input[type="checkbox"]) span{
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
34
www/css/user.css
Normal file
34
www/css/user.css
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
label:has(input[name="password-action"]:not(:checked)) + label{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:has(input[name="password-action"]) + label{
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td{
|
||||||
|
padding: .5rem;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:first-child{
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td + td{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
form{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ if($exception){
|
||||||
|
|
||||||
<?= Template::Error(['exception' => $exception]) ?>
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
|
|
||||||
<form action="/newsletter/subscriptions" method="post">
|
<form action="/newsletter/subscriptions" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||||
<label class="automation-test"><? /* Test for spam bots filling out all fields */ ?>
|
<label class="automation-test"><? /* Test for spam bots filling out all fields */ ?>
|
||||||
<input type="text" name="automationtest" value="" maxlength="80" />
|
<input type="text" name="automationtest" value="" maxlength="80" />
|
||||||
</label>
|
</label>
|
||||||
|
@ -47,10 +47,10 @@ if($exception){
|
||||||
<p>What kind of email would you like to receive?</p>
|
<p>What kind of email would you like to receive?</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<label class="checkbox"><input type="checkbox" value="1" name="issubscribedtonewsletter"<? if($subscription->IsSubscribedToNewsletter){ ?> checked="checked"<? } ?> />The occasional Standard Ebooks newsletter</label>
|
<label><input type="checkbox" value="1" name="issubscribedtonewsletter"<? if($subscription->IsSubscribedToNewsletter){ ?> checked="checked"<? } ?> />The occasional Standard Ebooks newsletter</label>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label class="checkbox"><input type="checkbox" value="1" name="issubscribedtosummary"<? if($subscription->IsSubscribedToSummary){ ?> checked="checked"<? } ?> />A monthly summary of new ebook releases</label>
|
<label><input type="checkbox" value="1" name="issubscribedtosummary"<? if($subscription->IsSubscribedToSummary){ ?> checked="checked"<? } ?> />A monthly summary of new ebook releases</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -4,20 +4,14 @@ use function Safe\session_unset;
|
||||||
session_start();
|
session_start();
|
||||||
|
|
||||||
$poll = new Poll();
|
$poll = new Poll();
|
||||||
$vote = new PollVote();
|
$vote = HttpInput::SessionObject('vote', PollVote::class) ?? new PollVote();
|
||||||
/** @var ?\Exception $exception */
|
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||||
$exception = $_SESSION['exception'] ?? null;
|
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if(Session::$User === null){
|
if(Session::$User === null){
|
||||||
throw new Exceptions\LoginRequiredException();
|
throw new Exceptions\LoginRequiredException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($_SESSION['vote'])){
|
|
||||||
/** @var PollVote $vote */
|
|
||||||
$vote = $_SESSION['vote'];
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!isset($vote->UserId)){
|
if(!isset($vote->UserId)){
|
||||||
$vote->UserId = Session::$User->UserId;
|
$vote->UserId = Session::$User->UserId;
|
||||||
$vote->User = Session::$User;
|
$vote->User = Session::$User;
|
||||||
|
@ -60,7 +54,7 @@ catch(Exceptions\PollVoteExistsException $ex){
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>Vote in the <?= Formatter::EscapeHtml($poll->Name) ?> Poll</h1>
|
<h1>Vote in the <?= Formatter::EscapeHtml($poll->Name) ?> Poll</h1>
|
||||||
<?= Template::Error(['exception' => $exception]) ?>
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
<form method="post" action="<?= Formatter::EscapeHtml($poll->Url) ?>/votes">
|
<form method="<?= Enums\HttpMethod::Post->value ?>" action="<?= Formatter::EscapeHtml($poll->Url) ?>/votes">
|
||||||
<input type="hidden" name="email" value="<?= Formatter::EscapeHtml($vote->User->Email) ?>" maxlength="80" required="required" />
|
<input type="hidden" name="email" value="<?= Formatter::EscapeHtml($vote->User->Email) ?>" maxlength="80" required="required" />
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<p>Select one of these options.</p>
|
<p>Select one of these options.</p>
|
||||||
|
|
|
@ -39,7 +39,7 @@ if($exception){
|
||||||
<p>Anyone can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
|
<p>Anyone can <a href="/donate#patrons-circle">join the Patrons Circle</a> with a small donation in support of our continuing mission to create free, beautiful digital literature.</p>
|
||||||
<p><strong>Important:</strong> When making your donation, you must have selected either “List my name publicly” or “Don’t list publicly, but reveal to project” on the donation form; otherwise, your email address isn’t shared with us, and we can’t include you in our login system.</p>
|
<p><strong>Important:</strong> When making your donation, you must have selected either “List my name publicly” or “Don’t list publicly, but reveal to project” on the donation form; otherwise, your email address isn’t shared with us, and we can’t include you in our login system.</p>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<form method="post" action="/sessions" class="single-row">
|
<form method="<?= Enums\HttpMethod::Post->value ?>" action="/sessions" class="single-row">
|
||||||
<input type="hidden" name="redirect" value="<?= Formatter::EscapeHtml($redirect) ?>" />
|
<input type="hidden" name="redirect" value="<?= Formatter::EscapeHtml($redirect) ?>" />
|
||||||
<? if($passwordRequired){ ?>
|
<? if($passwordRequired){ ?>
|
||||||
<input type="hidden" name="email" value="<?= Formatter::EscapeHtml($email) ?>" maxlength="80" required="required" />
|
<input type="hidden" name="email" value="<?= Formatter::EscapeHtml($email) ?>" maxlength="80" required="required" />
|
||||||
|
|
|
@ -4,7 +4,7 @@ $colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
|
||||||
?><?= Template::Header(['title' => 'Website Settings', 'description' => 'Adjust your settings for viewing the Standard Ebooks website.']) ?>
|
?><?= Template::Header(['title' => 'Website Settings', 'description' => 'Adjust your settings for viewing the Standard Ebooks website.']) ?>
|
||||||
<main>
|
<main>
|
||||||
<h1>Website Settings</h1>
|
<h1>Website Settings</h1>
|
||||||
<form action="/settings" method="post">
|
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||||
<label>
|
<label>
|
||||||
<span>Color scheme</span>
|
<span>Color scheme</span>
|
||||||
<span>
|
<span>
|
||||||
|
|
59
www/users/edit.php
Normal file
59
www/users/edit.php
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<?
|
||||||
|
use function Safe\session_unset;
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||||
|
$user = HttpInput::SessionObject('user', User::class);
|
||||||
|
$generateNewUuid = HttpInput::Bool(SESSION, 'generate-new-uuid') ?? false;
|
||||||
|
$passwordAction = HttpInput::SessionObject('password-action', Enums\PasswordActionType::class) ?? Enums\PasswordActionType::None;
|
||||||
|
|
||||||
|
try{
|
||||||
|
if($user === null){
|
||||||
|
$user = User::Get(HttpInput::Int(GET, 'user-id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Session::$User === null){
|
||||||
|
throw new Exceptions\LoginRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Session::$User->Benefits->CanReviewOwnArtwork){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got here because a `User` update had errors and the user has to try again.
|
||||||
|
if($exception){
|
||||||
|
http_response_code(Enums\HttpCode::UnprocessableContent->value);
|
||||||
|
session_unset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\UserNotFoundException){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
catch(Exceptions\LoginRequiredException){
|
||||||
|
Template::RedirectToLogin();
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
|
Template::Emit403(); // No permissions to edit artwork.
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<?= Template::Header(
|
||||||
|
[
|
||||||
|
'title' => 'Edit user #' . $user->UserId,
|
||||||
|
'css' => ['/css/user.css'],
|
||||||
|
'highlight' => ''
|
||||||
|
]
|
||||||
|
) ?>
|
||||||
|
<main>
|
||||||
|
<section class="narrow">
|
||||||
|
<h1>Edit User #<?= $user->UserId ?></h1>
|
||||||
|
|
||||||
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
|
|
||||||
|
<form class="create-update-artwork" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $user->Url ?>" autocomplete="off">
|
||||||
|
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
|
||||||
|
<?= Template::UserForm(['user' => $user, 'isEditForm' => true, 'generateNewUuid' => $generateNewUuid, 'passwordAction' => $passwordAction]) ?>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
157
www/users/get.php
Normal file
157
www/users/get.php
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
<?
|
||||||
|
use function Safe\session_unset;
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
$isSaved = HttpInput::Bool(SESSION, 'is-user-saved') ?? false;
|
||||||
|
|
||||||
|
try{
|
||||||
|
$user = User::GetByIdentifier(HttpInput::Str(GET, 'user-identifier'));
|
||||||
|
|
||||||
|
// Even though the identifier can be either an email, user ID, or UUID, we want the URL of this page to be based on a user ID only.
|
||||||
|
if(!ctype_digit(HttpInput::Str(GET, 'user-identifier'))){
|
||||||
|
throw new Exceptions\SeeOtherException($user->Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Session::$User === null){
|
||||||
|
throw new Exceptions\LoginRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Session::$User->Benefits->CanEditUsers){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// We got here because a `User` was successfully saved.
|
||||||
|
if($isSaved){
|
||||||
|
session_unset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\UserNotFoundException){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
catch(Exceptions\LoginRequiredException){
|
||||||
|
Template::RedirectToLogin();
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
|
Template::Emit403();
|
||||||
|
}
|
||||||
|
catch(Exceptions\SeeOtherException $ex){
|
||||||
|
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||||
|
header('Location: ' . $ex->Url);
|
||||||
|
}
|
||||||
|
|
||||||
|
?><?= Template::Header(['title' => 'User #' . $user->UserId, 'css' => ['/css/user.css']]) ?>
|
||||||
|
<main>
|
||||||
|
<section class="narrow">
|
||||||
|
<h1>User #<?= $user->UserId ?></h1>
|
||||||
|
|
||||||
|
<? if($isSaved){ ?>
|
||||||
|
<p class="message success">User saved!</p>
|
||||||
|
<? } ?>
|
||||||
|
|
||||||
|
<a href="<?= $user->Url ?>/edit">Edit user</a>
|
||||||
|
|
||||||
|
<h2>Basics</h2>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Email:</td>
|
||||||
|
<td><?= Formatter::EscapeHtml($user->Email) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Name:</td>
|
||||||
|
<td><?= Formatter::EscapeHtml($user->Name) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>UUID:</td>
|
||||||
|
<td><?= Formatter::EscapeHtml($user->Uuid) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Created:</td>
|
||||||
|
<td><?= $user->Created->Format(Enums\DateTimeFormat::FullDateTime->value) ?></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Patron info</h2>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Is Patron:</td>
|
||||||
|
<td><? if($user->IsPatron){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? if($user->IsPatron && $user->Patron !== null){ ?>
|
||||||
|
<tr>
|
||||||
|
<td>Created:</td>
|
||||||
|
<td><?= $user->Patron->Created->format(Enums\DateTimeFormat::FullDateTime->value) ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Is anonymous:</td>
|
||||||
|
<td><? if($user->Patron->IsAnonymous){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? if($user->Patron->AlternateName !== null){ ?>
|
||||||
|
<tr>
|
||||||
|
<td>Alternate credit:</td>
|
||||||
|
<td><?= Formatter::EscapeHtml($user->Patron->AlternateName) ?></td>
|
||||||
|
</tr>
|
||||||
|
<? } ?>
|
||||||
|
<? } ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2>Newsletter subscriptions</h2>
|
||||||
|
<? if($user->NewsletterSubscription === null || (!$user->NewsletterSubscription->IsSubscribedToNewsletter && !$user->NewsletterSubscription->IsSubscribedToSummary)){ ?>
|
||||||
|
<p>None.</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<ul>
|
||||||
|
<? if($user->NewsletterSubscription->IsSubscribedToNewsletter){ ?>
|
||||||
|
<li>General newsletter</li>
|
||||||
|
<? } ?>
|
||||||
|
<? if($user->NewsletterSubscription->IsSubscribedToSummary){ ?>
|
||||||
|
<li>Monthly summary newsletter</li>
|
||||||
|
<? } ?>
|
||||||
|
</ul>
|
||||||
|
<? } ?>
|
||||||
|
|
||||||
|
<h2>Registration info</h2>
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Is registered:</td>
|
||||||
|
<td><? if($user->IsRegistered){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? if($user->IsRegistered){ ?>
|
||||||
|
<tr>
|
||||||
|
<td>Can access feeds:</td>
|
||||||
|
<td><? if($user->Benefits->CanAccessFeeds){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can vote:</td>
|
||||||
|
<td><? if($user->Benefits->CanVote){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can bulk download:</td>
|
||||||
|
<td><? if($user->Benefits->CanBulkDownload){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can upload artwork:</td>
|
||||||
|
<td><? if($user->Benefits->CanUploadArtwork){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can review artwork:</td>
|
||||||
|
<td><? if($user->Benefits->CanReviewArtwork){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can review own artwork:</td>
|
||||||
|
<td><? if($user->Benefits->CanReviewOwnArtwork){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can edit users:</td>
|
||||||
|
<td><? if($user->Benefits->CanEditUsers){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? } ?>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
83
www/users/post.php
Normal file
83
www/users/post.php
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
<?
|
||||||
|
|
||||||
|
try{
|
||||||
|
session_start();
|
||||||
|
$httpMethod = HttpInput::ValidateRequestMethod([Enums\HttpMethod::Patch]);
|
||||||
|
$exceptionRedirectUrl = '/users';
|
||||||
|
|
||||||
|
$user = User::Get(HttpInput::Int(GET, 'user-id'));
|
||||||
|
|
||||||
|
if(Session::$User === null){
|
||||||
|
throw new Exceptions\LoginRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!Session::$User->Benefits->CanEditUsers){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
// PATCHing a `User`.
|
||||||
|
if($httpMethod == Enums\HttpMethod::Patch){
|
||||||
|
$exceptionRedirectUrl = $user->Url . '/edit';
|
||||||
|
|
||||||
|
$user->FillFromHttpPost();
|
||||||
|
|
||||||
|
$generateNewUuid = HttpInput::Bool(POST, 'generate-new-uuid') ?? false;
|
||||||
|
|
||||||
|
if($generateNewUuid){
|
||||||
|
$oldUuid = $user->Uuid;
|
||||||
|
$user->GenerateUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
$passwordAction = Enums\PasswordActionType::tryFrom(HttpInput::Str(POST, 'password-action') ?? '') ?? Enums\PasswordActionType::None;
|
||||||
|
$oldPasswordHash = $user->PasswordHash;
|
||||||
|
|
||||||
|
switch($passwordAction){
|
||||||
|
case Enums\PasswordActionType::Delete:
|
||||||
|
$user->PasswordHash = null;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Enums\PasswordActionType::Edit:
|
||||||
|
$password = HttpInput::Str(POST, 'user-password');
|
||||||
|
|
||||||
|
if($password !== null){
|
||||||
|
$user->PasswordHash = password_hash($password, PASSWORD_DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->Save();
|
||||||
|
|
||||||
|
$_SESSION['is-user-saved'] = true;
|
||||||
|
|
||||||
|
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||||
|
header('Location: ' . $user->Url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exceptions\LoginRequiredException){
|
||||||
|
Template::RedirectToLogin();
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
|
Template::Emit403();
|
||||||
|
}
|
||||||
|
catch(Exceptions\UserNotFoundException){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidUserException | Exceptions\UserExistsException $ex){
|
||||||
|
if($generateNewUuid){
|
||||||
|
$user->Uuid = $oldUuid;
|
||||||
|
$_SESSION['generate-new-uuid'] = $generateNewUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['password-action'] = $passwordAction;
|
||||||
|
|
||||||
|
if($ex instanceof Exceptions\InvalidUserException && $ex->Has(Exceptions\BenefitsRequirePasswordException::class)){
|
||||||
|
$user->PasswordHash = $oldPasswordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
$_SESSION['user'] = $user;
|
||||||
|
$_SESSION['exception'] = $ex;
|
||||||
|
|
||||||
|
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||||
|
header('Location: ' . $exceptionRedirectUrl);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue