mirror of
https://github.com/standardebooks/web.git
synced 2025-07-06 06:40:33 -04:00
Allow editing of projects
This commit is contained in:
parent
e21f411191
commit
b2191d1219
16 changed files with 191 additions and 75 deletions
|
@ -1 +1,6 @@
|
|||
RewriteRule ^/ebooks/([^\.]+?)/projects/new$ /projects/new.php?ebook-url-path=$1
|
||||
|
||||
RewriteRule ^/projects/([\d]+)/edit$ /projects/edit.php?project-id=$1
|
||||
|
||||
RewriteCond expr "tolower(%{REQUEST_METHOD}) =~ /^post$/"
|
||||
RewriteRule ^/projects/([\d]+)$ /projects/post.php?project-id=$1 [L]
|
||||
|
|
|
@ -36,6 +36,7 @@ use function Safe\shell_exec;
|
|||
* @property string $ReadingTime
|
||||
* @property string $AuthorsHtml
|
||||
* @property string $AuthorsUrl This is a single URL even if there are multiple authors; for example, `/ebooks/karl-marx_friedrich-engels/`.
|
||||
* @property string $AuthorsString
|
||||
* @property string $ContributorsHtml
|
||||
* @property string $TitleWithCreditsHtml
|
||||
* @property string $TextUrl
|
||||
|
@ -115,6 +116,7 @@ class Ebook{
|
|||
protected string $_ReadingTime;
|
||||
protected string $_AuthorsHtml;
|
||||
protected string $_AuthorsUrl;
|
||||
protected string $_AuthorsString;
|
||||
protected string $_ContributorsHtml;
|
||||
protected string $_TitleWithCreditsHtml;
|
||||
protected string $_TextUrl;
|
||||
|
@ -538,6 +540,14 @@ class Ebook{
|
|||
return $this->_AuthorsUrl;
|
||||
}
|
||||
|
||||
protected function GetAuthorsString(): string{
|
||||
if(!isset($this->_AuthorsString)){
|
||||
$this->_AuthorsString = strip_tags(Ebook::GenerateContributorList($this->Authors, false));
|
||||
}
|
||||
|
||||
return $this->_AuthorsString;
|
||||
}
|
||||
|
||||
protected function GetContributorsHtml(): string{
|
||||
if(!isset($this->_ContributorsHtml)){
|
||||
$this->_ContributorsHtml = '';
|
||||
|
|
|
@ -16,6 +16,7 @@ use Safe\DateTimeImmutable;
|
|||
* @property User $Manager
|
||||
* @property User $Reviewer
|
||||
* @property string $Url
|
||||
* @property string $EditUrl
|
||||
* @property DateTimeImmutable $LastActivityTimestamp The timestamp of the latest activity, whether it's a commit, a discussion post, or simply the started timestamp.
|
||||
* @property array<ProjectReminder> $Reminders
|
||||
* @property ?string $VcsUrlDomain
|
||||
|
@ -46,6 +47,7 @@ class Project{
|
|||
protected User $_Manager;
|
||||
protected User $_Reviewer;
|
||||
protected string $_Url;
|
||||
protected string $_EditUrl;
|
||||
protected DateTimeImmutable $_LastActivityTimestamp;
|
||||
/** @var array<ProjectReminder> $_Reminders */
|
||||
protected array $_Reminders;
|
||||
|
@ -115,6 +117,14 @@ class Project{
|
|||
return $this->_Url;
|
||||
}
|
||||
|
||||
protected function GetEditUrl(): string{
|
||||
if(!isset($this->_EditUrl)){
|
||||
$this->_EditUrl = $this->Url . '/edit';
|
||||
}
|
||||
|
||||
return $this->_EditUrl;
|
||||
}
|
||||
|
||||
protected function GetLastActivityTimestamp(): DateTimeImmutable{
|
||||
if(!isset($this->_LastActivityTimestamp)){
|
||||
$dates = [
|
||||
|
|
10
lib/User.php
10
lib/User.php
|
@ -9,6 +9,7 @@ use function Safe\preg_match;
|
|||
* @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 string $EditUrl
|
||||
* @property ?Patron $Patron
|
||||
* @property ?NewsletterSubscription $NewsletterSubscription
|
||||
* @property ?Payment $LastPayment
|
||||
|
@ -32,6 +33,7 @@ class User{
|
|||
protected ?Payment $_LastPayment;
|
||||
protected Benefits $_Benefits;
|
||||
protected string $_Url;
|
||||
protected string $_EditUrl;
|
||||
protected ?Patron $_Patron;
|
||||
protected ?NewsletterSubscription $_NewsletterSubscription;
|
||||
protected string $_DisplayName;
|
||||
|
@ -91,6 +93,14 @@ class User{
|
|||
return $this->_Url;
|
||||
}
|
||||
|
||||
protected function GetEditUrl(): string{
|
||||
if(!isset($this->_EditUrl)){
|
||||
$this->_EditUrl = $this->Url . '/edit';
|
||||
}
|
||||
|
||||
return $this->_EditUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Payment>
|
||||
*/
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
/**
|
||||
* @var Ebook $ebook
|
||||
*/
|
||||
|
||||
$showPlaceholderMetadata = $showPlaceholderMetadata ?? false;
|
||||
?>
|
||||
<section id="metadata">
|
||||
<h2>Metadata</h2>
|
||||
|
@ -11,7 +13,16 @@
|
|||
<td>Ebook ID:</td>
|
||||
<td><?= $ebook->EbookId ?></td>
|
||||
</tr>
|
||||
<? if($ebook->IsPlaceholder() && $ebook->EbookPlaceholder !== null){ ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<? if($showPlaceholderMetadata && $ebook->IsPlaceholder() && $ebook->EbookPlaceholder !== null){ ?>
|
||||
<section id="placeholder-metadata">
|
||||
<h2>Placeholder metadata</h2>
|
||||
<p><a href="<?= $ebook->EditUrl ?>">Edit placeholder</a></p>
|
||||
<table class="admin-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Is wanted:</td>
|
||||
<td><? if($ebook->EbookPlaceholder->IsWanted){ ?>☑<? }else{ ?>☐<? } ?></td>
|
||||
|
@ -26,7 +37,8 @@
|
|||
<td>Difficulty:</td>
|
||||
<td><?= ucfirst($ebook->EbookPlaceholder->Difficulty->value ?? '') ?></td>
|
||||
</tr>
|
||||
<? } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</section>
|
||||
<? } ?>
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?
|
||||
$ebook = $ebook ?? new Ebook();
|
||||
$isEditForm = $isEditForm ?? false;
|
||||
?>
|
||||
<fieldset>
|
||||
<legend>Contributors</legend>
|
||||
|
@ -189,7 +190,8 @@ $ebook = $ebook ?? new Ebook();
|
|||
</label>
|
||||
</fieldset>
|
||||
</details>
|
||||
<fieldset>
|
||||
<? if(!$isEditForm){ ?>
|
||||
<fieldset>
|
||||
<legend>Project</legend>
|
||||
<label class="controls-following-fieldset">
|
||||
<span>In progress?</span>
|
||||
|
@ -203,7 +205,8 @@ $ebook = $ebook ?? new Ebook();
|
|||
<fieldset class="project-form">
|
||||
<?= Template::ProjectForm(['project' => $ebook->ProjectInProgress, 'areFieldsRequired' => false]) ?>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<? } ?>
|
||||
<fieldset>
|
||||
<legend>Wanted list</legend>
|
||||
<label class="controls-following-fieldset">
|
||||
|
|
|
@ -4,13 +4,16 @@
|
|||
*/
|
||||
|
||||
$showAddButton = $showAddButton ?? false;
|
||||
$showEditButton = $showEditButton ?? false;
|
||||
?>
|
||||
<section id="projects">
|
||||
<h2>Projects</h2>
|
||||
<? if($showAddButton){ ?>
|
||||
<p>
|
||||
<a href="<?= $ebook->Url ?>/projects/new">New project</a>
|
||||
</p>
|
||||
<? } ?>
|
||||
<? if(sizeof($ebook->Projects) > 0){ ?>
|
||||
<?= Template::ProjectsTable(['projects' => $ebook->Projects, 'includeTitle' => false]) ?>
|
||||
<?= Template::ProjectsTable(['projects' => $ebook->Projects, 'includeTitle' => false, 'showEditButton' => $showEditButton]) ?>
|
||||
<? } ?>
|
||||
</section>
|
||||
|
|
|
@ -3,8 +3,11 @@ $project = $project ?? new Project();
|
|||
$managers = User::GetAllByCanManageProjects();
|
||||
$reviewers = User::GetAllByCanReviewProjects();
|
||||
$areFieldsRequired = $areFieldsRequired ?? true;
|
||||
$isEditForm = $isEditForm ?? false;
|
||||
?>
|
||||
<input type="hidden" name="project-ebook-id" value="<?= $project->EbookId ?? '' ?>" />
|
||||
<? if(!$isEditForm){ ?>
|
||||
<input type="hidden" name="project-ebook-id" value="<?= $project->EbookId ?? '' ?>" />
|
||||
<? } ?>
|
||||
|
||||
<label class="icon user">
|
||||
<span>Producer name</span>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
$includeTitle = $includeTitle ?? true;
|
||||
$includeStatus = $includeStatus ?? true;
|
||||
$showEditButton = $showEditButton ?? false;
|
||||
?>
|
||||
<table class="data-table">
|
||||
<caption aria-hidden="true">Scroll right →</caption>
|
||||
|
@ -22,6 +23,9 @@ $includeStatus = $includeStatus ?? true;
|
|||
<? } ?>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<? if($showEditButton){ ?>
|
||||
<th></th>
|
||||
<? } ?>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -65,6 +69,11 @@ $includeStatus = $includeStatus ?? true;
|
|||
<a href="<?= Formatter::EscapeHtml($project->DiscussionUrl) ?>">Discussion</a>
|
||||
<? } ?>
|
||||
</td>
|
||||
<? if($showEditButton){ ?>
|
||||
<td>
|
||||
<a href="<?= $project->EditUrl ?>">Edit</a>
|
||||
</td>
|
||||
<? } ?>
|
||||
</tr>
|
||||
<? } ?>
|
||||
</tbody>
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
grid-column: 1 / span 2
|
||||
}
|
||||
|
||||
|
||||
.project-form label:has(input[name="project-manager-user-id"]){
|
||||
grid-column-start: 1;
|
||||
}
|
||||
|
|
|
@ -3,21 +3,11 @@ use function Safe\session_unset;
|
|||
|
||||
session_start();
|
||||
|
||||
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
|
||||
/** @var string $identifier Passed from script this is included from. */
|
||||
$ebook = HttpInput::SessionObject('ebook', Ebook::class);
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
|
||||
try{
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
if($ebook === null){
|
||||
$ebook = Ebook::GetByIdentifier($identifier);
|
||||
}
|
||||
|
@ -26,6 +16,14 @@ try{
|
|||
throw new Exceptions\EbookNotFoundException();
|
||||
}
|
||||
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!Session::$User->Benefits->CanEditEbookPlaceholders){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
if($exception){
|
||||
http_response_code(Enums\HttpCode::UnprocessableContent->value);
|
||||
session_unset();
|
||||
|
@ -57,7 +55,7 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
|
||||
<form class="create-update-ebook-placeholder" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $ebook->Url ?>" autocomplete="off">
|
||||
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" />
|
||||
<?= Template::EbookPlaceholderForm(['ebook' => $ebook]) ?>
|
||||
<?= Template::EbookPlaceholderForm(['ebook' => $ebook, 'isEditForm' => true]) ?>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
@ -8,6 +8,7 @@ session_start();
|
|||
$ebook = null;
|
||||
|
||||
$isSaved = HttpInput::Bool(SESSION, 'is-ebook-placeholder-saved') ?? false;
|
||||
$isProjectSaved = HttpInput::Bool(SESSION, 'is-project-saved') ?? false;
|
||||
|
||||
try{
|
||||
$ebook = Ebook::GetByIdentifier($identifier);
|
||||
|
@ -63,7 +64,11 @@ catch(Exceptions\EbookNotFoundException){
|
|||
</header>
|
||||
|
||||
<? if($isSaved){ ?>
|
||||
<p class="message success">Ebook Placeholder saved!</p>
|
||||
<p class="message success">Ebook placeholder saved!</p>
|
||||
<? } ?>
|
||||
|
||||
<? if($isProjectSaved){ ?>
|
||||
<p class="message success">Project saved!</p>
|
||||
<? } ?>
|
||||
|
||||
<aside id="reading-ease">
|
||||
|
@ -115,17 +120,12 @@ catch(Exceptions\EbookNotFoundException){
|
|||
<? } ?>
|
||||
</section>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditEbooks){ ?>
|
||||
<?= Template::EbookMetadata(['ebook' => $ebook]) ?>
|
||||
<? if(Session::$User?->Benefits->CanEditEbooks || Session::$User?->Benefits->CanEditEbookPlaceholders){ ?>
|
||||
<?= Template::EbookMetadata(['ebook' => $ebook, 'showPlaceholderMetadata' => Session::$User?->Benefits->CanEditEbookPlaceholders]) ?>
|
||||
<? } ?>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditProjects || Session::$User?->Benefits->CanManageProjects || Session::$User?->Benefits->CanReviewProjects){ ?>
|
||||
<?= Template::EbookProjects(['ebook' => $ebook, 'showAddButton' => Session::$User->Benefits->CanEditProjects && $ebook->ProjectInProgress === null]) ?>
|
||||
<? } ?>
|
||||
|
||||
<? if(Session::$User?->Benefits->CanEditEbookPlaceholders){ ?>
|
||||
<h2>Edit ebook placeholder</h2>
|
||||
<p><a href="<?= $ebook->EditUrl ?>">Edit this ebook placeholder.</a></p>
|
||||
<?= Template::EbookProjects(['ebook' => $ebook, 'showAddButton' => Session::$User->Benefits->CanEditProjects && $ebook->ProjectInProgress === null, 'showEditButton' => Session::$User->Benefits->CanEditProjects]) ?>
|
||||
<? } ?>
|
||||
</article>
|
||||
</main>
|
||||
|
|
|
@ -16,7 +16,7 @@ try{
|
|||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
// POSTing a new ebook placeholder.
|
||||
// POSTing an `EbookPlaceholder`.
|
||||
if($httpMethod == Enums\HttpMethod::Post){
|
||||
$ebook = new Ebook();
|
||||
|
||||
|
@ -61,7 +61,8 @@ try{
|
|||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: /ebook-placeholders/new');
|
||||
}
|
||||
// PUT a new ebook placeholder.
|
||||
|
||||
// PUT an `EbookPlaceholder`.
|
||||
if($httpMethod == Enums\HttpMethod::Put){
|
||||
$originalEbook = Ebook::GetByIdentifier($identifier);
|
||||
$exceptionRedirectUrl = $originalEbook->EditUrl;
|
||||
|
@ -72,35 +73,8 @@ try{
|
|||
$ebook->EbookId = $originalEbook->EbookId;
|
||||
$ebook->Created = $originalEbook->Created;
|
||||
|
||||
// Do we have a `Project` to create/save at the same time?
|
||||
$project = null;
|
||||
if($ebook->EbookPlaceholder?->IsInProgress){
|
||||
$originalProject = $originalEbook->ProjectInProgress;
|
||||
$project = new Project();
|
||||
$project->FillFromHttpPost();
|
||||
$project->EbookId = $ebook->EbookId;
|
||||
$project->Ebook = $ebook;
|
||||
if(isset($originalProject)){
|
||||
$project->ProjectId = $originalProject->ProjectId;
|
||||
$project->Started = $originalProject->Started;
|
||||
}
|
||||
else{
|
||||
$project->Started = NOW;
|
||||
}
|
||||
$project->Validate();
|
||||
}
|
||||
|
||||
$ebook->Save();
|
||||
|
||||
if($ebook->EbookPlaceholder?->IsInProgress && $project !== null){
|
||||
if(isset($originalProject)){
|
||||
$project->Save();
|
||||
}
|
||||
else{
|
||||
$project->Create();
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['is-ebook-placeholder-saved'] = true;
|
||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: ' . $ebook->Url);
|
||||
|
|
65
www/projects/edit.php
Normal file
65
www/projects/edit.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?
|
||||
use function Safe\session_unset;
|
||||
|
||||
session_start();
|
||||
|
||||
$project = HttpInput::SessionObject('project', Project::class);
|
||||
$exception = HttpInput::SessionObject('exception', Exceptions\AppException::class);
|
||||
|
||||
try{
|
||||
if($project === null){
|
||||
$project = Project::Get(HttpInput::Int(GET, 'project-id'));
|
||||
}
|
||||
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
}
|
||||
|
||||
if(!Session::$User->Benefits->CanEditProjects){
|
||||
throw new Exceptions\InvalidPermissionsException();
|
||||
}
|
||||
|
||||
if($exception){
|
||||
http_response_code(Enums\HttpCode::UnprocessableContent->value);
|
||||
session_unset();
|
||||
}
|
||||
}
|
||||
catch(Exceptions\ProjectNotFoundException){
|
||||
Template::ExitWithCode(Enums\HttpCode::NotFound);
|
||||
}
|
||||
catch(Exceptions\LoginRequiredException){
|
||||
Template::RedirectToLogin();
|
||||
}
|
||||
catch(Exceptions\InvalidPermissionsException){
|
||||
Template::ExitWithCode(Enums\HttpCode::Forbidden);
|
||||
}
|
||||
?>
|
||||
<?= Template::Header(
|
||||
[
|
||||
'title' => 'Edit Project for ' . $project->Ebook->Title,
|
||||
'css' => ['/css/project.css'],
|
||||
'highlight' => '',
|
||||
'description' => 'Edit the project for ' . $project->Ebook->Title
|
||||
]
|
||||
) ?>
|
||||
<main>
|
||||
<section class="narrow">
|
||||
<nav class="breadcrumbs">
|
||||
<a href="<?= $project->Ebook->AuthorsUrl ?>"><?= $project->Ebook->AuthorsString ?></a> →
|
||||
<a href="<?= $project->Ebook->Url ?>"><?= Formatter::EscapeHtml($project->Ebook->Title) ?></a> →
|
||||
</nav>
|
||||
|
||||
<h1>Edit Project</h1>
|
||||
|
||||
<?= Template::Error(['exception' => $exception]) ?>
|
||||
|
||||
<form class="project-form" method="<?= Enums\HttpMethod::Post->value ?>" action="<?= $project->Url ?>" autocomplete="off">
|
||||
<input type="hidden" name="_method" value="<?= Enums\HttpMethod::Put->value ?>" />
|
||||
<?= Template::ProjectForm(['project' => $project, 'isEditForm' => true]) ?>
|
||||
<div class="footer">
|
||||
<button>Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
</main>
|
||||
<?= Template::Footer() ?>
|
|
@ -4,6 +4,7 @@ try{
|
|||
session_start();
|
||||
|
||||
$httpMethod = HttpInput::ValidateRequestMethod([Enums\HttpMethod::Post, Enums\HttpMethod::Patch, Enums\HttpMethod::Put]);
|
||||
$exceptionRedirectUrl = '/projects/new';
|
||||
|
||||
if(Session::$User === null){
|
||||
throw new Exceptions\LoginRequiredException();
|
||||
|
@ -26,6 +27,20 @@ try{
|
|||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: /projects');
|
||||
}
|
||||
|
||||
// PUTing a `Project`.
|
||||
if($httpMethod == Enums\HttpMethod::Put){
|
||||
$project = Project::Get(HttpInput::Int(GET, 'project-id'));
|
||||
$exceptionRedirectUrl = $project->EditUrl;
|
||||
|
||||
$project->FillFromHttpPost();
|
||||
|
||||
$project->Save();
|
||||
|
||||
$_SESSION['is-project-saved'] = true;
|
||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: ' . $project->Ebook->Url);
|
||||
}
|
||||
}
|
||||
catch(Exceptions\EbookNotFoundException){
|
||||
Template::ExitWithCode(Enums\HttpCode::NotFound);
|
||||
|
@ -41,5 +56,5 @@ catch(Exceptions\InvalidProjectException | Exceptions\ProjectExistsException | E
|
|||
$_SESSION['exception'] = $ex;
|
||||
|
||||
http_response_code(Enums\HttpCode::SeeOther->value);
|
||||
header('Location: /projects/new');
|
||||
header('Location: ' . $exceptionRedirectUrl);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ catch(Exceptions\InvalidPermissionsException){
|
|||
<p class="message success">User saved!</p>
|
||||
<? } ?>
|
||||
|
||||
<a href="<?= $user->Url ?>/edit">Edit user</a>
|
||||
<a href="<?= $user->EditUrl ?>">Edit user</a>
|
||||
|
||||
<? if($user->Benefits->CanManageProjects || $user->Benefits->CanReviewProjects){ ?>
|
||||
<a href="<?= $user->Url ?>/projects">Projects</a>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue