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}/artworks.conf
|
||||
Include ${conf_rewrite_root}/polls.conf
|
||||
Include ${conf_rewrite_root}/users.conf
|
||||
|
||||
# Specific config for /ebooks/<author>/<ebook>/downloads
|
||||
<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}/artworks.conf
|
||||
Include ${conf_rewrite_root}/polls.conf
|
||||
Include ${conf_rewrite_root}/users.conf
|
||||
|
||||
# Specific config for /ebooks/<author>/<ebook>/downloads
|
||||
<DirectoryMatch "^${web_root}/www/ebooks/.+">
|
||||
|
|
|
@ -6,6 +6,7 @@ CREATE TABLE IF NOT EXISTS `Benefits` (
|
|||
`CanUploadArtwork` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||
`CanReviewArtwork` 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`),
|
||||
KEY `idxBenefits` (`CanAccessFeeds`,`CanVote`,`CanBulkDownload`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
|
|
@ -3,8 +3,13 @@ CREATE TABLE IF NOT EXISTS `Users` (
|
|||
`Email` varchar(80) DEFAULT NULL,
|
||||
`Name` varchar(255) DEFAULT NULL,
|
||||
`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()),
|
||||
`PasswordHash` varchar(255) NULL,
|
||||
PRIMARY KEY (`UserId`),
|
||||
UNIQUE KEY `idxEmail` (`Email`)
|
||||
UNIQUE KEY `idxEmail` (`Email`,`Uuid`,`UserId`),
|
||||
UNIQUE KEY `idxUniqueEmail` (`Email`)
|
||||
) 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{
|
||||
use Traits\Accessor;
|
||||
use Traits\PropertyFromHttp;
|
||||
|
||||
public int $UserId;
|
||||
public bool $CanAccessFeeds = false;
|
||||
public bool $CanVote = false;
|
||||
public bool $CanBulkDownload = false;
|
||||
public bool $CanUploadArtwork = false;
|
||||
public bool $CanReviewArtwork = 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{
|
||||
$this->Validate($expectedCaptcha, $receivedCaptcha);
|
||||
|
||||
// Do we need to create a user?
|
||||
// Do we need to create a `User`?
|
||||
try{
|
||||
$this->User = User::GetByEmail($this->User->Email);
|
||||
}
|
||||
catch(Exceptions\UserNotFoundException){
|
||||
// User doesn't exist, create the user
|
||||
// User doesn't exist, create the `User`.
|
||||
|
||||
try{
|
||||
$this->User->Create();
|
||||
}
|
||||
catch(Exceptions\UserExistsException){
|
||||
// User exists, pass
|
||||
catch(Exceptions\UserExistsException | Exceptions\InvalidUserException){
|
||||
// `User` exists, pass.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ class NewsletterSubscription{
|
|||
throw new Exceptions\NewsletterSubscriptionExistsException();
|
||||
}
|
||||
|
||||
// Send the double opt-in confirmation email
|
||||
// Send the double opt-in confirmation email.
|
||||
$this->SendConfirmationEmail();
|
||||
}
|
||||
|
||||
|
@ -162,13 +162,27 @@ class NewsletterSubscription{
|
|||
throw new Exceptions\NewsletterSubscriptionNotFoundException();
|
||||
}
|
||||
|
||||
$result = Db::Query('
|
||||
return Db::Query('
|
||||
SELECT ns.*
|
||||
from NewsletterSubscriptions ns
|
||||
inner join Users u using(UserId)
|
||||
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{
|
||||
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){
|
||||
try{
|
||||
$user = User::GetByEmail($this->User->Email);
|
||||
|
||||
// User exists, use their data
|
||||
// `User` exists, use their data
|
||||
$user->Name = $this->User->Name;
|
||||
$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('
|
||||
UPDATE Users
|
||||
set Name = ?
|
||||
|
@ -64,13 +64,13 @@ class Payment{
|
|||
', [$this->User->Name, $this->User->UserId]);
|
||||
}
|
||||
catch(Exceptions\UserNotFoundException){
|
||||
// User doesn't exist, create it now
|
||||
// User doesn't exist, create it now.
|
||||
|
||||
try{
|
||||
$this->User->Create();
|
||||
}
|
||||
catch(Exceptions\UserExistsException){
|
||||
// User already exists, pass
|
||||
catch(Exceptions\UserExistsException | Exceptions\InvalidUserException){
|
||||
// `User` already exists, pass.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
194
lib/User.php
194
lib/User.php
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -196,5 +196,11 @@ $isEditForm = $isEditForm ?? false;
|
|||
</fieldset>
|
||||
<? } ?>
|
||||
<div class="footer">
|
||||
<button><? if($isEditForm){ ?>Save changes<? }else{ ?>Submit<? } ?></button>
|
||||
<button>
|
||||
<? if($isEditForm){ ?>
|
||||
Save changes
|
||||
<? }else{ ?>
|
||||
Submit
|
||||
<? } ?>
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -41,7 +41,7 @@ $digits = str_split(str_pad((string)$current, 3, "0", STR_PAD_LEFT))
|
|||
?>
|
||||
<aside class="donation counter closable">
|
||||
<? if($autoHide){ ?>
|
||||
<form action="/settings" method="post">
|
||||
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||
<input type="hidden" name="hide-donation-alert" value="true" />
|
||||
<button class="close" title="Close this box">Close this box</button>
|
||||
</form>
|
||||
|
|
|
@ -43,7 +43,7 @@ else{
|
|||
?>
|
||||
<aside class="donation closable">
|
||||
<? if($autoHide){ ?>
|
||||
<form action="/settings" method="post">
|
||||
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||
<input type="hidden" name="hide-donation-alert" value="true" />
|
||||
<button class="close" title="Close this box">Close this box</button>
|
||||
</form>
|
||||
|
|
|
@ -5,7 +5,6 @@ $title = $title ?? '';
|
|||
$highlight = $highlight ?? '';
|
||||
$description = $description ?? '';
|
||||
$manual = $manual ?? false;
|
||||
$artwork = $artwork ?? false;
|
||||
$colorScheme = $_COOKIE['color-scheme'] ?? 'auto';
|
||||
$isXslt = $isXslt ?? false;
|
||||
$feedUrl = $feedUrl ?? null;
|
||||
|
@ -13,11 +12,11 @@ $feedTitle = $feedTitle ?? '';
|
|||
$isErrorPage = $isErrorPage ?? false;
|
||||
$downloadUrl = $downloadUrl ?? 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,
|
||||
// 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.
|
||||
// See https://bugs.webkit.org/show_bug.cgi?id=245411
|
||||
// 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.
|
||||
// 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>.
|
||||
$isSafari = stripos($_SERVER['HTTP_USER_AGENT'] ?? '', 'safari') !== false;
|
||||
|
||||
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"/>
|
||||
<? } ?>
|
||||
<? } ?>
|
||||
<? if($artwork){ ?>
|
||||
<link href="/css/artwork.css?version=<?= filemtime(WEB_ROOT . '/css/artwork.css') ?>" media="screen" rel="stylesheet" type="text/css"/>
|
||||
<? foreach($css as $url){ ?>
|
||||
<link href="<?= Formatter::EscapeHtml($url) ?>?version=<?= filemtime(WEB_ROOT . $url) ?>" media="screen" rel="stylesheet" type="text/css"/>
|
||||
<? } ?>
|
||||
<? if($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){
|
||||
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">
|
||||
<section class="narrow">
|
||||
<h1>Artwork by <?= Formatter::EscapeHtml($artworks[0]->Artist->Name) ?></h1>
|
||||
|
|
|
@ -3,10 +3,8 @@ use function Safe\session_unset;
|
|||
|
||||
session_start();
|
||||
|
||||
/** @var ?\Exception $exception */
|
||||
$exception = $_SESSION['exception'] ?? null;
|
||||
/** @var ?Artwork $artwork */
|
||||
$artwork = $_SESSION['artwork'] ?? null;
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
|
||||
|
||||
try{
|
||||
if(Session::$User === null){
|
||||
|
@ -41,7 +39,7 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
<?= Template::Header(
|
||||
[
|
||||
'title' => 'Edit ' . $artwork->Name . ', by ' . $artwork->Artist->Name,
|
||||
'artwork' => true,
|
||||
'css' => ['/css/artwork.css'],
|
||||
'highlight' => '',
|
||||
'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"/>
|
||||
</picture>
|
||||
|
||||
<form class="create-update-artwork" method="post" action="<?= $artwork->Url ?>" enctype="multipart/form-data" autocomplete="off">
|
||||
<input type="hidden" name="_method" value="PUT" />
|
||||
<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="<?= Enums\HttpMethod::Put->value ?>" />
|
||||
<?= Template::ArtworkForm(['artwork' => $artwork, 'isEditForm' => true]) ?>
|
||||
</form>
|
||||
</section>
|
||||
|
|
|
@ -4,8 +4,7 @@ use function Safe\session_unset;
|
|||
session_start();
|
||||
|
||||
$isSaved = HttpInput::Bool(SESSION, 'is-artwork-saved') ?? false;
|
||||
/** @var ?\Exception $exception */
|
||||
$exception = $_SESSION['exception'] ?? null;
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
|
||||
try{
|
||||
try{
|
||||
|
@ -65,7 +64,7 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
Template::Emit403();
|
||||
}
|
||||
|
||||
?><?= Template::Header(['title' => $artwork->Name, 'artwork' => true]) ?>
|
||||
?><?= Template::Header(['title' => $artwork->Name, 'css' => ['/css/artwork.css']]) ?>
|
||||
<main class="artworks">
|
||||
<section class="narrow">
|
||||
<h1><?= Formatter::EscapeHtml($artwork->Name) ?></h1>
|
||||
|
@ -173,8 +172,8 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
<? 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>
|
||||
<? } ?>
|
||||
<form method="post" action="<?= $artwork->Url ?>" autocomplete="off">
|
||||
<input type="hidden" name="_method" value="PATCH" />
|
||||
<form method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $artwork->Url ?>" autocomplete="off">
|
||||
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Patch->value ?>" />
|
||||
<? if($artwork->CanStatusBeChangedBy(Session::$User)){ ?>
|
||||
<label>
|
||||
<span>Artwork approval status</span>
|
||||
|
|
|
@ -135,7 +135,7 @@ catch(Exceptions\PageOutOfBoundsException){
|
|||
header('Location: ' . $url);
|
||||
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">
|
||||
<section class="narrow">
|
||||
<h1>Browse U.S. Public Domain Artwork</h1>
|
||||
|
|
|
@ -4,10 +4,8 @@ use function Safe\session_unset;
|
|||
session_start();
|
||||
|
||||
$isCreated = HttpInput::Bool(SESSION, 'is-artwork-created') ?? false;
|
||||
/** @var ?\Exception $exception */
|
||||
$exception = $_SESSION['exception'] ?? null;
|
||||
/** @var ?Artwork $artwork */
|
||||
$artwork = $_SESSION['artwork'] ?? null;
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
$artwork = HttpInput::SessionObject('artwork', Artwork::class);
|
||||
|
||||
try{
|
||||
if(Session::$User === null){
|
||||
|
@ -51,7 +49,7 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
<?= Template::Header(
|
||||
[
|
||||
'title' => 'Submit an Artwork',
|
||||
'artwork' => true,
|
||||
'css' => ['/css/artwork.css'],
|
||||
'highlight' => '',
|
||||
'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>
|
||||
<? } ?>
|
||||
|
||||
<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]) ?>
|
||||
</form>
|
||||
</section>
|
||||
|
|
|
@ -835,7 +835,7 @@ input[type="number"]:focus,
|
|||
input[type="url"]:focus,
|
||||
input[type="search"]:focus,
|
||||
input[type="file"]:focus,
|
||||
label.checkbox:focus-within,
|
||||
label:has(input[type="radio"]):focus-within,
|
||||
label:has(input[type="checkbox"]):focus-within,
|
||||
select:focus,
|
||||
button:focus,
|
||||
|
@ -1829,20 +1829,24 @@ label span + span i{
|
|||
|
||||
input[type="file"],
|
||||
label:has(input[type="file"]),
|
||||
label:has(input[type="radio"]),
|
||||
label:has(input[type="checkbox"]){
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
label + label:has(input[type="radio"]),
|
||||
label + label:has(input[type="checkbox"]){
|
||||
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){
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
label:has(input[type="radio"]):has(> span) input,
|
||||
label:has(input[type="checkbox"]):has(> span) input{
|
||||
grid-row: 1 / span 2;
|
||||
justify-self: center;
|
||||
|
@ -1897,6 +1901,7 @@ label.tags,
|
|||
label.picture,
|
||||
label.user,
|
||||
label:has(input[type="password"]){
|
||||
display: block;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
@ -2483,10 +2488,12 @@ fieldset{
|
|||
border: none;
|
||||
}
|
||||
|
||||
fieldset p{
|
||||
fieldset p,
|
||||
fieldset legend{
|
||||
border-bottom: 1px dashed var(--input-border);
|
||||
}
|
||||
|
||||
fieldset legend,
|
||||
fieldset p:has(+ ul){
|
||||
font-weight: bold;
|
||||
}
|
||||
|
@ -2495,7 +2502,8 @@ input[type="checkbox"] + span:has(+ span){
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
label.checkbox{
|
||||
label:has(input[type="radio"]),
|
||||
label:has(input[type="checkbox"]){
|
||||
display: inline-flex;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
|
@ -2503,20 +2511,24 @@ label.checkbox{
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
label.checkbox input{
|
||||
label:has(input[type="radio"]) input,
|
||||
label:has(input[type="checkbox"]) input{
|
||||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
label.checkbox span{
|
||||
label:has(input[type="radio"]) span,
|
||||
label:has(input[type="checkbox"]) span{
|
||||
display: block;
|
||||
}
|
||||
|
||||
label.checkbox span > span{
|
||||
label:has(input[type="radio"]) span > span,
|
||||
label:has(input[type="checkbox"]) span > span{
|
||||
line-height: 1.6;
|
||||
margin-top: .25rem;
|
||||
}
|
||||
|
||||
label.checkbox span{
|
||||
label:has(input[type="radio"]) span,
|
||||
label:has(input[type="checkbox"]) span{
|
||||
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]) ?>
|
||||
|
||||
<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 */ ?>
|
||||
<input type="text" name="automationtest" value="" maxlength="80" />
|
||||
</label>
|
||||
|
@ -47,10 +47,10 @@ if($exception){
|
|||
<p>What kind of email would you like to receive?</p>
|
||||
<ul>
|
||||
<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>
|
||||
<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>
|
||||
</ul>
|
||||
</fieldset>
|
||||
|
|
|
@ -4,20 +4,14 @@ use function Safe\session_unset;
|
|||
session_start();
|
||||
|
||||
$poll = new Poll();
|
||||
$vote = new PollVote();
|
||||
/** @var ?\Exception $exception */
|
||||
$exception = $_SESSION['exception'] ?? null;
|
||||
$vote = HttpInput::SessionObject('vote', PollVote::class) ?? new PollVote();
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
|
||||
try{
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(isset($_SESSION['vote'])){
|
||||
/** @var PollVote $vote */
|
||||
$vote = $_SESSION['vote'];
|
||||
}
|
||||
|
||||
if(!isset($vote->UserId)){
|
||||
$vote->UserId = Session::$User->UserId;
|
||||
$vote->User = Session::$User;
|
||||
|
@ -60,7 +54,7 @@ catch(Exceptions\PollVoteExistsException $ex){
|
|||
<section class="narrow">
|
||||
<h1>Vote in the <?= Formatter::EscapeHtml($poll->Name) ?> Poll</h1>
|
||||
<?= 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" />
|
||||
<fieldset>
|
||||
<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><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) ?>" />
|
||||
<? if($passwordRequired){ ?>
|
||||
<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.']) ?>
|
||||
<main>
|
||||
<h1>Website Settings</h1>
|
||||
<form action="/settings" method="post">
|
||||
<form action="/settings" method="<?= Enums\HttpMethod::Post->value ?>">
|
||||
<label>
|
||||
<span>Color scheme</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