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/([\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,
|
||||
`CanEditCollections` 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,
|
||||
`CanReviewProjects` 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 $CanEditCollections = false;
|
||||
public bool $CanEditEbooks = false;
|
||||
public bool $CanCreateEbookPlaceholders = false;
|
||||
public bool $CanEditEbookPlaceholders = false;
|
||||
public bool $CanManageProjects = false;
|
||||
public bool $CanReviewProjects = false;
|
||||
public bool $CanEditProjects = false;
|
||||
|
@ -38,7 +38,7 @@ class Benefits{
|
|||
||
|
||||
$this->CanEditEbooks
|
||||
||
|
||||
$this->CanCreateEbookPlaceholders
|
||||
$this->CanEditEbookPlaceholders
|
||||
||
|
||||
$this->CanManageProjects
|
||||
||
|
||||
|
@ -76,18 +76,18 @@ class Benefits{
|
|||
|
||||
public function Create(): void{
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
', [$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{
|
||||
Db::Query('
|
||||
UPDATE Benefits
|
||||
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?, CanCreateEbookPlaceholders = ?
|
||||
set CanAccessFeeds = ?, CanVote = ?, CanBulkDownload = ?, CanUploadArtwork = ?, CanReviewArtwork = ?, CanReviewOwnArtwork = ?, CanEditUsers = ?, CanEditEbookPlaceholders = ?
|
||||
where
|
||||
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{
|
||||
|
@ -98,6 +98,9 @@ class Benefits{
|
|||
$this->PropertyFromHttp('CanReviewArtwork');
|
||||
$this->PropertyFromHttp('CanReviewOwnArtwork');
|
||||
$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 $ReviewerUser
|
||||
* @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{
|
||||
use Traits\Accessor;
|
||||
|
@ -40,6 +41,7 @@ class Project{
|
|||
protected User $_ManagerUser;
|
||||
protected User $_ReviewerUser;
|
||||
protected string $_Url;
|
||||
protected DateTimeImmutable $_LastActivityTimestamp;
|
||||
|
||||
|
||||
// *******
|
||||
|
@ -54,6 +56,22 @@ class Project{
|
|||
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
|
||||
|
@ -96,6 +114,12 @@ class Project{
|
|||
if($this->DiscussionUrl == ''){
|
||||
$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 ?? ''), '/');
|
||||
if($this->VcsUrl == ''){
|
||||
|
@ -268,6 +292,10 @@ class Project{
|
|||
* @throws Exceptions\AppException If the operation failed.
|
||||
*/
|
||||
public function FetchLatestCommitTimestamp(?string $apiKey = null): void{
|
||||
if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl)){
|
||||
return;
|
||||
}
|
||||
|
||||
$headers = [
|
||||
'Accept: application/vnd.github+json',
|
||||
'X-GitHub-Api-Version: 2022-11-28',
|
||||
|
@ -327,7 +355,7 @@ class Project{
|
|||
* @throws Exceptions\AppException If the operation faile.d
|
||||
*/
|
||||
public function FetchLastDiscussionTimestamp(): void{
|
||||
if($this->DiscussionUrl === null){
|
||||
if(!preg_match('|^https://groups\.google\.com/g/standardebooks/|iu', $this->DiscussionUrl ?? '')){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -347,7 +375,7 @@ class Project{
|
|||
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){
|
||||
// Unsure of the time zone, so just assume UTC.
|
||||
|
@ -390,4 +418,18 @@ class Project{
|
|||
public static function GetAllByStatus(Enums\ProjectStatusType $status): array{
|
||||
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 ?NewsletterSubscription $NewsletterSubscription
|
||||
* @property ?Payment $LastPayment
|
||||
* @property string $DisplayName A string that represent's the `User`'s name, or email, or ID.
|
||||
*/
|
||||
class User{
|
||||
use Traits\Accessor;
|
||||
|
@ -33,12 +34,29 @@ class User{
|
|||
protected string $_Url;
|
||||
protected ?Patron $_Patron;
|
||||
protected ?NewsletterSubscription $_NewsletterSubscription;
|
||||
protected string $_DisplayName;
|
||||
|
||||
|
||||
// *******
|
||||
// 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{
|
||||
if(!isset($this->_NewsletterSubscription)){
|
||||
try{
|
||||
|
|
|
@ -3,17 +3,12 @@
|
|||
require_once('/standardebooks.org/web/lib/Core.php');
|
||||
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
use Safe\DateTimeImmutable;
|
||||
|
||||
|
||||
|
||||
$projects = array_merge(
|
||||
Project::GetAllByStatus(Enums\ProjectStatusType::InProgress),
|
||||
Project::GetAllByStatus(Enums\ProjectStatusType::Stalled)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
* @var Ebook $ebook
|
||||
*/
|
||||
?>
|
||||
<section id="metadata">
|
||||
<h2>Metadata</h2>
|
||||
<table class="admin-table">
|
||||
<tbody>
|
||||
|
@ -28,8 +29,4 @@
|
|||
<? } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<? if(sizeof($ebook->Projects) > 0){ ?>
|
||||
<h2>Projects</h2>
|
||||
<?= Template::ProjectsTable(['projects' => $ebook->Projects, 'includeTitle' => false]) ?>
|
||||
<? } ?>
|
||||
</section>
|
||||
|
|
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">Producer</th>
|
||||
<th scope="col">Manager</th>
|
||||
<th scope="col">Reviewer</th>
|
||||
<th scope="col">Started</th>
|
||||
<th scope="col">Last commit</th>
|
||||
<th scope="col">Last activity</th>
|
||||
<? if($includeStatus){ ?>
|
||||
<th scope="col">Status</th>
|
||||
<? } ?>
|
||||
|
@ -38,14 +40,20 @@ $includeStatus = $includeStatus ?? true;
|
|||
<?= Formatter::EscapeHtml($project->ProducerName) ?>
|
||||
<? } ?>
|
||||
</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>
|
||||
<?= $project->Started->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
||||
</td>
|
||||
<td>
|
||||
<?= $project->LastCommitTimestamp?->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
||||
<?= $project->LastActivityTimestamp->format(Enums\DateTimeFormat::ShortDate->value) ?>
|
||||
</td>
|
||||
<? if($includeStatus){ ?>
|
||||
<td class="status">
|
||||
<td class="status<? if($project->Status == Enums\ProjectStatusType::Stalled){ ?> stalled<? } ?>">
|
||||
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
||||
</td>
|
||||
<? } ?>
|
||||
|
|
|
@ -137,9 +137,30 @@ $passwordAction = $passwordAction ?? Enums\PasswordActionType::None;
|
|||
</li>
|
||||
<li>
|
||||
<label>
|
||||
<input type="hidden" name="benefits-can-create-ebook-placeholder" value="false" />
|
||||
<input type="checkbox" name="benefits-can-create-ebook-placeholder" value="true"<? if($user->Benefits->CanCreateEbookPlaceholders){ ?> checked="checked"<? } ?> />
|
||||
Can create ebook placeholders
|
||||
<input type="hidden" name="benefits-can-edit-ebook-placeholders" value="false" />
|
||||
<input type="checkbox" name="benefits-can-edit-ebook-placeholders" value="true"<? if($user->Benefits->CanEditEbookPlaceholders){ ?> checked="checked"<? } ?> />
|
||||
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>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
@ -749,6 +749,16 @@ ul.message.error > li + li{
|
|||
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 th{
|
||||
padding: .25rem .5rem;
|
||||
|
@ -3293,6 +3303,20 @@ table.admin-table td + td{
|
|||
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 */
|
||||
/* For iPad, unset the height so it matches the other elements */
|
||||
select[multiple]{
|
||||
|
|
|
@ -12,15 +12,16 @@
|
|||
grid-column-start: 1;
|
||||
}
|
||||
|
||||
h2 + table.projects{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
table.projects thead{
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
table.data-table .status,
|
||||
table.data-table .producer{
|
||||
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>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
||||
<section id="metadata">
|
||||
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
||||
</section>
|
||||
<? } ?>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditProjects || Session::$User?->Benefits->CanManageProjects || Session::$User?->Benefits->CanReviewProjects){ ?>
|
||||
<?= Template::EbookProjects(['ebook' => $ebook]) ?>
|
||||
<? } ?>
|
||||
</article>
|
||||
</main>
|
||||
|
|
|
@ -12,7 +12,7 @@ try{
|
|||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
||||
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ try{
|
|||
|
||||
// POSTing a new ebook placeholder.
|
||||
if($httpMethod == Enums\HttpMethod::Post){
|
||||
if(!Session::$User->Benefits->CanCreateEbookPlaceholders){
|
||||
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
|
|
|
@ -398,9 +398,7 @@ catch(Exceptions\EbookNotFoundException){
|
|||
</section>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
||||
<section id="metadata">
|
||||
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
||||
</section>
|
||||
<? } ?>
|
||||
|
||||
<? if(sizeof($carousel) > 0){ ?>
|
||||
|
|
|
@ -4,7 +4,13 @@ try{
|
|||
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();
|
||||
}
|
||||
|
||||
|
@ -17,22 +23,28 @@ catch(Exceptions\LoginRequiredException){
|
|||
catch(Exceptions\InvalidPermissionsException){
|
||||
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>
|
||||
<section class="narrow">
|
||||
<section>
|
||||
<h1>Projects</h1>
|
||||
<section id="active">
|
||||
<h2>Active projects</h2>
|
||||
<? if(sizeof($inProgressProjects) == 0){ ?>
|
||||
<p>
|
||||
<i>None.</i>
|
||||
</p>
|
||||
<p class="empty-notice">None.</p>
|
||||
<? }else{ ?>
|
||||
<?= Template::ProjectsTable(['projects' => $inProgressProjects, 'includeStatus' => false]) ?>
|
||||
<? } ?>
|
||||
</section>
|
||||
|
||||
<? if(sizeof($stalledProjects) > 0){ ?>
|
||||
<section id="stalled">
|
||||
<h2>Stalled projects</h2>
|
||||
<?= Template::ProjectsTable(['projects' => $stalledProjects, 'includeStatus' => false]) ?>
|
||||
</section>
|
||||
<? } ?>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
@ -10,7 +10,7 @@ $passwordAction = HttpInput::SessionObject('password-action', Enums\PasswordActi
|
|||
|
||||
try{
|
||||
if($user === null){
|
||||
$user = User::Get(HttpInput::Int(GET, 'user-id'));
|
||||
$user = User::GetByIdentifier(HttpInput::Str(GET, 'user-identifier'));
|
||||
}
|
||||
|
||||
if(Session::$User === null){
|
||||
|
@ -40,13 +40,14 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
<?= Template::Header(
|
||||
[
|
||||
'title' => 'Edit user #' . $user->UserId,
|
||||
'canonicalUrl' => $user->Url . '/edit',
|
||||
'css' => ['/css/user.css'],
|
||||
'highlight' => ''
|
||||
]
|
||||
) ?>
|
||||
<main>
|
||||
<section class="narrow">
|
||||
<h1>Edit User #<?= $user->UserId ?></h1>
|
||||
<h1>Edit <?= Formatter::EscapeHtml($user->DisplayName) ?></h1>
|
||||
|
||||
<?= Template::Error(['exception' => $exception]) ?>
|
||||
|
||||
|
|
|
@ -8,11 +8,6 @@ $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();
|
||||
}
|
||||
|
@ -35,15 +30,16 @@ catch(Exceptions\LoginRequiredException){
|
|||
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']]) ?>
|
||||
?><?= Template::Header(
|
||||
[
|
||||
'title' => $user->DisplayName,
|
||||
'canonicalUrl' => $user->Url,
|
||||
'css' => ['/css/user.css']
|
||||
]
|
||||
) ?>
|
||||
<main>
|
||||
<section class="narrow">
|
||||
<h1>User #<?= $user->UserId ?></h1>
|
||||
<h1><?= Formatter::EscapeHtml($user->DisplayName) ?></h1>
|
||||
|
||||
<? if($isSaved){ ?>
|
||||
<p class="message success">User saved!</p>
|
||||
|
@ -51,9 +47,17 @@ catch(Exceptions\SeeOtherException $ex){
|
|||
|
||||
<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>
|
||||
<table class="admin-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>User ID:</td>
|
||||
<td><?= $user->UserId ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Email:</td>
|
||||
<td><?= Formatter::EscapeHtml($user->Email) ?></td>
|
||||
|
@ -170,8 +174,20 @@ catch(Exceptions\SeeOtherException $ex){
|
|||
<td><? if($user->Benefits->CanEditUsers){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Can create ebook placeholders:</td>
|
||||
<td><? if($user->Benefits->CanCreateEbookPlaceholders){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||
<td>Can edit ebook placeholders:</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>
|
||||
<? } ?>
|
||||
</tbody>
|
||||
|
@ -179,7 +195,7 @@ catch(Exceptions\SeeOtherException $ex){
|
|||
|
||||
<h2>Payments</h2>
|
||||
<? if(sizeof($user->Payments) == 0){ ?>
|
||||
<p>None.</p>
|
||||
<p class="empty-notice">None.</p>
|
||||
<? }else{ ?>
|
||||
<p>
|
||||
<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