Allow VCS URLs to be null in projects

This commit is contained in:
Alex Cabal 2024-12-16 21:31:49 -06:00
parent d902074285
commit 7a3c7ad503
12 changed files with 94 additions and 36 deletions

View file

@ -5,7 +5,7 @@ CREATE TABLE IF NOT EXISTS `Projects` (
`ProducerName` varchar(151) NOT NULL DEFAULT '', `ProducerName` varchar(151) NOT NULL DEFAULT '',
`ProducerEmail` varchar(80) DEFAULT NULL, `ProducerEmail` varchar(80) DEFAULT NULL,
`DiscussionUrl` varchar(255) DEFAULT NULL, `DiscussionUrl` varchar(255) DEFAULT NULL,
`VcsUrl` varchar(255) NOT NULL, `VcsUrl` varchar(255) DEFAULT NULL,
`Created` timestamp NOT NULL DEFAULT current_timestamp(), `Created` timestamp NOT NULL DEFAULT current_timestamp(),
`Updated` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), `Updated` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`Started` datetime NOT NULL, `Started` datetime NOT NULL,

View file

@ -1,7 +0,0 @@
<?
namespace Exceptions;
class VcsUrlRequiredException extends AppException{
/** @var string $message */
protected $message = 'VCS URL is required.';
}

View file

@ -4,6 +4,7 @@ use function Safe\curl_getinfo;
use function Safe\curl_init; use function Safe\curl_init;
use function Safe\curl_setopt; use function Safe\curl_setopt;
use function Safe\json_decode; use function Safe\json_decode;
use function Safe\parse_url;
use function Safe\preg_match; 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;
@ -17,6 +18,8 @@ use Safe\DateTimeImmutable;
* @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. * @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 array<ProjectReminder> $Reminders
* @property ?string $VcsUrlDomain
* @property ?string $DiscussionUrlDomain
*/ */
class Project{ class Project{
use Traits\Accessor; use Traits\Accessor;
@ -28,7 +31,7 @@ class Project{
public string $ProducerName; public string $ProducerName;
public ?string $ProducerEmail = null; public ?string $ProducerEmail = null;
public ?string $DiscussionUrl = null; public ?string $DiscussionUrl = null;
public string $VcsUrl; public ?string $VcsUrl;
public DateTimeImmutable $Created; public DateTimeImmutable $Created;
public DateTimeImmutable $Updated; public DateTimeImmutable $Updated;
public DateTimeImmutable $Started; public DateTimeImmutable $Started;
@ -46,12 +49,64 @@ class Project{
protected DateTimeImmutable $_LastActivityTimestamp; protected DateTimeImmutable $_LastActivityTimestamp;
/** @var array<ProjectReminder> $_Reminders */ /** @var array<ProjectReminder> $_Reminders */
protected array $_Reminders; protected array $_Reminders;
protected ?string $_VcsUrlDomain;
protected ?string $_DiscussionUrlDomain;
// ******* // *******
// GETTERS // GETTERS
// ******* // *******
protected function GetVcsUrlDomain(): ?string{
if(!isset($this->_VcsUrlDomain)){
if($this->VcsUrl === null){
$this->_VcsUrlDomain = null;
}
else{
try{
$domain = parse_url($this->VcsUrl, PHP_URL_HOST);
if(is_string($domain)){
$this->_VcsUrlDomain = strtolower($domain);
}
else{
$this->_VcsUrlDomain = null;
}
}
catch(\Exception){
$this->_VcsUrlDomain = null;
}
}
}
return $this->_VcsUrlDomain;
}
protected function GetDiscussionUrlDomain(): ?string{
if(!isset($this->_DiscussionUrlDomain)){
if($this->DiscussionUrl === null){
$this->_DiscussionUrlDomain = null;
}
else{
try{
$domain = parse_url($this->DiscussionUrl, PHP_URL_HOST);
if(is_string($domain)){
$this->_DiscussionUrlDomain = strtolower($domain);
}
else{
$this->_DiscussionUrlDomain = null;
}
}
catch(\Exception){
$this->_DiscussionUrlDomain = null;
}
}
}
return $this->_DiscussionUrlDomain;
}
protected function GetUrl(): string{ protected function GetUrl(): string{
if(!isset($this->_Url)){ if(!isset($this->_Url)){
$this->_Url = '/projects/' . $this->ProjectId; $this->_Url = '/projects/' . $this->ProjectId;
@ -158,12 +213,15 @@ class Project{
} }
} }
$this->VcsUrl = rtrim(trim($this->VcsUrl ?? ''), '/'); $this->VcsUrl = trim($this->VcsUrl ?? '');
if($this->VcsUrl == ''){ if($this->VcsUrl == ''){
$error->Add(new Exceptions\VcsUrlRequiredException()); $this->VcsUrl = null;
} }
elseif(!preg_match('|^https://github.com/[^/]+/[^/]+|ius', $this->VcsUrl)){ elseif(preg_match('|^https?://(www\.)?github.com/|ius', $this->VcsUrl)){
$error->Add(new Exceptions\InvalidVcsUrlException()); $this->VcsUrl = rtrim($this->VcsUrl, '/');
if(!preg_match('|^https://github.com/[^/]+/[^/]+|ius', $this->VcsUrl)){
$error->Add(new Exceptions\InvalidVcsUrlException());
}
} }
if(!isset($this->ManagerUserId)){ if(!isset($this->ManagerUserId)){
@ -373,7 +431,7 @@ 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)){ if(!preg_match('|^https://github\.com/|iu', $this->VcsUrl ?? '')){
return; return;
} }

View file

@ -5,7 +5,7 @@
*/ */
?> ?>
<table class="data-table bulk-downloads-table"> <table class="data-table bulk-downloads-table">
<caption aria-hidden="hidden">Scroll right </caption> <caption aria-hidden="true">Scroll right </caption>
<thead> <thead>
<tr class="mid-header"> <tr class="mid-header">
<th scope="col"><?= Formatter::EscapeHtml($label) ?></th> <th scope="col"><?= Formatter::EscapeHtml($label) ?></th>

View file

@ -36,17 +36,19 @@
<a href="<?= SITE_URL ?><?= $project->Reviewer->Url ?>/projects"><?= Formatter::EscapeHtml($project->Reviewer->DisplayName) ?></a> <a href="<?= SITE_URL ?><?= $project->Reviewer->Url ?>/projects"><?= Formatter::EscapeHtml($project->Reviewer->DisplayName) ?></a>
</td> </td>
</tr> </tr>
<tr> <? if($project->VcsUrl !== null){ ?>
<td>Repository:</td> <tr>
<td> <td>Repository:</td>
<a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>">GitHub</a> <td>
</td> <a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>"><?= Formatter::EscapeHtml($project->VcsUrlDomain) ?></a>
</tr> </td>
</tr>
<? } ?>
<? if($project->DiscussionUrl !== null){ ?> <? if($project->DiscussionUrl !== null){ ?>
<tr> <tr>
<td>Discussion:</td> <td>Discussion:</td>
<td> <td>
<a href="<?= Formatter::EscapeHtml($project->DiscussionUrl) ?>">Google Groups</a> <a href="<?= Formatter::EscapeHtml($project->DiscussionUrl) ?>"><?= Formatter::EscapeHtml($project->DiscussionUrlDomain) ?></a>
</td> </td>
</tr> </tr>
<? } ?> <? } ?>

View file

@ -16,10 +16,12 @@ Youve been assigned a new ebook project to **<?= $role ?>**:
- Reviewer: [<?= Formatter::EscapeMarkdown($project->Reviewer->DisplayName) ?>](<?= Formatter::EscapeMarkdown(SITE_URL . $project->Reviewer->Url . '/projects') ?>) - Reviewer: [<?= Formatter::EscapeMarkdown($project->Reviewer->DisplayName) ?>](<?= Formatter::EscapeMarkdown(SITE_URL . $project->Reviewer->Url . '/projects') ?>)
- Repository: [GitHub](<?= Formatter::EscapeMarkdown($project->VcsUrl) ?>) <? if($project->VcsUrl !== null){ ?>
- Repository: [<?= Formatter::EscapeHtml($project->VcsUrlDomain) ?>](<?= Formatter::EscapeMarkdown($project->VcsUrl) ?>)
<? } ?>
<? if($project->DiscussionUrl !== null){ ?> <? if($project->DiscussionUrl !== null){ ?>
- Discussion: [Google Groups](<?= Formatter::EscapeMarkdown($project->DiscussionUrl) ?>) - Discussion: [<?= Formatter::EscapeHtml($project->DiscussionUrlDomain) ?>](<?= Formatter::EscapeMarkdown($project->DiscussionUrl) ?>)
<? } ?> <? } ?>
If youre unable to <?= $role ?> this ebook project, [email the Editor-in-Chief](mailto:<?= Formatter::EscapeMarkdown(EDITOR_IN_CHIEF_EMAIL_ADDRESS) ?>) and well reassign it. If youre unable to <?= $role ?> this ebook project, [email the Editor-in-Chief](mailto:<?= Formatter::EscapeMarkdown(EDITOR_IN_CHIEF_EMAIL_ADDRESS) ?>) and well reassign it.

View file

@ -76,8 +76,6 @@ $areFieldsRequired = $areFieldsRequired ?? true;
<input <input
type="url" type="url"
name="project-vcs-url" name="project-vcs-url"
placeholder="https://github.com/..."
pattern="^https:\/\/github\.com\/[^\/]+/[^\/]+/?$"
autocomplete="off" autocomplete="off"
value="<?= Formatter::EscapeHtml($project->VcsUrl ?? '') ?>" value="<?= Formatter::EscapeHtml($project->VcsUrl ?? '') ?>"
/> />

View file

@ -7,7 +7,7 @@ $includeTitle = $includeTitle ?? true;
$includeStatus = $includeStatus ?? true; $includeStatus = $includeStatus ?? true;
?> ?>
<table class="data-table"> <table class="data-table">
<caption aria-hidden="hidden">Scroll right </caption> <caption aria-hidden="true">Scroll right </caption>
<thead> <thead>
<tr class="mid-header"> <tr class="mid-header">
<? if($includeTitle){ ?> <? if($includeTitle){ ?>
@ -20,8 +20,8 @@ $includeStatus = $includeStatus ?? true;
<? if($includeStatus){ ?> <? if($includeStatus){ ?>
<th scope="col">Status</th> <th scope="col">Status</th>
<? } ?> <? } ?>
<th/> <th></th>
<th/> <th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -56,7 +56,9 @@ $includeStatus = $includeStatus ?? true;
</td> </td>
<? } ?> <? } ?>
<td> <td>
<a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>">Repository</a> <? if($project->VcsUrl !== null){ ?>
<a href="<?= Formatter::EscapeHtml($project->VcsUrl) ?>">Repository</a>
<? } ?>
</td> </td>
<td> <td>
<? if($project->DiscussionUrl !== null){ ?> <? if($project->DiscussionUrl !== null){ ?>

View file

@ -37,7 +37,7 @@ $title = preg_replace('/s$/', '', ucfirst($class));
<p>These zip files contain each ebook in every format we offer, and are kept updated with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p> <p>These zip files contain each ebook in every format we offer, and are kept updated with the latest versions of each ebook. Read about <a href="/help/how-to-use-our-ebooks#which-file-to-download">which file format to download</a>.</p>
<? if($class == 'months'){ ?> <? if($class == 'months'){ ?>
<table class="data-table"> <table class="data-table">
<caption aria-hidden="hidden">Scroll right </caption> <caption aria-hidden="true">Scroll right </caption>
<tbody> <tbody>
<? foreach($collection as $year => $months){ ?> <? foreach($collection as $year => $months){ ?>
<? $yearHeader = Formatter::EscapeHtml($year); ?> <? $yearHeader = Formatter::EscapeHtml($year); ?>

View file

@ -364,7 +364,7 @@
</li> </li>
<li id="i-still-have-questions"> <li id="i-still-have-questions">
<h2>I still have questions!</h2> <h2>I still have questions!</h2>
<p>If youre unsure about anything, or have a question that isnt answered here, please ask on the <a href="https://groups.google.com/g/standardebooks">Google Groups mailing list.</a> The experienced producers there can answer any question you might have.</p> <p>If youre unsure about anything, or have a question that isnt answered here, please ask on our <a href="https://groups.google.com/g/standardebooks">mailing list.</a> The experienced producers there can answer any question you might have.</p>
</li> </li>
</ol> </ol>
</article> </article>

View file

@ -72,10 +72,12 @@ catch(Exceptions\InvalidPermissionsException){
<?= Template::Error(['exception' => $exception]) ?> <?= Template::Error(['exception' => $exception]) ?>
<? if($isOnlyProjectCreated){ ?> <? if(isset($createdEbook)){ ?>
<p class="message success">An ebook placeholder <a href="<?= $createdEbook->Url ?>">already exists</a> for this ebook, but a a new project was created!</p> <? if($isOnlyProjectCreated){ ?>
<? }elseif($isCreated && isset($createdEbook)){ ?> <p class="message success">An ebook placeholder <a href="<?= $createdEbook->Url ?>">already exists</a> for this ebook, but a a new project was created!</p>
<p class="message success">Ebook placeholder created: <a href="<?= $createdEbook->Url ?>"><?= Formatter::EscapeHtml($createdEbook->Title) ?></a>!</p> <? }elseif($isCreated){ ?>
<p class="message success">Ebook placeholder created: <a href="<?= $createdEbook->Url ?>"><?= Formatter::EscapeHtml($createdEbook->Title) ?></a>!</p>
<? } ?>
<? } ?> <? } ?>
<form class="create-update-ebook-placeholder" method="<?= Enums\HttpMethod::Post->value ?>" action="/ebook-placeholders" autocomplete="off"> <form class="create-update-ebook-placeholder" method="<?= Enums\HttpMethod::Post->value ?>" action="/ebook-placeholders" autocomplete="off">

View file

@ -1,4 +1,5 @@
<? <?
use function Safe\session_unset;
try{ try{
if(Session::$User === null){ if(Session::$User === null){