mirror of
https://github.com/standardebooks/web.git
synced 2025-07-08 07:40:39 -04:00
Add 'awaiting review' and 'reviewed' project statuses that update from GitHub; allow project owners to update their project statuses
This commit is contained in:
parent
b48f3a5798
commit
6378d687d8
12 changed files with 204 additions and 21 deletions
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE IF NOT EXISTS `Projects` (
|
CREATE TABLE IF NOT EXISTS `Projects` (
|
||||||
`ProjectId` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
`ProjectId` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||||
`Status` enum('in_progress','stalled','completed','abandoned') NOT NULL DEFAULT 'in_progress',
|
`Status` enum('in_progress','awaiting_review','reviewed','stalled','completed','abandoned') NOT NULL DEFAULT 'in_progress',
|
||||||
`EbookId` int(11) NOT NULL,
|
`EbookId` int(11) NOT NULL,
|
||||||
`ProducerName` varchar(151) NOT NULL DEFAULT '',
|
`ProducerName` varchar(151) NOT NULL DEFAULT '',
|
||||||
`ProducerEmail` varchar(80) DEFAULT NULL,
|
`ProducerEmail` varchar(80) DEFAULT NULL,
|
||||||
|
|
|
@ -185,8 +185,8 @@ final class Ebook{
|
||||||
inner join Ebooks
|
inner join Ebooks
|
||||||
on Projects.EbookId = Ebooks.EbookId
|
on Projects.EbookId = Ebooks.EbookId
|
||||||
where Ebooks.EbookId = ?
|
where Ebooks.EbookId = ?
|
||||||
and Status in (?, ?)
|
and Status in (?, ?, ?, ?)
|
||||||
', [$this->EbookId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled], Project::class)[0] ?? null;
|
', [$this->EbookId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled, Enums\ProjectStatusType::AwaitingReview, Enums\ProjectStatusType::Reviewed], Project::class)[0] ?? null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ namespace Enums;
|
||||||
|
|
||||||
enum ProjectStatusType: string{
|
enum ProjectStatusType: string{
|
||||||
case InProgress = 'in_progress';
|
case InProgress = 'in_progress';
|
||||||
|
case AwaitingReview = 'awaiting_review';
|
||||||
|
case Reviewed = 'reviewed';
|
||||||
case Stalled = 'stalled';
|
case Stalled = 'stalled';
|
||||||
case Completed = 'completed';
|
case Completed = 'completed';
|
||||||
case Abandoned = 'abandoned';
|
case Abandoned = 'abandoned';
|
||||||
|
@ -10,6 +12,7 @@ enum ProjectStatusType: string{
|
||||||
public function GetDisplayName(): string{
|
public function GetDisplayName(): string{
|
||||||
return match($this){
|
return match($this){
|
||||||
self::InProgress => 'in progress',
|
self::InProgress => 'in progress',
|
||||||
|
self::AwaitingReview => 'awaiting review',
|
||||||
default => $this->value
|
default => $this->value
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ use function Safe\preg_match;
|
||||||
use function Safe\preg_match_all;
|
use function Safe\preg_match_all;
|
||||||
use function Safe\preg_replace;
|
use function Safe\preg_replace;
|
||||||
|
|
||||||
|
use Enums\ProjectStatusType;
|
||||||
use Safe\DateTimeImmutable;
|
use Safe\DateTimeImmutable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -342,7 +343,7 @@ final class Project{
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$this->FetchLatestCommitTimestamp();
|
$this->FetchLastCommitTimestamp();
|
||||||
}
|
}
|
||||||
catch(Exceptions\AppException){
|
catch(Exceptions\AppException){
|
||||||
// Pass; it's OK if this fails during creation.
|
// Pass; it's OK if this fails during creation.
|
||||||
|
@ -511,9 +512,69 @@ final class Project{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Update this object's `Status` to `reviewed` if there is a GitHub issue containing the word `review`.
|
||||||
|
*
|
||||||
* @throws Exceptions\AppException If the operation failed.
|
* @throws Exceptions\AppException If the operation failed.
|
||||||
*/
|
*/
|
||||||
public function FetchLatestCommitTimestamp(?string $apiKey = null): void{
|
public function FetchReviewStatus(?string $apiKey = null): void{
|
||||||
|
if($this->Status == Enums\ProjectStatusType::Reviewed){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl ?? '')){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Accept: application/vnd.github+json',
|
||||||
|
'X-GitHub-Api-Version: 2022-11-28',
|
||||||
|
'User-Agent: Standard Ebooks' // Required by GitHub.
|
||||||
|
];
|
||||||
|
|
||||||
|
if($apiKey !== null){
|
||||||
|
$headers[] = 'Authorization: Bearer ' . $apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
$url = preg_replace('|^https://github.com/|iu', 'https://api.github.com/repos/', $this->VcsUrl . '/issues');
|
||||||
|
|
||||||
|
$curl = curl_init($url);
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
||||||
|
|
||||||
|
try{
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
/** @var int $httpCode */
|
||||||
|
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||||
|
|
||||||
|
if(!is_string($response)){
|
||||||
|
throw new Exceptions\AppException('Server did not respond with a string: ' . $response);
|
||||||
|
}
|
||||||
|
|
||||||
|
if($httpCode != Enums\HttpCode::Ok->value){
|
||||||
|
throw new Exception('Server responded with HTTP ' . $httpCode . '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var array<stdClass> $issues */
|
||||||
|
$issues = json_decode($response);
|
||||||
|
|
||||||
|
foreach($issues as $issue){
|
||||||
|
if(preg_match('/\breview/iu', $issue->title)){
|
||||||
|
$this->Status = Enums\ProjectStatusType::Reviewed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(Exception $ex){
|
||||||
|
throw new Exceptions\AppException('Error when fetching issues for URL <' . $url . '>: ' . $ex->getMessage(), 0, $ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this object's `LastCommitTimestamp` with data from its GitHub repo.
|
||||||
|
*
|
||||||
|
* @throws Exceptions\AppException If the operation failed.
|
||||||
|
*/
|
||||||
|
public function FetchLastCommitTimestamp(?string $apiKey = null): void{
|
||||||
if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl ?? '')){
|
if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl ?? '')){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -574,7 +635,9 @@ final class Project{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exceptions\AppException If the operation faile.d
|
* Update this object's `LastDiscussionTimestamp` with data from its discussion page.
|
||||||
|
*
|
||||||
|
* @throws Exceptions\AppException If the operation failed.
|
||||||
*/
|
*/
|
||||||
public function FetchLastDiscussionTimestamp(): void{
|
public function FetchLastDiscussionTimestamp(): void{
|
||||||
if(!preg_match('|^https://groups\.google\.com/g/standardebooks/|iu', $this->DiscussionUrl ?? '')){
|
if(!preg_match('|^https://groups\.google\.com/g/standardebooks/|iu', $this->DiscussionUrl ?? '')){
|
||||||
|
@ -628,6 +691,9 @@ final class Project{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an email reminder to the producer notifying them about their project status.
|
||||||
|
*/
|
||||||
public function SendReminder(Enums\ProjectReminderType $type): void{
|
public function SendReminder(Enums\ProjectReminderType $type): void{
|
||||||
if($this->ProducerEmail === null || $this->GetReminder($type) !== null){
|
if($this->ProducerEmail === null || $this->GetReminder($type) !== null){
|
||||||
return;
|
return;
|
||||||
|
@ -693,18 +759,27 @@ final class Project{
|
||||||
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where Projects.Status = ? order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$status], Project::class);
|
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where Projects.Status = ? order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$status], Project::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<Enums\ProjectStatusType> $statuses
|
||||||
|
*
|
||||||
|
* @return array<Project>
|
||||||
|
*/
|
||||||
|
public static function GetAllByStatuses(array $statuses): array{
|
||||||
|
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where Projects.Status in ' . Db::CreateSetSql($statuses) . ' order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', $statuses, Project::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Project>
|
* @return array<Project>
|
||||||
*/
|
*/
|
||||||
public static function GetAllByManagerUserId(int $userId): array{
|
public static function GetAllByManagerUserId(int $userId): array{
|
||||||
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where ManagerUserId = ? and Status in (?, ?) order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled], Project::class);
|
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where ManagerUserId = ? and Status in (?, ?, ?, ?) order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled, Enums\ProjectStatusType::AwaitingReview, ProjectStatusType::Reviewed], Project::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<Project>
|
* @return array<Project>
|
||||||
*/
|
*/
|
||||||
public static function GetAllByReviewerUserId(int $userId): array{
|
public static function GetAllByReviewerUserId(int $userId): array{
|
||||||
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where ReviewerUserId = ? and Status in (?, ?) order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled], Project::class);
|
return Db::MultiTableSelect('SELECT * from Projects inner join Ebooks on Projects.EbookId = Ebooks.EbookId where ReviewerUserId = ? and Status in (?, ?, ?, ?) order by regexp_replace(Title, \'^(A|An|The)\\\s\', \'\') asc', [$userId, Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::Stalled, Enums\ProjectStatusType::AwaitingReview, ProjectStatusType::Reviewed], Project::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -16,10 +16,7 @@ use Safe\DateTimeImmutable;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @var array<Project> $projects */
|
/** @var array<Project> $projects */
|
||||||
$projects = array_merge(
|
$projects = Project::GetAllByStatuses([Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::AwaitingReview, Enums\ProjectStatusType::Reviewed, Enums\ProjectStatusType::Stalled]);
|
||||||
Project::GetAllByStatus(Enums\ProjectStatusType::InProgress),
|
|
||||||
Project::GetAllByStatus(Enums\ProjectStatusType::Stalled)
|
|
||||||
);
|
|
||||||
|
|
||||||
$apiKey = trim(file_get_contents('/standardebooks.org/config/secrets/se-vcs-bot@api.github.com'));
|
$apiKey = trim(file_get_contents('/standardebooks.org/config/secrets/se-vcs-bot@api.github.com'));
|
||||||
$oldestStalledTimestamp = new DateTimeImmutable('60 days ago');
|
$oldestStalledTimestamp = new DateTimeImmutable('60 days ago');
|
||||||
|
@ -34,7 +31,7 @@ foreach($projects as $project){
|
||||||
}
|
}
|
||||||
|
|
||||||
try{
|
try{
|
||||||
$project->FetchLatestCommitTimestamp($apiKey);
|
$project->FetchLastCommitTimestamp($apiKey);
|
||||||
|
|
||||||
}
|
}
|
||||||
catch(Exceptions\AppException $ex){
|
catch(Exceptions\AppException $ex){
|
||||||
|
@ -42,8 +39,19 @@ foreach($projects as $project){
|
||||||
}
|
}
|
||||||
|
|
||||||
if($project->IsStatusAutomaticallyUpdated){
|
if($project->IsStatusAutomaticallyUpdated){
|
||||||
|
// Check if this project is in review.
|
||||||
|
if($project->Status == Enums\ProjectStatusType::InProgress){
|
||||||
|
$project->FetchReviewStatus();
|
||||||
|
}
|
||||||
|
|
||||||
if(
|
if(
|
||||||
$project->Status == Enums\ProjectStatusType::InProgress
|
(
|
||||||
|
$project->Status == Enums\ProjectStatusType::InProgress
|
||||||
|
||
|
||||||
|
$project->Status == Enums\ProjectStatusType::AwaitingReview
|
||||||
|
||
|
||||||
|
$project->Status == Enums\ProjectStatusType::Reviewed
|
||||||
|
)
|
||||||
&&
|
&&
|
||||||
$project->LastActivityTimestamp < $oldestStalledTimestamp
|
$project->LastActivityTimestamp < $oldestStalledTimestamp
|
||||||
){
|
){
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
* @var Project $project
|
* @var Project $project
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Enums\HttpMethod;
|
||||||
|
|
||||||
$useFullyQualifiedUrls = $useFullyQualifiedUrls ?? false;
|
$useFullyQualifiedUrls = $useFullyQualifiedUrls ?? false;
|
||||||
$showTitle = $showTitle ?? true;
|
$showTitle = $showTitle ?? true;
|
||||||
$showArtworkStatus = $showArtworkStatus ?? true;
|
$showArtworkStatus = $showArtworkStatus ?? true;
|
||||||
|
@ -69,5 +71,37 @@ $showArtworkStatus = $showArtworkStatus ?? true;
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
<tr>
|
||||||
|
<td>Status:</td>
|
||||||
|
<td>
|
||||||
|
<? if(
|
||||||
|
Session::$User?->Benefits->CanEditProjects
|
||||||
|
||
|
||||||
|
$project->ManagerUserId == Session::$User?->UserId
|
||||||
|
||
|
||||||
|
$project->ReviewerUserId == Session::$User?->UserId
|
||||||
|
){ ?>
|
||||||
|
|
||||||
|
<form action="<?= $project->Url ?>" method="<?= HttpMethod::Post->value ?>" class="single-line-form">
|
||||||
|
<input type="hidden" name="_method" value="<?= HttpMethod::Patch->value ?>" />
|
||||||
|
<label class="icon meter">
|
||||||
|
<span>
|
||||||
|
<select name="project-status">
|
||||||
|
<option value="<?= Enums\ProjectStatusType::InProgress->value ?>"<? if($project->Status == Enums\ProjectStatusType::InProgress){?> selected="selected"<? } ?>>In progress</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::AwaitingReview->value ?>"<? if($project->Status == Enums\ProjectStatusType::AwaitingReview){?> selected="selected"<? } ?>>Awaiting review</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::Reviewed->value ?>"<? if($project->Status == Enums\ProjectStatusType::Reviewed){?> selected="selected"<? } ?>>Reviewed</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::Stalled->value ?>"<? if($project->Status == Enums\ProjectStatusType::Stalled){?> selected="selected"<? } ?>>Stalled</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::Completed->value ?>"<? if($project->Status == Enums\ProjectStatusType::Completed){?> selected="selected"<? } ?>>Completed</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::Abandoned->value ?>"<? if($project->Status == Enums\ProjectStatusType::Abandoned){?> selected="selected"<? } ?>>Abandoned</option>
|
||||||
|
</select>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<button>Save changes</button>
|
||||||
|
</form>
|
||||||
|
<? }else{ ?>
|
||||||
|
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
||||||
|
<? } ?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -77,6 +77,8 @@ $isEditForm = $isEditForm ?? false;
|
||||||
<span>
|
<span>
|
||||||
<select name="project-status">
|
<select name="project-status">
|
||||||
<option value="<?= Enums\ProjectStatusType::InProgress->value ?>"<? if($project->Status == Enums\ProjectStatusType::InProgress){?> selected="selected"<? } ?>>In progress</option>
|
<option value="<?= Enums\ProjectStatusType::InProgress->value ?>"<? if($project->Status == Enums\ProjectStatusType::InProgress){?> selected="selected"<? } ?>>In progress</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::AwaitingReview->value ?>"<? if($project->Status == Enums\ProjectStatusType::AwaitingReview){?> selected="selected"<? } ?>>Awaiting review</option>
|
||||||
|
<option value="<?= Enums\ProjectStatusType::Reviewed->value ?>"<? if($project->Status == Enums\ProjectStatusType::Reviewed){?> selected="selected"<? } ?>>Reviewed</option>
|
||||||
<option value="<?= Enums\ProjectStatusType::Stalled->value ?>"<? if($project->Status == Enums\ProjectStatusType::Stalled){?> selected="selected"<? } ?>>Stalled</option>
|
<option value="<?= Enums\ProjectStatusType::Stalled->value ?>"<? if($project->Status == Enums\ProjectStatusType::Stalled){?> selected="selected"<? } ?>>Stalled</option>
|
||||||
<option value="<?= Enums\ProjectStatusType::Completed->value ?>"<? if($project->Status == Enums\ProjectStatusType::Completed){?> selected="selected"<? } ?>>Completed</option>
|
<option value="<?= Enums\ProjectStatusType::Completed->value ?>"<? if($project->Status == Enums\ProjectStatusType::Completed){?> selected="selected"<? } ?>>Completed</option>
|
||||||
<option value="<?= Enums\ProjectStatusType::Abandoned->value ?>"<? if($project->Status == Enums\ProjectStatusType::Abandoned){?> selected="selected"<? } ?>>Abandoned</option>
|
<option value="<?= Enums\ProjectStatusType::Abandoned->value ?>"<? if($project->Status == Enums\ProjectStatusType::Abandoned){?> selected="selected"<? } ?>>Abandoned</option>
|
||||||
|
|
|
@ -59,7 +59,7 @@ $showEditButton = $showEditButton ?? false;
|
||||||
<? } ?>
|
<? } ?>
|
||||||
</td>
|
</td>
|
||||||
<? if($includeStatus){ ?>
|
<? if($includeStatus){ ?>
|
||||||
<td class="status<? if($project->Status == Enums\ProjectStatusType::Stalled){ ?> stalled<? } ?>">
|
<td class="status<? if($project->Status == Enums\ProjectStatusType::Stalled){ ?> stalled<? } ?><? if($project->Status == Enums\ProjectStatusType::AwaitingReview){ ?> awaiting-review<? } ?><? if($project->Status == Enums\ProjectStatusType::Reviewed){ ?> reviewed<? } ?>">
|
||||||
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
<?= ucfirst($project->Status->GetDisplayName()) ?>
|
||||||
</td>
|
</td>
|
||||||
<? } ?>
|
<? } ?>
|
||||||
|
|
|
@ -1927,7 +1927,7 @@ label:has(select) > span + span:last-child{
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
label:has(select) > span + span:last-child::after{
|
label:has(select) > span:has(select)::after{
|
||||||
display: block;
|
display: block;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -2227,6 +2227,15 @@ main nav.pagination ol li.highlighted:nth-last-child(2)::after{
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.single-line-form{
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-form button{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.has-hero hgroup{
|
.has-hero hgroup{
|
||||||
padding: 2rem 0 1.75rem 0;
|
padding: 2rem 0 1.75rem 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -3786,6 +3795,14 @@ a.patron-selection:any-link:hover{
|
||||||
gap: 0;
|
gap: 0;
|
||||||
padding: .5rem .75rem;
|
padding: .5rem .75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.admin-table .single-line-form{
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.admin-table .single-line-form button{
|
||||||
|
align-self: flex-end;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media(max-width: 680px){
|
@media(max-width: 680px){
|
||||||
|
|
|
@ -35,11 +35,25 @@ table.data-table{
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.data-table td.status{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
table.data-table td.status.stalled{
|
table.data-table td.status.stalled{
|
||||||
background: #861d1d !important; /* Override hover backgound color */
|
background: #861d1d !important; /* Override hover backgound color */
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.data-table td.status.awaiting-review{
|
||||||
|
background: #d5b434 !important; /* Override hover backgound color */
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.data-table td.status.reviewed{
|
||||||
|
background: #1d863c !important; /* Override hover backgound color */
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
@media(max-width: 750px){
|
@media(max-width: 750px){
|
||||||
.project-form{
|
.project-form{
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|
|
@ -28,7 +28,7 @@ try{
|
||||||
session_unset();
|
session_unset();
|
||||||
}
|
}
|
||||||
|
|
||||||
$inProgressProjects = Project::GetAllByStatus(Enums\ProjectStatusType::InProgress);
|
$inProgressProjects = Project::GetAllByStatuses([Enums\ProjectStatusType::InProgress, Enums\ProjectStatusType::AwaitingReview, Enums\ProjectStatusType::Reviewed]);
|
||||||
$stalledProjects = Project::GetAllByStatus(Enums\ProjectStatusType::Stalled);
|
$stalledProjects = Project::GetAllByStatus(Enums\ProjectStatusType::Stalled);
|
||||||
}
|
}
|
||||||
catch(Exceptions\LoginRequiredException){
|
catch(Exceptions\LoginRequiredException){
|
||||||
|
|
|
@ -10,12 +10,12 @@ try{
|
||||||
throw new Exceptions\LoginRequiredException();
|
throw new Exceptions\LoginRequiredException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Session::$User->Benefits->CanEditProjects){
|
|
||||||
throw new Exceptions\InvalidPermissionsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// POSTing a new `Project`.
|
// POSTing a new `Project`.
|
||||||
if($httpMethod == Enums\HttpMethod::Post){
|
if($httpMethod == Enums\HttpMethod::Post){
|
||||||
|
if(!Session::$User->Benefits->CanEditProjects){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
$project = new Project();
|
$project = new Project();
|
||||||
|
|
||||||
$project->FillFromHttpPost();
|
$project->FillFromHttpPost();
|
||||||
|
@ -69,6 +69,10 @@ try{
|
||||||
|
|
||||||
// PUTing a `Project`.
|
// PUTing a `Project`.
|
||||||
if($httpMethod == Enums\HttpMethod::Put){
|
if($httpMethod == Enums\HttpMethod::Put){
|
||||||
|
if(!Session::$User->Benefits->CanEditProjects){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
$project = Project::Get(HttpInput::Int(GET, 'project-id'));
|
$project = Project::Get(HttpInput::Int(GET, 'project-id'));
|
||||||
$exceptionRedirectUrl = $project->EditUrl;
|
$exceptionRedirectUrl = $project->EditUrl;
|
||||||
|
|
||||||
|
@ -80,6 +84,32 @@ try{
|
||||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||||
header('Location: ' . $project->Ebook->Url);
|
header('Location: ' . $project->Ebook->Url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PATCHing a `Project`.
|
||||||
|
if($httpMethod == Enums\HttpMethod::Patch){
|
||||||
|
$project = Project::Get(HttpInput::Int(GET, 'project-id'));
|
||||||
|
$exceptionRedirectUrl = $project->EditUrl;
|
||||||
|
|
||||||
|
if(
|
||||||
|
!Session::$User->Benefits->CanEditProjects
|
||||||
|
&&
|
||||||
|
(
|
||||||
|
$project->ManagerUserId != Session::$User->UserId
|
||||||
|
||
|
||||||
|
$project->ReviewerUserId != Session::$User->UserId
|
||||||
|
)
|
||||||
|
){
|
||||||
|
throw new Exceptions\InvalidPermissionsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$project->PropertyFromHttp('Status');
|
||||||
|
|
||||||
|
$project->Save();
|
||||||
|
|
||||||
|
$_SESSION['is-project-saved'] = true;
|
||||||
|
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||||
|
header('Location: ' . $project->Ebook->Url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(Exceptions\EbookNotFoundException){
|
catch(Exceptions\EbookNotFoundException){
|
||||||
Template::ExitWithCode(Enums\HttpCode::NotFound);
|
Template::ExitWithCode(Enums\HttpCode::NotFound);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue