mirror of
https://github.com/standardebooks/web.git
synced 2025-07-06 06:40:33 -04:00
Further refine projects system
This commit is contained in:
parent
2449de6f6c
commit
5782d6ca7d
20 changed files with 307 additions and 94 deletions
|
@ -3,4 +3,6 @@ RewriteRule ^/users/([\d]+)$ /users/post.php?user-id=$1 [L]
|
||||||
|
|
||||||
RewriteRule ^/users/([^/]+)$ /users/get.php?user-identifier=$1 [B,L]
|
RewriteRule ^/users/([^/]+)$ /users/get.php?user-identifier=$1 [B,L]
|
||||||
|
|
||||||
RewriteRule ^/users/([\d]+)/edit$ /users/edit.php?user-id=$1 [L]
|
RewriteRule ^/users/([^/]+)/edit$ /users/edit.php?user-identifier=$1 [L]
|
||||||
|
|
||||||
|
RewriteRule ^/users/([^/]+)/projects$ /users/projects/index.php?user-identifier=$1 [L]
|
||||||
|
|
|
@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS `Benefits` (
|
||||||
`CanEditUsers` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanEditUsers` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanEditCollections` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanEditCollections` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanEditEbooks` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanEditEbooks` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanCreateEbookPlaceholders` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanEditEbookPlaceholders` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanManageProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanManageProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanReviewProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanReviewProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
`CanEditProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
`CanEditProjects` tinyint(1) unsigned NOT NULL DEFAULT 0,
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Benefits{
|
||||||
public bool $CanEditUsers = false;
|
public bool $CanEditUsers = false;
|
||||||
public bool $CanEditCollections = false;
|
public bool $CanEditCollections = false;
|
||||||
public bool $CanEditEbooks = false;
|
public bool $CanEditEbooks = false;
|
||||||
public bool $CanCreateEbookPlaceholders = false;
|
public bool $CanEditEbookPlaceholders = false;
|
||||||
public bool $CanManageProjects = false;
|
public bool $CanManageProjects = false;
|
||||||
public bool $CanReviewProjects = false;
|
public bool $CanReviewProjects = false;
|
||||||
public bool $CanEditProjects = false;
|
public bool $CanEditProjects = false;
|
||||||
|
@ -38,7 +38,7 @@ class Benefits{
|
||||||
||
|
||
|
||||||
$this->CanEditEbooks
|
$this->CanEditEbooks
|
||||||
||
|
||
|
||||||
$this->CanCreateEbookPlaceholders
|
$this->CanEditEbookPlaceholders
|
||||||
||
|
||
|
||||||
$this->CanManageProjects
|
$this->CanManageProjects
|
||||||
||
|
||
|
||||||
|
@ -76,18 +76,18 @@ class Benefits{
|
||||||
|
|
||||||
public function Create(): void{
|
public function Create(): void{
|
||||||
Db::Query('
|
Db::Query('
|
||||||
INSERT into Benefits (UserId, CanAccessFeeds, CanVote, CanBulkDownload, CanUploadArtwork, CanReviewArtwork, CanReviewOwnArtwork, CanEditUsers, CanCreateEbookPlaceholders)
|
INSERT into Benefits (UserId, CanAccessFeeds, CanVote, CanBulkDownload, CanUploadArtwork, CanReviewArtwork, CanReviewOwnArtwork, CanEditUsers, CanEditEbookPlaceholders)
|
||||||
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
', [$this->UserId, $this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanCreateEbookPlaceholders]);
|
', [$this->UserId, $this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanEditEbookPlaceholders]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Save(): void{
|
public function Save(): void{
|
||||||
Db::Query('
|
Db::Query('
|
||||||
UPDATE Benefits
|
UPDATE Benefits
|
||||||
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?, CanCreateEbookPlaceholders = ?
|
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?, CanEditEbookPlaceholders = ?
|
||||||
where
|
where
|
||||||
UserId = ?
|
UserId = ?
|
||||||
', [$this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanCreateEbookPlaceholders, $this->UserId]);
|
', [$this->CanAccessFeeds, $this->CanVote, $this->CanBulkDownload, $this->CanUploadArtwork, $this->CanReviewArtwork, $this->CanReviewOwnArtwork, $this->CanEditUsers, $this->CanEditEbookPlaceholders, $this->UserId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function FillFromHttpPost(): void{
|
public function FillFromHttpPost(): void{
|
||||||
|
@ -98,6 +98,9 @@ class Benefits{
|
||||||
$this->PropertyFromHttp('CanReviewArtwork');
|
$this->PropertyFromHttp('CanReviewArtwork');
|
||||||
$this->PropertyFromHttp('CanReviewOwnArtwork');
|
$this->PropertyFromHttp('CanReviewOwnArtwork');
|
||||||
$this->PropertyFromHttp('CanEditUsers');
|
$this->PropertyFromHttp('CanEditUsers');
|
||||||
$this->PropertyFromHttp('CanCreateEbookPlaceholders');
|
$this->PropertyFromHttp('CanEditEbookPlaceholders');
|
||||||
|
$this->PropertyFromHttp('CanEditProjects');
|
||||||
|
$this->PropertyFromHttp('CanReviewProjects');
|
||||||
|
$this->PropertyFromHttp('CanManageProjects');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use Safe\DateTimeImmutable;
|
||||||
* @property User $ManagerUser
|
* @property User $ManagerUser
|
||||||
* @property User $ReviewerUser
|
* @property User $ReviewerUser
|
||||||
* @property string $Url
|
* @property string $Url
|
||||||
|
* @property DateTimeImmutable $LastActivityTimestamp The timestamp of the latest activity, whether it's a commit, a discussion post, or simply the started timestamp.
|
||||||
*/
|
*/
|
||||||
class Project{
|
class Project{
|
||||||
use Traits\Accessor;
|
use Traits\Accessor;
|
||||||
|
@ -40,6 +41,7 @@ class Project{
|
||||||
protected User $_ManagerUser;
|
protected User $_ManagerUser;
|
||||||
protected User $_ReviewerUser;
|
protected User $_ReviewerUser;
|
||||||
protected string $_Url;
|
protected string $_Url;
|
||||||
|
protected DateTimeImmutable $_LastActivityTimestamp;
|
||||||
|
|
||||||
|
|
||||||
// *******
|
// *******
|
||||||
|
@ -54,6 +56,22 @@ class Project{
|
||||||
return $this->_Url;
|
return $this->_Url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function GetLastActivityTimestamp(): DateTimeImmutable{
|
||||||
|
if(!isset($this->_LastActivityTimestamp)){
|
||||||
|
$dates = [
|
||||||
|
(int)($this->LastCommitTimestamp?->format(Enums\DateTimeFormat::UnixTimestamp->value) ?? 0) => $this->LastCommitTimestamp ?? NOW,
|
||||||
|
(int)($this->LastDiscussionTimestamp?->format(Enums\DateTimeFormat::UnixTimestamp->value) ?? 0) => $this->LastDiscussionTimestamp ?? NOW,
|
||||||
|
(int)($this->Started->format(Enums\DateTimeFormat::UnixTimestamp->value)) => $this->Started,
|
||||||
|
];
|
||||||
|
|
||||||
|
ksort($dates);
|
||||||
|
|
||||||
|
$this->_LastActivityTimestamp = end($dates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_LastActivityTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// *******
|
// *******
|
||||||
// METHODS
|
// METHODS
|
||||||
|
@ -96,6 +114,12 @@ class Project{
|
||||||
if($this->DiscussionUrl == ''){
|
if($this->DiscussionUrl == ''){
|
||||||
$this->DiscussionUrl = null;
|
$this->DiscussionUrl = null;
|
||||||
}
|
}
|
||||||
|
else{
|
||||||
|
if(preg_match('|^https://groups\.google\.com/g/standardebooks/|iu', $this->DiscussionUrl)){
|
||||||
|
// Get the base thread URL in case we were passed a URL with a specific message or query string.
|
||||||
|
$this->DiscussionUrl = preg_replace('|^(https://groups\.google\.com/g/standardebooks/c/[^/]+).*|iu', '\1', $this->DiscussionUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$this->VcsUrl = rtrim(trim($this->VcsUrl ?? ''), '/');
|
$this->VcsUrl = rtrim(trim($this->VcsUrl ?? ''), '/');
|
||||||
if($this->VcsUrl == ''){
|
if($this->VcsUrl == ''){
|
||||||
|
@ -268,6 +292,10 @@ class Project{
|
||||||
* @throws Exceptions\AppException If the operation failed.
|
* @throws Exceptions\AppException If the operation failed.
|
||||||
*/
|
*/
|
||||||
public function FetchLatestCommitTimestamp(?string $apiKey = null): void{
|
public function FetchLatestCommitTimestamp(?string $apiKey = null): void{
|
||||||
|
if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$headers = [
|
$headers = [
|
||||||
'Accept: application/vnd.github+json',
|
'Accept: application/vnd.github+json',
|
||||||
'X-GitHub-Api-Version: 2022-11-28',
|
'X-GitHub-Api-Version: 2022-11-28',
|
||||||
|
@ -327,7 +355,7 @@ class Project{
|
||||||
* @throws Exceptions\AppException If the operation faile.d
|
* @throws Exceptions\AppException If the operation faile.d
|
||||||
*/
|
*/
|
||||||
public function FetchLastDiscussionTimestamp(): void{
|
public function FetchLastDiscussionTimestamp(): void{
|
||||||
if($this->DiscussionUrl === null){
|
if(!preg_match('|^https://groups\.google\.com/g/standardebooks/|iu', $this->DiscussionUrl ?? '')){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -347,7 +375,7 @@ class Project{
|
||||||
throw new Exception('HTTP code ' . $httpCode . ' received for URL <' . $this->DiscussionUrl . '>.');
|
throw new Exception('HTTP code ' . $httpCode . ' received for URL <' . $this->DiscussionUrl . '>.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$matchCount = preg_match_all('/<span class="[^"]+?">([a-z]{3} [\d]{1,2}, [\d]{4}, [\d]{2}:[\d]{2}:[\d]{2} (?:AM|PM))<\/span>/iu', $response, $matches);
|
$matchCount = preg_match_all('/<span class="[^"]+?">([a-z]{3} [\d]{1,2}, [\d]{4}, [\d]{1,2}:[\d]{1,2}:[\d]{1,2} (?:AM|PM))/iu', $response, $matches);
|
||||||
|
|
||||||
if($matchCount > 0){
|
if($matchCount > 0){
|
||||||
// Unsure of the time zone, so just assume UTC.
|
// Unsure of the time zone, so just assume UTC.
|
||||||
|
@ -390,4 +418,18 @@ class Project{
|
||||||
public static function GetAllByStatus(Enums\ProjectStatusType $status): array{
|
public static function GetAllByStatus(Enums\ProjectStatusType $status): array{
|
||||||
return Db::Query('SELECT * from Projects where Status = ? order by Started desc', [$status], Project::class);
|
return Db::Query('SELECT * from Projects where Status = ? order by Started desc', [$status], Project::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<Project>
|
||||||
|
*/
|
||||||
|
public static function GetAllByManagerUserId(int $userId): array{
|
||||||
|
return Db::Query('SELECT * from Projects where ManagerUserId = ? and Status in (?, ?) order by Started desc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled], Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<Project>
|
||||||
|
*/
|
||||||
|
public static function GetAllByReviewerUserId(int $userId): array{
|
||||||
|
return Db::Query('SELECT * from Projects where ReviewerUserId = ? and Status in (?, ?) order by Started desc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled], Project::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
18
lib/User.php
18
lib/User.php
|
@ -12,6 +12,7 @@ use function Safe\preg_match;
|
||||||
* @property ?Patron $Patron
|
* @property ?Patron $Patron
|
||||||
* @property ?NewsletterSubscription $NewsletterSubscription
|
* @property ?NewsletterSubscription $NewsletterSubscription
|
||||||
* @property ?Payment $LastPayment
|
* @property ?Payment $LastPayment
|
||||||
|
* @property string $DisplayName A string that represent's the `User`'s name, or email, or ID.
|
||||||
*/
|
*/
|
||||||
class User{
|
class User{
|
||||||
use Traits\Accessor;
|
use Traits\Accessor;
|
||||||
|
@ -33,12 +34,29 @@ class User{
|
||||||
protected string $_Url;
|
protected string $_Url;
|
||||||
protected ?Patron $_Patron;
|
protected ?Patron $_Patron;
|
||||||
protected ?NewsletterSubscription $_NewsletterSubscription;
|
protected ?NewsletterSubscription $_NewsletterSubscription;
|
||||||
|
protected string $_DisplayName;
|
||||||
|
|
||||||
|
|
||||||
// *******
|
// *******
|
||||||
// GETTERS
|
// GETTERS
|
||||||
// *******
|
// *******
|
||||||
|
|
||||||
|
protected function GetDisplayName(): string{
|
||||||
|
if(!isset($this->_DisplayName)){
|
||||||
|
if($this->Name !== null){
|
||||||
|
$this->_DisplayName = $this->Name;
|
||||||
|
}
|
||||||
|
elseif($this->Email !== null){
|
||||||
|
$this->_DisplayName = $this->Email;
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
$this->_DisplayName = 'User #' . $this->UserId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_DisplayName;
|
||||||
|
}
|
||||||
|
|
||||||
protected function GetNewsletterSubscription(): ?NewsletterSubscription{
|
protected function GetNewsletterSubscription(): ?NewsletterSubscription{
|
||||||
if(!isset($this->_NewsletterSubscription)){
|
if(!isset($this->_NewsletterSubscription)){
|
||||||
try{
|
try{
|
||||||
|
|
|
@ -3,17 +3,12 @@
|
||||||
require_once('/standardebooks.org/web/lib/Core.php');
|
require_once('/standardebooks.org/web/lib/Core.php');
|
||||||
|
|
||||||
use function Safe\file_get_contents;
|
use function Safe\file_get_contents;
|
||||||
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterate over all `Project`s that are in progress or stalled and get their latest GitHub commit. If the commit is more than 30 days old, mark the `Project` as stalled.
|
* Iterate over all `Project`s that are in progress or stalled and get their latest GitHub commit. If the commit is more than 30 days old, mark the `Project` as stalled.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
use Safe\DateTimeImmutable;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$projects = array_merge(
|
$projects = array_merge(
|
||||||
Project::GetAllByStatus(Enums\ProjectStatusType::InProgress),
|
Project::GetAllByStatus(Enums\ProjectStatusType::InProgress),
|
||||||
Project::GetAllByStatus(Enums\ProjectStatusType::Stalled)
|
Project::GetAllByStatus(Enums\ProjectStatusType::Stalled)
|
||||||
|
|
|
@ -3,33 +3,30 @@
|
||||||
* @var Ebook $ebook
|
* @var Ebook $ebook
|
||||||
*/
|
*/
|
||||||
?>
|
?>
|
||||||
<h2>Metadata</h2>
|
<section id="metadata">
|
||||||
<table class="admin-table">
|
<h2>Metadata</h2>
|
||||||
<tbody>
|
<table class="admin-table">
|
||||||
<tr>
|
<tbody>
|
||||||
<td>Ebook ID:</td>
|
|
||||||
<td><?= $ebook->EbookId ?></td>
|
|
||||||
</tr>
|
|
||||||
<? if($ebook->IsPlaceholder() && $ebook->EbookPlaceholder !== null){ ?>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>Is wanted:</td>
|
<td>Ebook ID:</td>
|
||||||
<td><? if($ebook->EbookPlaceholder->IsWanted){ ?>☑<? }else{ ?>☐<? } ?></td>
|
<td><?= $ebook->EbookId ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<? if($ebook->EbookPlaceholder->IsWanted){ ?>
|
<? if($ebook->IsPlaceholder() && $ebook->EbookPlaceholder !== null){ ?>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Is Patron selection:</td>
|
<td>Is wanted:</td>
|
||||||
<td><? if($ebook->EbookPlaceholder->IsPatron){ ?>☑<? }else{ ?>☐<? } ?></td>
|
<td><? if($ebook->EbookPlaceholder->IsWanted){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? if($ebook->EbookPlaceholder->IsWanted){ ?>
|
||||||
|
<tr>
|
||||||
|
<td>Is Patron selection:</td>
|
||||||
|
<td><? if($ebook->EbookPlaceholder->IsPatron){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<? } ?>
|
||||||
|
<tr>
|
||||||
|
<td>Difficulty:</td>
|
||||||
|
<td><?= ucfirst($ebook->EbookPlaceholder->Difficulty->value ?? '') ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<tr>
|
</tbody>
|
||||||
<td>Difficulty:</td>
|
</table>
|
||||||
<td><?= ucfirst($ebook->EbookPlaceholder->Difficulty->value ?? '') ?></td>
|
</section>
|
||||||
</tr>
|
|
||||||
<? } ?>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<? if(sizeof($ebook->Projects) > 0){ ?>
|
|
||||||
<h2>Projects</h2>
|
|
||||||
<?= Template::ProjectsTable(['projects' => $ebook->Projects, 'includeTitle' => false]) ?>
|
|
||||||
<? } ?>
|
|
||||||
|
|
11
templates/EbookProjects.php
Normal file
11
templates/EbookProjects.php
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?
|
||||||
|
/**
|
||||||
|
* @var Ebook $ebook
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<? if(sizeof($ebook->Projects) > 0){ ?>
|
||||||
|
<section id="projects">
|
||||||
|
<h2>Projects</h2>
|
||||||
|
<?= Template::ProjectsTable(['projects' => $ebook->Projects, 'includeTitle' => false]) ?>
|
||||||
|
</section>
|
||||||
|
<? } ?>
|
|
@ -14,8 +14,10 @@ $includeStatus = $includeStatus ?? true;
|
||||||
<th scope="col">Title</th>
|
<th scope="col">Title</th>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
<th scope="col">Producer</th>
|
<th scope="col">Producer</th>
|
||||||
|
<th scope="col">Manager</th>
|
||||||
|
<th scope="col">Reviewer</th>
|
||||||
<th scope="col">Started</th>
|
<th scope="col">Started</th>
|
||||||
<th scope="col">Last commit</th>
|
<th scope="col">Last activity</th>
|
||||||
<? if($includeStatus){ ?>
|
<? if($includeStatus){ ?>
|
||||||
<th scope="col">Status</th>
|
<th scope="col">Status</th>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
@ -38,14 +40,20 @@ $includeStatus = $includeStatus ?? true;
|
||||||
<?= Formatter::EscapeHtml($project->ProducerName) ?>
|
<?= Formatter::EscapeHtml($project->ProducerName) ?>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
</td>
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="<?= $project->ManagerUser->Url ?>/projects"><?= Formatter::EscapeHtml($project->ManagerUser->DisplayName) ?></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="<?= $project->ReviewerUser->Url ?>/projects"><?= Formatter::EscapeHtml($project->ReviewerUser->DisplayName) ?></a>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?= $project->Started->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
<?= $project->Started->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<?= $project->LastCommitTimestamp?->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
<?= $project->LastActivityTimestamp->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
||||||
</td>
|
</td>
|
||||||
<? if($includeStatus){ ?>
|
<? if($includeStatus){ ?>
|
||||||
<td class="status">
|
<td class="status<? if($project->Status == Enums\ProjectStatusType::Stalled){ ?> stalled<? } ?>">
|
||||||
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
||||||
</td>
|
</td>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
|
@ -137,9 +137,30 @@ $passwordAction = $passwordAction ?? Enums\PasswordActionType::None;
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<label>
|
<label>
|
||||||
<input type="hidden" name="benefits-can-create-ebook-placeholder" value="false" />
|
<input type="hidden" name="benefits-can-edit-ebook-placeholders" value="false" />
|
||||||
<input type="checkbox" name="benefits-can-create-ebook-placeholder" value="true"<? if($user->Benefits->CanCreateEbookPlaceholders){ ?> checked="checked"<? } ?> />
|
<input type="checkbox" name="benefits-can-edit-ebook-placeholders" value="true"<? if($user->Benefits->CanEditEbookPlaceholders){ ?> checked="checked"<? } ?> />
|
||||||
Can create ebook placeholders
|
Can edit ebook placeholders
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-edit-projects" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-edit-projects" value="true"<? if($user->Benefits->CanManageProjects){ ?> checked="checked"<? } ?> />
|
||||||
|
Can edit projects
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-manage-projects" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-manage-projects" value="true"<? if($user->Benefits->CanManageProjects){ ?> checked="checked"<? } ?> />
|
||||||
|
Can manage projects
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="hidden" name="benefits-can-review-projects" value="false" />
|
||||||
|
<input type="checkbox" name="benefits-can-review-projects" value="true"<? if($user->Benefits->CanManageProjects){ ?> checked="checked"<? } ?> />
|
||||||
|
Can review projects
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -749,6 +749,16 @@ ul.message.error > li + li{
|
||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.data-table td:first-child,
|
||||||
|
.data-table th:first-child{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-table td:last-child,
|
||||||
|
.data-table th:last-child{
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.data-table td,
|
.data-table td,
|
||||||
.data-table th{
|
.data-table th{
|
||||||
padding: .25rem .5rem;
|
padding: .25rem .5rem;
|
||||||
|
@ -3293,6 +3303,20 @@ table.admin-table td + td{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty-notice{
|
||||||
|
text-align: center;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs{
|
||||||
|
font-style: italic;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.breadcrumbs + h1{
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
|
@media (hover: none) and (pointer: coarse){ /* target ipads and smartphones without a mouse */
|
||||||
/* For iPad, unset the height so it matches the other elements */
|
/* For iPad, unset the height so it matches the other elements */
|
||||||
select[multiple]{
|
select[multiple]{
|
||||||
|
|
|
@ -12,15 +12,16 @@
|
||||||
grid-column-start: 1;
|
grid-column-start: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 + table.projects{
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.projects thead{
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.data-table .status,
|
table.data-table .status,
|
||||||
table.data-table .producer{
|
table.data-table .producer{
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.data-table{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data-table td.status.stalled{
|
||||||
|
background: #861d1d !important; /* Override hover backgound color */
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
|
@ -104,9 +104,11 @@ catch(Exceptions\EbookNotFoundException){
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
||||||
<section id="metadata">
|
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
||||||
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
<? } ?>
|
||||||
</section>
|
|
||||||
|
<? if(Session::$User?->Benefits->CanEditProjects || Session::$User?->Benefits->CanManageProjects || Session::$User?->Benefits->CanReviewProjects){ ?>
|
||||||
|
<?= Template::EbookProjects(['ebook' => $ebook]) ?>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
</article>
|
</article>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -12,7 +12,7 @@ try{
|
||||||
throw new Exceptions\LoginRequiredException();
|
throw new Exceptions\LoginRequiredException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||||
throw new Exceptions\InvalidPermissionsException();
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ try{
|
||||||
|
|
||||||
// POSTing a new ebook placeholder.
|
// POSTing a new ebook placeholder.
|
||||||
if($httpMethod == Enums\HttpMethod::Post){
|
if($httpMethod == Enums\HttpMethod::Post){
|
||||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||||
throw new Exceptions\InvalidPermissionsException();
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -398,9 +398,7 @@ catch(Exceptions\EbookNotFoundException){
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
||||||
<section id="metadata">
|
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
||||||
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
|
||||||
</section>
|
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
||||||
<? if(sizeof($carousel) > 0){ ?>
|
<? if(sizeof($carousel) > 0){ ?>
|
||||||
|
|
|
@ -4,7 +4,13 @@ try{
|
||||||
throw new Exceptions\LoginRequiredException();
|
throw new Exceptions\LoginRequiredException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Session::$User->Benefits->CanEditProjects){
|
if(
|
||||||
|
!Session::$User->Benefits->CanManageProjects
|
||||||
|
&&
|
||||||
|
!Session::$User->Benefits->CanReviewProjects
|
||||||
|
&&
|
||||||
|
!Session::$User->Benefits->CanEditProjects
|
||||||
|
){
|
||||||
throw new Exceptions\InvalidPermissionsException();
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,22 +23,28 @@ catch(Exceptions\LoginRequiredException){
|
||||||
catch(Exceptions\InvalidPermissionsException){
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
Template::Emit403();
|
Template::Emit403();
|
||||||
}
|
}
|
||||||
?><?= Template::Header(['title' => 'Projects', 'css' => ['/css/project.css'], 'description' => 'Ebook projects currently underway at Standard Ebooks.']) ?>
|
?><?= Template::Header([
|
||||||
|
'title' => 'Projects',
|
||||||
|
'css' => ['/css/project.css'],
|
||||||
|
'description' => 'Ebook projects currently underway at Standard Ebooks.'
|
||||||
|
]) ?>
|
||||||
<main>
|
<main>
|
||||||
<section class="narrow">
|
<section>
|
||||||
<h1>Projects</h1>
|
<h1>Projects</h1>
|
||||||
<h2>Active projects</h2>
|
<section id="active">
|
||||||
<? if(sizeof($inProgressProjects) == 0){ ?>
|
<h2>Active projects</h2>
|
||||||
<p>
|
<? if(sizeof($inProgressProjects) == 0){ ?>
|
||||||
<i>None.</i>
|
<p class="empty-notice">None.</p>
|
||||||
</p>
|
<? }else{ ?>
|
||||||
<? }else{ ?>
|
<?= Template::ProjectsTable(['projects' => $inProgressProjects, 'includeStatus' => false]) ?>
|
||||||
<?= Template::ProjectsTable(['projects' => $inProgressProjects, 'includeStatus' => false]) ?>
|
<? } ?>
|
||||||
<? } ?>
|
</section>
|
||||||
|
|
||||||
<? if(sizeof($stalledProjects) > 0){ ?>
|
<? if(sizeof($stalledProjects) > 0){ ?>
|
||||||
<h2>Stalled projects</h2>
|
<section id="stalled">
|
||||||
<?= Template::ProjectsTable(['projects' => $stalledProjects, 'includeStatus' => false]) ?>
|
<h2>Stalled projects</h2>
|
||||||
|
<?= Template::ProjectsTable(['projects' => $stalledProjects, 'includeStatus' => false]) ?>
|
||||||
|
</section>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -10,7 +10,7 @@ $passwordAction = HttpInput::SessionObject('password-action', Enums\PasswordActi
|
||||||
|
|
||||||
try{
|
try{
|
||||||
if($user === null){
|
if($user === null){
|
||||||
$user = User::Get(HttpInput::Int(GET, 'user-id'));
|
$user = User::GetByIdentifier(HttpInput::Str(GET, 'user-identifier'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Session::$User === null){
|
if(Session::$User === null){
|
||||||
|
@ -40,13 +40,14 @@ catch(Exceptions\InvalidPermissionsException){
|
||||||
<?= Template::Header(
|
<?= Template::Header(
|
||||||
[
|
[
|
||||||
'title' => 'Edit user #' . $user->UserId,
|
'title' => 'Edit user #' . $user->UserId,
|
||||||
|
'canonicalUrl' => $user->Url . '/edit',
|
||||||
'css' => ['/css/user.css'],
|
'css' => ['/css/user.css'],
|
||||||
'highlight' => ''
|
'highlight' => ''
|
||||||
]
|
]
|
||||||
) ?>
|
) ?>
|
||||||
<main>
|
<main>
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>Edit User #<?= $user->UserId ?></h1>
|
<h1>Edit <?= Formatter::EscapeHtml($user->DisplayName) ?></h1>
|
||||||
|
|
||||||
<?= Template::Error(['exception' => $exception]) ?>
|
<?= Template::Error(['exception' => $exception]) ?>
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,6 @@ $isSaved = HttpInput::Bool(SESSION, 'is-user-saved') ?? false;
|
||||||
try{
|
try{
|
||||||
$user = User::GetByIdentifier(HttpInput::Str(GET, 'user-identifier'));
|
$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){
|
if(Session::$User === null){
|
||||||
throw new Exceptions\LoginRequiredException();
|
throw new Exceptions\LoginRequiredException();
|
||||||
}
|
}
|
||||||
|
@ -35,15 +30,16 @@ catch(Exceptions\LoginRequiredException){
|
||||||
catch(Exceptions\InvalidPermissionsException){
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
Template::Emit403();
|
Template::Emit403();
|
||||||
}
|
}
|
||||||
catch(Exceptions\SeeOtherException $ex){
|
?><?= Template::Header(
|
||||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
[
|
||||||
header('Location: ' . $ex->Url);
|
'title' => $user->DisplayName,
|
||||||
}
|
'canonicalUrl' => $user->Url,
|
||||||
|
'css' => ['/css/user.css']
|
||||||
?><?= Template::Header(['title' => 'User #' . $user->UserId, 'css' => ['/css/user.css']]) ?>
|
]
|
||||||
|
) ?>
|
||||||
<main>
|
<main>
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>User #<?= $user->UserId ?></h1>
|
<h1><?= Formatter::EscapeHtml($user->DisplayName) ?></h1>
|
||||||
|
|
||||||
<? if($isSaved){ ?>
|
<? if($isSaved){ ?>
|
||||||
<p class="message success">User saved!</p>
|
<p class="message success">User saved!</p>
|
||||||
|
@ -51,9 +47,17 @@ catch(Exceptions\SeeOtherException $ex){
|
||||||
|
|
||||||
<a href="<?= $user->Url ?>/edit">Edit user</a>
|
<a href="<?= $user->Url ?>/edit">Edit user</a>
|
||||||
|
|
||||||
|
<? if($user->Benefits->CanManageProjects || $user->Benefits->CanReviewProjects){ ?>
|
||||||
|
<a href="<?= $user->Url ?>/projects">Projects</a>
|
||||||
|
<? } ?>
|
||||||
|
|
||||||
<h2>Basics</h2>
|
<h2>Basics</h2>
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>User ID:</td>
|
||||||
|
<td><?= $user->UserId ?></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Email:</td>
|
<td>Email:</td>
|
||||||
<td><?= Formatter::EscapeHtml($user->Email) ?></td>
|
<td><?= Formatter::EscapeHtml($user->Email) ?></td>
|
||||||
|
@ -170,8 +174,20 @@ catch(Exceptions\SeeOtherException $ex){
|
||||||
<td><? if($user->Benefits->CanEditUsers){ ?>☑<? }else{ ?>☐<? } ?></td>
|
<td><? if($user->Benefits->CanEditUsers){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Can create ebook placeholders:</td>
|
<td>Can edit ebook placeholders:</td>
|
||||||
<td><? if($user->Benefits->CanCreateEbookPlaceholders){ ?>☑<? }else{ ?>☐<? } ?></td>
|
<td><? if($user->Benefits->CanEditEbookPlaceholders){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can edit projects:</td>
|
||||||
|
<td><? if($user->Benefits->CanEditProjects){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can manage projects:</td>
|
||||||
|
<td><? if($user->Benefits->CanManageProjects){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Can review projects:</td>
|
||||||
|
<td><? if($user->Benefits->CanReviewProjects){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||||
</tr>
|
</tr>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -179,7 +195,7 @@ catch(Exceptions\SeeOtherException $ex){
|
||||||
|
|
||||||
<h2>Payments</h2>
|
<h2>Payments</h2>
|
||||||
<? if(sizeof($user->Payments) == 0){ ?>
|
<? if(sizeof($user->Payments) == 0){ ?>
|
||||||
<p>None.</p>
|
<p class="empty-notice">None.</p>
|
||||||
<? }else{ ?>
|
<? }else{ ?>
|
||||||
<p>
|
<p>
|
||||||
<a href="https://fundraising.fracturedatlas.org/admin/general_support/donations?query=<?= urlencode($user->Email ?? '') ?>">View all payments at Fractured Atlas</a>
|
<a href="https://fundraising.fracturedatlas.org/admin/general_support/donations?query=<?= urlencode($user->Email ?? '') ?>">View all payments at Fractured Atlas</a>
|
||||||
|
|
62
www/users/projects/index.php
Normal file
62
www/users/projects/index.php
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<?
|
||||||
|
try{
|
||||||
|
$user = User::GetByIdentifier(HttpInput::Str(GET, 'user-identifier'));
|
||||||
|
|
||||||
|
if(Session::$User === null){
|
||||||
|
throw new Exceptions\LoginRequiredException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(
|
||||||
|
!Session::$User->Benefits->CanManageProjects
|
||||||
|
&&
|
||||||
|
!Session::$User->Benefits->CanReviewProjects
|
||||||
|
&&
|
||||||
|
!Session::$User->Benefits->CanEditProjects
|
||||||
|
){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$managingProjects = Project::GetAllByManagerUserId($user->UserId);
|
||||||
|
$reviewingProjects = Project::GetAllByReviewerUserId($user->UserId);
|
||||||
|
}
|
||||||
|
catch(Exceptions\UserNotFoundException){
|
||||||
|
Template::Emit404();
|
||||||
|
}
|
||||||
|
catch(Exceptions\LoginRequiredException){
|
||||||
|
Template::RedirectToLogin();
|
||||||
|
}
|
||||||
|
catch(Exceptions\InvalidPermissionsException){
|
||||||
|
Template::Emit403();
|
||||||
|
}
|
||||||
|
?><?= Template::Header(
|
||||||
|
[
|
||||||
|
'title' => 'Projects',
|
||||||
|
'canonicalUrl' => $user->Url,
|
||||||
|
'css' => ['/css/project.css'],
|
||||||
|
'description' => 'Ebook projects currently underway at Standard Ebooks.'
|
||||||
|
]
|
||||||
|
) ?>
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
<nav class="breadcrumbs"><a href="<?= $user->Url ?>"><?= Formatter::EscapeHtml($user->DisplayName) ?></a> →</nav>
|
||||||
|
<h1>Projects</h1>
|
||||||
|
<section id="managing">
|
||||||
|
<h2>Managing</h2>
|
||||||
|
<? if(sizeof($managingProjects) == 0){ ?>
|
||||||
|
<p class="empty-notice">None.</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<?= Template::ProjectsTable(['projects' => $managingProjects]) ?>
|
||||||
|
<? } ?>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section id="reviewing">
|
||||||
|
<h2>Reviewing</h2>
|
||||||
|
<? if(sizeof($reviewingProjects) == 0){ ?>
|
||||||
|
<p class="empty-notice">None.</p>
|
||||||
|
<? }else{ ?>
|
||||||
|
<?= Template::ProjectsTable(['projects' => $reviewingProjects]) ?>
|
||||||
|
<? } ?>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
<?= Template::Footer() ?>
|
Loading…
Add table
Add a link
Reference in a new issue